1

After fiddling with a bunch of CMake settings in a project I'm working on, I'm encountering a linking issue which I didn't previously experience.

In a nutshell, I have a static library (.a file) with the following symbol (demangled):

00000000000018e0 g     F .text  0000000000000690 std::experimental::fundamentals_v1::optional<int> monetdb::gdk::buffer_pool::find_column<(monetdb::column_name_kind)2>(monetdb::column_name<(monetdb::column_name_kind)2> const&) const

but when I try to link an executable with this file and a compiled source using that method, I get:

main.cpp:(.text+0x6950): undefined reference to `std::experimental::optional<int> monetdb::gdk::buffer_pool::find_column<(monetdb::column_name_kind)2>(monetdb::column_name<(monetdb::column_name_kind)2> const&) const'

This is the single and only linking error, even though I instantiate a buffer_pool and use a bunch of other methods. On the other hand, this is also the only templated method the class have.

What are the potential causes for such an this error, given the existence of the symbol in the library?

My only "clue" so far is, that the name of the optional class is different: std::experimental::optional vs std::experimental::fundamentals_v1::optional. Could this be the cause?

Notes:

  • I'm asking about potential causes, not the actual cause (which you can't determine without a self-contained example).
  • I explicitly instantiate this templated method, just to be on the safe side.
  • Compilation used g++ 8.3.0, and /usr/bin/ld is 2.32.51 .
  • I'm on Devuan 3 Beowulf (~= Debian 10 Buster without systemd).
  • Result of g++- v:

    Using built-in specs.
    COLLECT_GCC=g++-8
    COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/8/lto-wrapper
    OFFLOAD_TARGET_NAMES=nvptx-none
    OFFLOAD_TARGET_DEFAULT=1
    Target: x86_64-linux-gnu
    Configured with: ../src/configure -v --with-pkgversion='Debian 8.3.0-22' --with-bugurl=file:///usr/share/doc/gcc-8/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-8 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto --enable-link-mutex
    Thread model: posix
    gcc version 8.3.0 (Debian 8.3.0-22) 
    
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • It would be nice to have a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Zheng Qu Feb 03 '20 at 22:04
  • It would improve the question to contain a [MRE](https://stackoverflow.com/help/minimal-reproducible-example) and the compilation command used. (If you can reproduce using `g++` commandline on a single file that will make things much easier since nobody can really tell what your cmake might be doing) – M.M Feb 03 '20 at 22:05
  • @ZhengQu: Yes, it would... but I asked about _potential_ causes, not _actual_ causes. Let me clarify that. – einpoklum Feb 03 '20 at 22:05
  • @M.M: There you go. – einpoklum Feb 03 '20 at 22:10
  • Did you use the same compiler and settings for building all the libraries involved? – M.M Feb 03 '20 at 22:14
  • This is off-topic without an MRE imo (and low-effort) – M.M Feb 03 '20 at 22:15
  • Pure speculation, but possibly your static library and executable are using different versions of the `` header. The static library version seems to be older and has parked all that experimental stuff into the inner `fundamentals_v1` namespace that your executable build does not. Can you rebuild the static library, ensuring the same compiler version and include paths? – 1201ProgramAlarm Feb 03 '20 at 22:18
  • @M.M: To my knowledge, I have used the same compiler and settings - but maybe 1021ProgramAlarm is right. – einpoklum Feb 03 '20 at 22:41
  • @1201ProgramAlarm: Did such a change happen in libstdc++ at some point? Say... between versions 8 and 9 perhaps? Because my system's default compiler is GCC 9.2.1 . – einpoklum Feb 03 '20 at 22:42
  • 2
    Experimenting on Compiler Explorer shows that recent versions of gcc (I spot checked 6.1 thru 9.3) will map `std::experimental::optional` to `std::experimental::fundamentals_v1::optional`. I don't know what you could be doing in your executable build that would change that. – 1201ProgramAlarm Feb 03 '20 at 22:51
  • @1201ProgramAlarm: ... but I do - see my answer. Thanks to you, I solved the mystery :-) – einpoklum Feb 03 '20 at 23:02
  • Glad I could help. – 1201ProgramAlarm Feb 03 '20 at 23:21
  • _"given the existence of the symbol in the library?"_ But that isn't a given. It's not true at all, because those symbols are obviously not the same. If the linker says it needs symbol `abcd` and you see `abd` in the library, **that's the cause of the error**. The linker and `nm` are telling you the exact symbol names. If they don't match, **that's the cause of the error**. – Jonathan Wakely Feb 05 '20 at 13:31
  • @JonathanWakely: 1. It was "given the existence of the symbol I listed". 2. It is not entirely obvious to everyone (e.g. it wasn't to me) that the symbols are incompatible, even if not literally identical. 3. The mismatch was not the cause of the error, it was a symptom of the error. Or if you wish - it was the immediate cause, not the deeper cause. – einpoklum Feb 05 '20 at 13:46
  • The linker requires symbol names to be identical. Two symbols that look similar but are not identical are **different symbols**. Period. – Jonathan Wakely Feb 05 '20 at 13:50

1 Answers1

1

tl;dr: This may be caused by using a custom optional implementation in the code outside the library.

My hunch was valid, but @1201ProgramAlarm's comments led me to the solution:

The definition of std::experimental::optional in the project using the static library wasn't taken from the standard C++ library. Instead, that was shadowed by Andrzej Krzemieński's optional implementation. Now, I have nothing bad to say about it - it's really nice; however, it does put that definition into std::experimental, which means that if you're not careful you can mistake it for C++14's std::experimental::optional.

In my case, the chain of #if __cplusplus > something define it one way, #else if __cplusplus > something else define it another way etc. was faulty - The custom optional implementation was used even when compiling with C++14.

The linker, on the other hand, doesn't confuse the different optional implementations; in fact, libstdc++'s std::experimental::optional is just an alias for std::experimental::fundamentals_v1::optional<int>; so the mangled name for a function returning an optional<int> is different. Thus even though in C++ you can't overload a function on the return type, i.e. no two functions can have the same signature except for a different return type - there's nothing preventing two mangled function names from being that way (IIANM), and that's indeed what happened in my case.

What I am not entirely sure about now is how I avoided the linking problem in the first place...

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    For function templates the return type is included in the mangled name. For non-template functions it isn't, so if you'd had a non-template function returning an `optional` then it probably would have linked and had silent undefined behaviour (for violating the one-definition rule). – Jonathan Wakely Feb 05 '20 at 13:33
  • @JonathanWakely: Is it possible to get visibility on that with a compiler flag? – einpoklum Feb 05 '20 at 13:47
  • I don't understand the question. Visibility on what? The undefined behaviour? – Jonathan Wakely Feb 05 '20 at 13:49
  • @JonathanWakely: I mean, visibility on the fact that two functions are to be linked which differ only on the return type and aren't templates. – einpoklum Feb 05 '20 at 13:51
  • 1
    It might be detectable using `-flto` and might be detectable using the `gold` linker and its option like `--detect-odr-violations` (or something like that). But in general, no. The reason it's undefined and not ill-formed is that it's not possible for the implementation to reliably detect it and give an error. If it was possible, it would be required to issue a diagnostic and not just result in silent undefined behaviour. – Jonathan Wakely Feb 05 '20 at 13:57