Skip to content

Linker Error with Clang on Windows linking against Boost built with MSVC #1212

Closed

Description

@jens-diewald

We are building Boost with MSVC and define BOOST_LIB_TOOLSET to compile some project with clang and link statically against Boost.
This does work fine with other Boost libraries.
We want to use Boost.JSON now, but unfortunately we get the following linker error in this situation:

lld-link : error : undefined symbol: private: static class boost::json::detail::default_resource boost::json::detail::default_resource::instance_

The cause of this is this block in boost/json/detail/config.hpp

#ifndef BOOST_JSON_NO_DESTROY
# if defined(__clang__) && defined(__has_cpp_attribute)
#  if __has_cpp_attribute(clang::no_destroy)
#   define BOOST_JSON_NO_DESTROY [[clang::no_destroy]]
#  endif
# endif
#endif

in combination with the definition of default_resource::instance_ in boost/json/detail/default_ressource.hpp:

#ifndef BOOST_JSON_WEAK_CONSTINIT
# ifndef BOOST_JSON_NO_DESTROY
    static holder instance_;
# else
    BOOST_JSON_NO_DESTROY
    static default_resource instance_;
# endif
#endif

When compiling with MSVC, BOOST_JSON_NO_DESTROY is not set, when compiling with clang, it is.
So different variants of the instance_ member are selected in the two cases.

If I remove the cited block from the config.hpp, everything compiles and links fine.

We could also work around this by building Boost with clang also, obviously.
But I do consider this a bug in Boost.JSON.
I am unsure, how to best prevent this error from within you codebase, however. Do you see a way?
At least an escape hatch macro like BOOST_JSON_AVOID_NO_DESTROY would be nice.

Activity

  1. grisumbras commented on Oct 21, 2026

    @grisumbras
    Member

    So, to be clear, you build Boost with MSVC, and it compiles Boost.Json a certain way. Then you compiled another project with clang, and it expects Boost.Json to have been built a certain different way.

    This doesn't sound to me like a bug in Boost.Json in any way. You're actually lucky you've got a linking error and not some kind of impossible to track down runtime error due to the ODR violation.

    I can suggest defining BOOST_JSON_NO_DESTROY when building with clang. That should disable the code that causes the linking error.

  2. jens-diewald commented on Oct 21, 2026

    @jens-diewald
    Author

    Thank you for your answer.

    So, to be clear, you build Boost with MSVC, and it compiles Boost.Json a certain way. Then you compiled another project with clang, and it expects Boost.Json to have been built a certain different way.

    Yes, that is correct. Although that is not as weird as you make it sound, this usually works very well. Clang on Windows aims to be compatible with MSVC, see https://clang.llvm.org/docs/MSVCCompatibility.html
    Clang also defines all sorts of MSVC-specific macros on windows. So the only obvious way to break this is by using Clang-specific macros, which is what happens here.

    I can suggest defining BOOST_JSON_NO_DESTROY when building with clang. That should disable the code that causes the linking error.

    No, that does not work. I need BOOST_JSON_NO_DESTROY to not be defined in default_ressource.hpp, for the ifdefs to select the correct member definition.

  3. grisumbras commented on Oct 21, 2026

    @grisumbras
    Member
    Yes, that is correct. Although that is not as weird as you make it sound, this usually works very well. Clang on Windows aims to be compatible with MSVC, see https://clang.llvm.org/docs/MSVCCompatibility.html
    

    I'm not saying it's weird, I'm saying it is fundamentally brittle and subject to arbitrary promises of compatibility between the two compilers.

    Clang also defines all sorts of MSVC-specific macros on windows. So the only obvious way to break this is by using Clang-specific macros, which is what happens here.
    

    When building with Clang we use features provided by Clang. It would have been a bug to try to use them if they were not provided, but the opposite is not a bug. Luckily, the use of this specific feature can be disabled by defining BOOST_JSON_NO_DESTROY.

    No, that does not work. I need `BOOST_JSON_NO_DESTROY` to not be defined in `default_ressource.hpp`, for the `ifdefs` to select the correct member definition.
    

    Can you elaborate on that? You want the macro not to be defined (manually), so that the static data member is declared. But then you will get a linking error, because that member is not defined anywhere. So, why do you want the member to be declared?

  4. jens-diewald commented on Oct 21, 2026

    @jens-diewald
    Author

    I'm not saying it's weird, I'm saying it is fundamentally brittle and subject to arbitrary promises of compatibility between the two compilers.

    Well, the promises are very much not arbitrary. One may even compare it to compiling with a newer version of one compiler, and linking it against Boost that is built with an older version of the same compiler that guarantees binary forward compatibility. Would you argue that one should not do that?
    (Even though, I agree that my situation is a bit more unique than this one.)

    When building with Clang we use features provided by Clang. It would have been a bug to try to use them if they were not provided, but the opposite is not a bug

    Depending on a macro to be set to an identical value when compiling the library and when consuming the library can very well be considered a bug. This is in general a dangerous source of errors. In this particular case with __clang__ etc., this is more debatable.
    So if you do not consider it a bug, that is your choice, fair enough.

    Can you elaborate on that? You want the macro not to be defined (manually), so that the static data member is declared. But then you will get a linking error, because that member is not defined anywhere. So, why do you want the member to be declared?

    Without BOOST_JSON_NO_DESTROY the data member is static holder instance_.
    With it, it is static default_resource instance_.
    With MSVC, the macro is not defined. With clang, it is set to [[clang::no_destroy]].
    If i define the macro (empty) with clang, it keeps its empty value in config.hpp, but is still defined.
    So i still get static default_resource instance_, which the linker does not find.

  5. grisumbras commented on Oct 22, 2026

    @grisumbras
    Member
    So i still get `static default_resource instance_`, which the linker does not find.
    

    Right! Sorry, I misread the code. I'll add a macro to disable the feature.

  6. grisumbras commented on Oct 23, 2026

    @grisumbras
    Member

    I was persuaded that your specific situation—compatibility of binaries built with MSVC and clang-cl— should work. So, I disabled usage of no_destroy attribute in clang-cl. Can you test #1213, so that I know if it does eliminate the problem?

  7. jens-diewald commented on Oct 26, 2026

    @jens-diewald
    Author

    Can you test #1213, so that I know if it does eliminate the problem?

    I did, works as expected :) Both, with only the first commit and setting the macro and with the second commit without an additional macro.
    Thanks a lot and sorry for the late reply, I did not get to this during the weekend.

  8. grisumbras commented on Oct 26, 2026

    @grisumbras
    Member

    Thanks for checking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions