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)