使用 Clang 编译时链接器抱怨未定义的引用

bie*_*000 9 c++ clang template-meta-programming c++20 c++-templates

我遇到了一个非常奇怪的 clang 问题。

当我使用 gcc-11 使用 C++20 编译代码时,一切都很好。

当我尝试使用 C++20 和 clang-14 编译它时出现问题(使用 clang-15/16/17 没有帮助)。

链接器抱怨对容器的所有类模板实例化的未定义引用,下面是错误消息之一:

/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../x86_64-linux-gnu/bin/ld: /tmp/example-ded8d6.o:(.rodata._ZTV6WidgetINSt7__cxx114listIlSaIlEEEE[_ZTV6WidgetINSt7__cxx114listIlSaIlEEEE]+0x10): undefined reference to `Widget<std::__cxx11::list<long, std::allocator<long> > >::doSth()'
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../x86_64-linux-gnu/bin/ld: /tmp/example-ded8d6.o:(.rodata._ZTV6WidgetISt6vectorIbSaIbEEE[_ZTV6WidgetISt6vectorIbSaIbEEE]+0x10): undefined reference to `Widget<std::vector<bool, std::allocator<bool> > >::doSth()'
Run Code Online (Sandbox Code Playgroud)

到目前为止,我已经设法通过更改 Widget 的 doSth 签名来解决这个问题

void doSth() override
Run Code Online (Sandbox Code Playgroud)

到:

__attribute__((used)) void doSth() override
Run Code Online (Sandbox Code Playgroud)

但这是——正如我上面所说的——而是一种解决方法,而不是真正的解决方案。有谁知道为什么 Clang 无法处理这个问题?

下面是编译器资源管理器和 C++ 见解的链接以及最小的可重现示例,这些示例是从我的代码库中提取的,并减少到绝对最小值,以便重现该示例并显示我的意图。

编译器浏览器:https://godbolt.org/z/83dMMvozn

C++ 见解:https://cppinsights.io/s/e35151b2

更新

我删除了不需要的代码部分,并添加了另一个(可接受的)解决方法 - 我没有使用 lambda 作为“调用”调用的参数,而是使用了可调用结构 - 它“神奇地”起作用了 - 但是,我不明白为什么它不适用于 lambda。

#include <memory>
#include <type_traits>
#include <variant>
#include <vector>
#include <list>

struct EnumPlaceholder
{
};

template <typename T>
struct ChannelTypeIdentity
{
    using Type = T;
};

template <template <typename, typename> typename T>
struct ContainerTypeIdentity;

template <>
struct ContainerTypeIdentity<std::vector>
{
    template <typename U>
    using Type = std::vector<U>;
};

template <>
struct ContainerTypeIdentity<std::list>
{
    template <typename U>
    using Type = std::list<U>;
};

enum class ValueType
{
    eUndefined,
    eBool,
    eUInt8T,
    eUInt16T,
    eUInt32T,
    eUInt64T,
    eInt8T,
    eInt16T,
    eInt32T,
    eInt64T,
    eFloat,
    eDouble,
    eEnum,
    eString
};

enum class ContainerType
{
    eNone,
    eVector,
    eList
};

using ChannelTypeMapperReturnType = std::variant<
    ChannelTypeIdentity<EnumPlaceholder>,
    ChannelTypeIdentity<uint8_t>,
    ChannelTypeIdentity<uint16_t>,
    ChannelTypeIdentity<uint32_t>,
    ChannelTypeIdentity<uint64_t>,
    ChannelTypeIdentity<int8_t>,
    ChannelTypeIdentity<int16_t>,
    ChannelTypeIdentity<int32_t>,
    ChannelTypeIdentity<bool>,
    ChannelTypeIdentity<float>,
    ChannelTypeIdentity<double>,
    ChannelTypeIdentity<std::string>
>;

using ContainerTypeMapperReturnType = std::variant<
    ContainerTypeIdentity<std::vector>,
    ContainerTypeIdentity<std::list>
>;

ChannelTypeMapperReturnType simulateChannelTypeMapperReverseMap()
{
    return ChannelTypeMapperReturnType(ChannelTypeIdentity<int32_t>());
}

ContainerTypeMapperReturnType simulateContainerTypeMapperReverseMap()
{
    return ContainerTypeMapperReturnType(ContainerTypeIdentity<std::vector>{});
}

template <typename Func, typename ChannelTypeId>
constexpr auto invoke(Func&& func, ChannelTypeId channelTypeId)
{
    const auto valueType = simulateChannelTypeMapperReverseMap(); // simulated value, normally lookup in mapper

    if (channelTypeId.containerType != ContainerType::eNone)
    {
        return std::visit(
            [&channelTypeId, func = std::forward<Func>(func)](const auto& valueIdentity) mutable {
                using ValueType = typename std::decay_t<decltype(valueIdentity)>::Type;
                return std::visit(
                    [func = std::forward<Func>(func)](const auto& containerIdentity) {
                        using Type = typename std::decay_t<decltype(containerIdentity)>::template Type<ValueType>;

                        return func(ChannelTypeIdentity<Type>{});
                    },
                    simulateContainerTypeMapperReverseMap()); // simulated value, normally lookup in mapper
            },
            valueType);
    }

    return std::visit(std::forward<Func>(func), valueType);
}

struct ChannelTypeId
{
    ValueType valueType;
    ContainerType containerType;
    friend auto operator<=>(const ChannelTypeId&, const ChannelTypeId&) = default;
};

class IWidget
{
public:
    IWidget() = default;

    virtual void doSth() = 0;

    virtual ~IWidget() = default;
};

template <typename T>
class Widget : public IWidget
{
public:
    void doSth() override
    {

    }
};

template <typename T>
class Factory
{
public:
    static std::unique_ptr<IWidget> create()
    {
        return std::make_unique<Widget<T>>();
    }
};

struct Callable
{
    template <typename T>
    auto operator()(const T& identity) const
    {
        using ValueType = typename T::Type;
        return Factory<ValueType>::create();
    }
};

std::unique_ptr<IWidget> doSth(ChannelTypeId id)
{
    // This does not work - linker complains
    return invoke(
        []([[maybe_unused]] const auto& identity) -> std::unique_ptr<IWidget> {
            using ValueType = typename std::decay_t<decltype(identity)>::Type;
            return Factory<ValueType>::create();
        
    }, id);

    // This works
    // return invoke(Callable(), id);
}

int main() 
{ 
    auto ptr = doSth(ChannelTypeId{ 
        .valueType = ValueType::eUInt32T, 
        .containerType = ContainerType::eVector
    });

    ptr->doSth();
}
Run Code Online (Sandbox Code Playgroud)