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();
}
dump()
and#include <source_location>
can be removed. Further code reducing for getting a minimal reproducible example is desired. – Austenenum class ValueType { eUInt32T, eInt32T };
and minus 20+ lines of the code. – Austen