Linker complains about undefined references while compiling with Clang

378 views Asked by At

I've encountered a really weird problem with clang.

When I compile my code with C++20 using gcc-11 everything is fine.

The problem appears when I try to compile it with C++20 and clang-14 (using clang-15/16/17 does not help).

Linker complains about undefined references to all class template instantiations for containers, below is e.g. one of the error messages:

/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()'

So far I've managed to work around it by changing Widget's doSth signature from

void doSth() override

to:

__attribute__((used)) void doSth() override

but this is - as I said above - rather a workaround, not the real solution. Does anyone have an idea why Clang can't handle this?

Below are links to Compiler Explorer and C++ insights and a minimum reproducible example, extracted from my code base and reduced to an absolute minimum that will allow to reproduce the example and show my intentions.

Compiler explorer: https://godbolt.org/z/83dMMvozn

C++ insights: https://cppinsights.io/s/e35151b2

Update

I removed some parts of the code that were not needed, and added another (acceptable) workaround - instead of using lambda as an argument for "invoke" call I used callable struct - it "magically" worked - however, I do not understand why it wasn't working with 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();
}
0

There are 0 answers