Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C++20][modules] Combining modules, concepts (and a JSON library) leads to a linkage error #121797

Open
AlbertSSj opened this issue Jan 6, 2025 · 4 comments
Assignees
Labels
clang:modules C++20 modules and Clang Header Modules

Comments

@AlbertSSj
Copy link

I have a small module (A) that defines a template function on objects that can be casted to an nlohmann::json object, and a concept to enable the operation. I have a separate module (B) to use the operation.
When used in a module, this leads to a linkage error. The linker seems to believe that the symbols for some of the JSON operation have to be provided by module A.

What is interesting is that by forcing module A to generate the symbols, by creating an exported function that does a bunch of JSON conversions, the linkage can complete successfully. So for now I'm adding one by one the JSON conversions that are required by B in A.

I don't even need to use my function to cause the issue, just defining a JSON object is enough.

Below is a set of files that can reproduce the problem:

module_a.cppm

module;

#include <nlohmann/json.hpp>

export module module_a;

namespace module_a {

template <typename T>
concept FromOrderedJson = requires(T t) {
    { static_cast<nlohmann::ordered_json>(t) };
};

template <typename T>
concept FromJson = requires(T t) {
    { static_cast<nlohmann::json>(t) };
};

export template <typename T>
    requires(FromOrderedJson<T>)
std::string MakeString(T value)
{
    return nlohmann::ordered_json(value);
}

export template <typename T>
    requires(FromJson<T>)
std::string MakeString(T value)
{
    return nlohmann::json(value);
}

} // namespace module_a

module_b.cpp

module;

#include <iostream>
#include <nlohmann/json.hpp>

export module module_b;
import module_a;

namespace {
struct MyJsonData
{
    std::string a;
    int b;
    bool c;
};

void to_json(nlohmann::json& j, const MyJsonData& p)
{
    j["a"] = p.a;
    j["b"] = p.b;
    j["c"] = nlohmann::json(p.c);
}
} // namespace

int main(int argc, char* argv[])
{
    MyJsonData value;
    auto x = nlohmann::json(value);
}

CMakeLists.txt

add_library(module_a SHARED)
target_sources(
    module_a
    PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
        module_a.cppm
)
target_compile_options(
    module_a
    #PUBLIC
    #-fexperimental-modules-reduced-bmi
)
target_link_libraries(
    module_a
)
add_executable(module_b)
target_sources(
    module_b
    PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
        module_b.cpp
)
target_link_libraries(
    module_b
    PUBLIC
    module_a
)

The error is this:

