C++ 中一组已知类型的成员函数模板的自动实例化

问题描述 投票:0回答:1

我试图显式实例化一个成员函数模板,以使其定义远离标头。需要实例化的类型是

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()
,我会收到链接器错误。


我的问题:

  1. 第一种方法真的可以移植吗?合格的编译器是否允许不实例化?
  2. 为什么第二种方法失败了?我认为采用函数指针应该强制实例化函数模板。
  3. 有更优雅/便携的方式来实现我的目标吗?
c++ c++17 template-meta-programming
1个回答
0
投票

您必须显式实例化成员函数模板。

函数模板、类模板的成员函数、变量模板或类模板的静态数据成员的定义应可从隐式定义域 ([basic.def.odr]) 的末尾访问实例化 ([temp.inst]) 除非相应的专业化在某个翻译单元中显式实例化 ([temp.explicit]) ;无需诊断。

- [温度.pre] p10

您试图通过隐式生成显式实例化来“欺骗系统”,但这是不可能的。

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
。 其余保持不变。

© www.soinside.com 2019 - 2024. All rights reserved.