显式实例化模板类的显式实例化模板方法

Posted

技术标签:

【中文标题】显式实例化模板类的显式实例化模板方法【英文标题】:Explicitly instantiate template method of explicitly instantiated template class 【发布时间】:2020-11-19 11:03:32 【问题描述】:

我有一个带有模板参数 T 的类 A,它仅限于两种类型:T1 和 T2。因此,我为 T1 和 T2 类型显式实例化了 A 类,这样 A 的功能可以在源文件中定义,而无需在每次包含 A.hpp 时都重新编译。

A.hpp:

template<typename T>
class A 
public:
    void basicMethod();
;

template class A<T1>;
template class A<T2>;

A.cpp:

template<typename T>
void A<T>::basicMethod() 
    // ...

但是,现在我想向 A 和 A 添加一个模板化方法,其中模板参数再次被限制为两种类型:S1 和 S2:

A.hpp:

template<typename T>
class A 
public:
    void basicMethod();
    template<typename S>
    void advancedMethod();
;

template class A<T1>;
template class A<T2>;

// How to explicitly instantiate A::advancedMethod here?

A.cpp:

template<typename T>
void A<T>::basicMethod() 
    // ...


template<typename T>
template<typename S>
void A<T>::advancedMethod() 
    // ...

如何为 (T, S) = T1, T2 x S1, S2 显式实例化 A::advancedMethod?根据我在网上找到的内容,我尝试在 A.hpp 的末尾添加这样的内容:

template void A<T1>::advancedMethod(S1);
template void A<T1>::advancedMethod(S2);
template void A<T2>::advancedMethod(S1);
template void A<T2>::advancedMethod(S2);

但是,这根本不起作用。

【问题讨论】:

我猜你.cpp 中的第二个模板方法应该是advancedMethod @super 是的,抱歉,我刚刚解决了这个问题。 【参考方案1】:

类模板的成员函数模板的显式实例化定义

你几乎明白了。正确的语法是:

template void A<T1>::advancedMethod<S1>();
template void A<T1>::advancedMethod<S2>();
template void A<T2>::advancedMethod<S1>();
template void A<T2>::advancedMethod<S2>();

特别注意advancedMethod() 是一个函数模板,它通过单一类型模板 参数参数化,没有函数 参数:您为函数的不同特化的显式实例化定义模板(对于定义它的类模板的不同特化)应该像类模板的显式实例化定义一样,提供模板参数 (&lt;...&gt;) 以指定您希望显式实例化的特化。

最后,advancedMethod() 函数模板自然需要为每个显式实例化的特化定义(通过主模板定义或显式特化);特别是(cppreference):

如果函数模板、变量模板、成员函数模板或类模板的成员函数或静态数据成员使用显式实例化定义显式实例化,则模板定义必须是出现在同一个翻译单元中。


将模板函数(/class 模板成员函数)的定义与其声明分开:-timl.hpp 模式

作为上述要求的结果,在提供显式实例化定义时,您通常将这些放在相关模板化实体的定义之后在源文件中,其中典型的用例是模板化实体的公共 API 的用户不应有权访问定义(如您的示例中所示)。这是必不可少的,因为根据[temp.spec]/5,您可能不会在不同的翻译单元上明确地两次实例化相同的专业化:

对于给定的一组模板参数,

显式实例化定义在程序中最多出现一次, [...]

实施不需要诊断违反此规则的情况

因此,如果您将显式实例化放在标头中,然后将标头包含在多个源文件中,您的程序将是非良构的,NDR。

// a.hpp
template<typename T>
class A 
public:
    void basicMethod();
    template<typename S>
    void advancedMethod();
;

// a-timpl.hpp
#include "a.hpp"

template<typename T>
void A<T>::basicMethod() 
    // ...


template<typename T>
template<typename S>
void A<T>::advancedMethod() 
    // ...


// a.cpp
#include "a-timpl.hpp"
#include "t1.hpp"
#include "t2.hpp"
#include "s1.hpp"
#include "s2.hpp"

// Explicit instantiation definitions for
// production intent.
template class A<T1>;
template class A<T2>;

template void A<T1>::advancedMethod<S1>();
template void A<T1>::advancedMethod<S2>();
template void A<T2>::advancedMethod<S1>();
template void A<T2>::advancedMethod<S2>();

对于测试,您同样包含 -timpl.hpp 头文件而不是主头文件(主头文件用于公共 API 公开),这样您就可以使用模板定义来显式实例化测试中使用的特化:

// a_test.cpp
#include "a-timpl.hpp"
#include "t1_mock.h"
#include "t2_mock.h"
#include "s1_mock.h"
#include "s2_mock.h"

// Explicit instantiation definitions for
// test intent.
template class A<T1Mock>;
template class A<T2Mock>;

template void A<T1Mock>::advancedMethod<S1Mock>();
template void A<T1Mock>::advancedMethod<S2Mock>();
template void A<T2Mock>::advancedMethod<S1Mock>();
template void A<T2Mock>::advancedMethod<S2Mock>();

【讨论】:

感谢您的回答。你的语法更有意义,我只是看到其他地方的人将模板参数放在括号之间,并没有真正理解为什么。我之前尝试过您的语法,但它不起作用,但事实证明这是因为我将显式实例化放在 A.hpp 中的类定义/方法声明之后,而不是在方法定义之后。现在似乎可以编译和链接,虽然我还没有真正测试过调用模板方法实例。 至于你回答的第二部分:这个我不太明白,具体在什么情况下会出现问题?现在我将类实例化放在类定义之后(所以在头文件中),将方法实例化放在它们的定义之后(所以在源文件中)。当我将类实例化移动到该类的一个源文件的末尾时,我得到了链接器错误。 @Wout12345 根据[temp.spec]/5,对于给定的模板参数集,“显式实例化定义应在程序中最多出现一次 [...] 不需要实现诊断违反此规则的行为。”。如果您将显式实例化放在头文件中,然后将头文件包含在多个源文件中,则您的程序将是格式错误的 NDR(不需要诊断)。 ... @Wout12345 ... 如果您收到链接器错误,那么您做错了什么:您是否在源文件中包含了a.hpp 文件而不是a-timpl.hpp 文件? 有问题的标头是包含保护的,所以它在我的编译过程中只能出现一次,并且标头是内部的(不是公共接口的一部分)所以它不会是任何其他的一部分编译过程。这是否意味着它永远不会被复制?

以上是关于显式实例化模板类的显式实例化模板方法的主要内容,如果未能解决你的问题,请参考以下文章

用于实例化模板代码的显式习惯用法 - 不包括其源代码

正确使用函数的显式模板实例化?

MSVC:显式模板实例化失败,而隐式实例化成功

使用模板化成员函数显式实例化模板类

将 pimpl 与 Templated Class 和显式实例化的模板一起使用

如何对复杂的模板化函数进行显式实例化?和相关的 Intellisense 错误