/usr/bin/ld: module_b.cpp.o: in function `_ZN8nlohmann10basic_jsonISt3mapSt6vectorNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEblmdSaNS_14adl_serializerES2_IhSaIhEEEC2IRKbbTnNSt9enable_ifIXaantsr6detail13is_basic_jsonIT0_EE5valuesr6detail18is_compatible_typeISC_SH_EE5valueEiE4typeELi0EEEOT_':
module_b.cpp:17931:(.text._ZN8nlohmann10basic_jsonISt3mapSt6vectorNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEblmdSaNS_14adl_serializerES2_IhSaIhEEEC2IRKbbTnNSt9enable_ifIXaantsr6detail13is_basic_jsonIT0_EE5valuesr6detail18is_compatible_typeISC_SH_EE5valueEiE4typeELi0EEEOT_[_ZN8nlohmann10basic_jsonISt3mapSt6vectorNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEblmdSaNS_14adl_serializerES2_IhSaIhEEEC2IRKbbTnNSt9enable_ifIXaantsr6detail13is_basic_jsonIT0_EE5valuesr6detail18is_compatible_typeISC_SH_EE5valueEiE4typeELi0EEEOT_]+0x34): undefined reference to `decltype ((nlohmann::(anonymous namespace)::to_json({parm#1}, (std::forward<bool const&>)({parm#2}))),((void)())) nlohmann::adl_serializer<bool, void>::to_json<nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> > >, bool const&>(nlohmann::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> > >&, bool const&)'

Which basically means that the symbol for the JSON conversion from bool is missing (I think).

In case it helps, I'm using clang version 19.1.6 and cmake version 3.28.3.

@EugeneZelenko EugeneZelenko added clang:modules C++20 modules and Clang Header Modules and removed new issue labels Jan 6, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 6, 2025

@llvm/issue-subscribers-clang-modules

Author: None (AlbertSSj)

I have a small module (A) that defines a template function on objects that can be casted to an nlohmann::json object, and a concept to enable the operation. I have a separate module (B) to use the operation. When used in a module, this leads to a linkage error. The linker seems to believe that the symbols for some of the JSON operation have to be provided by module A.

What is interesting is that by forcing module A to generate the symbols, by creating an exported function that does a bunch of JSON conversions, the linkage can complete successfully. So for now I'm adding one by one the JSON conversions that are required by B in A.

I don't even need to use my function to cause the issue, just defining a JSON object is enough.

Below is a set of files that can reproduce the problem:

module_a.cppm

module;

#include &lt;nlohmann/json.hpp&gt;

export module module_a;

namespace module_a {

template &lt;typename T&gt;
concept FromOrderedJson = requires(T t) {
    { static_cast&lt;nlohmann::ordered_json&gt;(t) };
};

template &lt;typename T&gt;
concept FromJson = requires(T t) {
    { static_cast&lt;nlohmann::json&gt;(t) };
};

export template &lt;typename T&gt;
    requires(FromOrderedJson&lt;T&gt;)
std::string MakeString(T value)
{
    return nlohmann::ordered_json(value);
}

export template &lt;typename T&gt;
    requires(FromJson&lt;T&gt;)
std::string MakeString(T value)
{
    return nlohmann::json(value);
}

} // namespace module_a

module_b.cpp

module;

#include &lt;iostream&gt;
#include &lt;nlohmann/json.hpp&gt;

export module module_b;
import module_a;

namespace {
struct MyJsonData
{
    std::string a;
    int b;
    bool c;
};

void to_json(nlohmann::json&amp; j, const MyJsonData&amp; p)
{
    j["a"] = p.a;
    j["b"] = p.b;
    j["c"] = nlohmann::json(p.c);
}
} // namespace

int main(int argc, char* argv[])
{
    MyJsonData value;
    auto x = nlohmann::json(value);
}

CMakeLists.txt

add_library(module_a SHARED)
target_sources(
    module_a
    PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
        module_a.cppm
)
target_compile_options(
    module_a
    #PUBLIC
    #-fexperimental-modules-reduced-bmi
)
target_link_libraries(
    module_a
)
add_executable(module_b)
target_sources(
    module_b
    PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
        module_b.cpp
)
target_link_libraries(
    module_b
    PUBLIC
    module_a
)

The error is this:

/usr/bin/ld: module_b.cpp.o: in function `_ZN8nlohmann10basic_jsonISt3mapSt6vectorNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEblmdSaNS_14adl_serializerES2_IhSaIhEEEC2IRKbbTnNSt9enable_ifIXaantsr6detail13is_basic_jsonIT0_EE5valuesr6detail18is_compatible_typeISC_SH_EE5valueEiE4typeELi0EEEOT_':
module_b.cpp:17931:(.text._ZN8nlohmann10basic_jsonISt3mapSt6vectorNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEblmdSaNS_14adl_serializerES2_IhSaIhEEEC2IRKbbTnNSt9enable_ifIXaantsr6detail13is_basic_jsonIT0_EE5valuesr6detail18is_compatible_typeISC_SH_EE5valueEiE4typeELi0EEEOT_[_ZN8nlohmann10basic_jsonISt3mapSt6vectorNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEblmdSaNS_14adl_serializerES2_IhSaIhEEEC2IRKbbTnNSt9enable_ifIXaantsr6detail13is_basic_jsonIT0_EE5valuesr6detail18is_compatible_typeISC_SH_EE5valueEiE4typeELi0EEEOT_]+0x34): undefined reference to `decltype ((nlohmann::(anonymous namespace)::to_json({parm#<!-- -->1}, (std::forward&lt;bool const&amp;&gt;)({parm#<!-- -->2}))),((void)())) nlohmann::adl_serializer&lt;bool, void&gt;::to_json&lt;nlohmann::basic_json&lt;std::map, std::vector, std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, std::vector&lt;unsigned char, std::allocator&lt;unsigned char&gt; &gt; &gt;, bool const&amp;&gt;(nlohmann::basic_json&lt;std::map, std::vector, std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;, bool, long, unsigned long, double, std::allocator, nlohmann::adl_serializer, std::vector&lt;unsigned char, std::allocator&lt;unsigned char&gt; &gt; &gt;&amp;, bool const&amp;)'

Which basically means that the symbol for the JSON conversion from bool is missing (I think).

In case it helps, I'm using clang version 19.1.6 and cmake version 3.28.3.

@ChuanqiXu9 ChuanqiXu9 self-assigned this Jan 7, 2025
@ChuanqiXu9
Copy link
Member

It will be helpful for me if we can have a reproducer without any dependencies (it will be better if the stl is not needed.)

@AlbertSSj
Copy link
Author

@ChuanqiXu9 I can remove stl, however right now I would not know how to recreate the issue without using nlohmann::json, can that stay?

@ChuanqiXu9
Copy link
Member

@ChuanqiXu9 I can remove stl, however right now I would not know how to recreate the issue without using nlohmann::json, can that stay?

The nlohmann::json part is slightly harder for me. If you like, you can try to preprocess the reproducer and reduce the preprocessed one further. This is more or less time consuming. Generally it may take me 1~2 hours to do this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:modules C++20 modules and Clang Header Modules
Projects
None yet
Development

No branches or pull requests

4 participants