Search code examples
visual-c++cmakec++20bazellnk2019

C++20 LNK2019 error with MSVC, while C++17 works


I'm facing a problem with C++20 on a project build with CMake and Bazel, where both work on c++17/c++20 on linux (GCC and Clang), but they fail only on windows msvc with c++20. The error is a link error (LNK2019), which is quite strange because it was CMake (or Bazel) themselves that built the objects, yet they fail to link. I put some reproducible code here on github, although quite big the project: https://github.com/manydeps/manydeps-cln I have tried to change the MSVC version (github actions offers 4 different ones for windows-2022 / windows-latest image): 14.16.27023, 14.29.30133, 14.35.32215 and 14.37.32822. Another strange thing is that CMake decides to use 14.35.32215, while Bazel uses 14.37.32822 (and I couldn't change that, even by deleting the folder). I'm building this as a static /MT library on windows (and .a on linux).

This is the strange link error for CLN project:

[5 / 6] Linking app_demo_cln.exe; 1s local
ERROR: D:/a/manydeps-cln/manydeps-cln/BUILD.bazel:21:10: Linking app_demo_cln.exe failed: (Exit 1120): link.exe failed: error executing command (from target //:app_demo_cln) C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.35.32215\bin\HostX64\x64\link.exe @bazel-out/x64_windows-fastbuild/bin/app_demo_cln.exe-2.params
cln.lib(cl_prin_globals.obj) : error LNK2019: unresolved external symbol "struct cln::cl_heap_string * __cdecl cl_make_heap_string(char const *)" (?cl_make_heap_string@@YAPEAUcl_heap_string@cln@@PEBD@Z) referenced in function "public: __cdecl cln::cl_string::cl_string(char const *)" (??0cl_string@cln@@QEAA@PEBD@Z)
  Hint on symbols that are defined and could potentially match:
    "struct cln::cl_heap_string * __cdecl cln::cl_make_heap_string(char const *)" (?cl_make_heap_string@cln@@YAPEAUcl_heap_string@1@PEBD@Z)
bazel-out\x64_windows-fastbuild\bin\app_demo_cln.exe : fatal error LNK1120: 1 unresolved externals
Target //:app_demo_cln failed to build

The function cl_make_heap_string exists on CLN library, but the name mangling seems strange... something like cl_make_heap_string@cln@@YAPEAUcl_heap_string@1@PEBD@Z and cl_make_heap_string@@YAPEAUcl_heap_string@cln@@PEBD@Z, so that they won't match.

I tried to change the compilers, change flags in the compiler and look for answers in the Internet for many days already, with no solution.


Solution

  • Thanks @Tsyvarev for the precise comment on the nature of the linking error, that demonstrated a problem in global vs specific namespace for naming. I indeed checked the code, and this is a snippet:

    Definition (some random .cc file):

    namespace cln { 
    cl_heap_string* cl_make_heap_string (const char * s) { ... } } 
    }
    

    Declaration (some random .h file):

    namespace cln { 
    struct heap_string { 
     // much more stuff here ...
     friend cl_heap_string* cl_make_heap_string(const char* s); 
    }; 
    }
    

    So it's interesting that up to C++17 standard, the symbols match between both, but with C++20 strict (/permissive-) behavior they no longer match (so /permissive is needed, but I wanted a real fix). I took a look and perhaps the bug defect on C++ that could be responsible is this "1477. Definition of a friend outside its namespace": https://cplusplus.github.io/CWG/issues/1477.html

    Original text:

    Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function95 the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (6.5.3 [basic.lookup.unqual]) or by qualified lookup (6.5.5 [basic.lookup.qual]) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship).

    Fixed text in 2012:

    Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class or function95 the friend class or function is a member of the innermost enclosing namespace. The name of the friend is not found by The friend declaration does not by itself make the name visible to unqualified lookup (6.5.3 [basic.lookup.unqual]) or by qualified lookup (6.5.5 [basic.lookup.qual]). [Note: The name of the friend will be visible in its namespace if until a matching declaration is provided in that at namespace scope (either before or after the class definition granting friendship). —end note] If a friend function is called...

    So, I changed the declaration and it really worked on C++20 strict mode!

    Fixed declaration:

    namespace cln { 
    struct heap_string { 
     // much more stuff here ...
     friend cl_heap_string* cl_make_heap_string(const char* s); 
    }; 
    // ADDED THIS PART HERE, TO ENFORCE THAT METHOD REALLY BELONGS TO cln:: NAMESPACE
    cl_heap_string* cl_make_heap_string(const char* s); 
    }
    

    So, thanks a lot for the help!