我试图显式实例化一个成员函数模板,以使其定义远离标头。需要实例化的类型是
std::variant
实例化的替代类型。
我想避免在源文件中输入所有显式实例化。
最小展示代码:
标题:
#include <variant>
using VarType = std::variant<bool, int, float>;
struct VarHolder
{
VarType var;
template<typename T>
const T* get() const;
};
来源:
// A trivial function, for demonstration only
template<typename T>
const T* VarHolder::get() const
{
if (std::holds_alternative<T>(var))
return &std::get<T>(var);
return nullptr;
}
//manual instantiations, which i'd like to avoid
//template const int* VarHolder::get<int>() const;
//...
到目前为止我已经想出了两种可能的解决方案:
template<class T>
struct F;
template<class ...Ts>
struct F<std::variant<Ts...>> {
static void f() {
VarHolder t{};
((t.get<Ts>()), ...);
}
};
template struct F<VarType>; // actual instantiation
这适用于 MSVC 2022 (C++17)。我能够包含标头并调用
VarHolder::get()
的所有实例化,而不会出现链接器错误。但我怀疑这是最佳方式。
由于我不熟悉成员函数指针,因此我决定创建一个包装函数模板,它将调用某些类型的
get()
方法。
template<typename T>
void fn(const VarHolder& t)
{
t.get<T>();
}
using fn_t = decltype(&fn<int>); // should be void (*)(const VarHolder&)
template<typename T>
struct S;
template<typename ...Ts>
struct S<std::variant<Ts...>> {
constexpr static fn_t arr[]{ (&fn<Ts>)... }; // store pointers to all instantiations of fn()
};
template struct S<VarType>; // actual instantiation
这在 MSVC 2022 中不起作用。如果在其他地方调用
VarHolder::get()
,我会收到链接器错误。
我的问题:
您必须显式实例化成员函数模板。
函数模板、类模板的成员函数、变量模板或类模板的静态数据成员的定义应可从隐式定义域 ([basic.def.odr]) 的末尾访问实例化 ([temp.inst]) 除非相应的专业化在某个翻译单元中显式实例化 ([temp.explicit]) ;无需诊断。
您试图通过隐式生成显式实例化来“欺骗系统”,但这是不可能的。
template F<VarType>;
(应该是template class F<VarType>
)尝试显式实例化F
,然后为您生成所有这些显式实例化。
然而,这不是便携式的。如果它有效,那也是巧合。
让我们看一个例子:
template <typename T>
void foo() {}
template <typename T>
struct S {
S() { foo<T>(); }
};
template struct S<int>;
S
的显式实例化还包括 foo<int>()
的隐式实例化。
从 GCC 或 Clang 生成的程序集仅包括:(https://godbolt.org/z/orffnM7eT)
S<int>::S() [base object constructor]:
ret
由于某种原因,MSVC 确实会发出
foo<int>
,但我不会依赖它。
如果仅发出 S<int>
并且 foo<int>
被内联并且在程序集中找不到,则从另一个对象文件调用 foo<int>
时会出现链接器错误,因为它不存在。
据推测,这就是为什么你的第二种方法会给你一个链接器错误。
// header: define the type list and the variant type
#define VAR_HOLDER_VARIANT_TYPE_LIST(F, S) \
F(bool) S() F(int) S() F(float)
#define IDENTITY(...) __VA_ARGS__
#define COMMA() ,
using VarType = std::variant<VAR_HOLDER_VARIANT_TYPE_LIST(IDENTITY, COMMA)>;
// source: define all explicit instantiations
#define EXPLICITLY_INSTANTIATE_VAR_HOLDER_GET(...) \
template const __VA_ARGS__* VarHolder::get<__VA_ARGS__>() const
#define SEMICOLON() ;
VAR_HOLDER_VARIANT_TYPE_LIST(EXPLICITLY_INSTANTIATE_VAR_HOLDER_GET, SEMICOLON);
宏很难看,但至少你不会重复自己。 当更改变体的类型时,您只需更新
VAR_HOLDER_VARIANT_TYPE_LIST
。
其余保持不变。