【问题标题】:How template explicit instantiation works and when?模板显式实例化如何工作以及何时工作?
【发布时间】:2020-11-12 21:39:12
【问题描述】:

这是 C++ 入门第 5 版中的一个练习:

“练习16.26:假设NoDefault是一个没有默认构造函数的类,我们可以显式实例化vector<NoDefault>吗?如果不能,为什么不呢?”

这是我的猜测:

是的,我们可以实例化它:

template <typename T>
class Foo
{
public:
    void func(){cout << x_.value_ << endl;}
private:
    T x_;
};

class Bar
{
public:
    Bar(int x) : value_(x){}
    void print(){}
private:
    int value_{};
template <class T>
friend class Foo;
};

extern template class Foo<Bar>; // instantiation declaration
template class  Foo<Bar>; // instantiation definition


int main()
{

  //  Foo<Bar> f;
}

代码工作正常,但如果我取消注释 main 中的行,我会得到预期的错误,因为Bar 不是默认可构造的。

如果我使用相同的类 Bar 作为 std::vector 的元素类型,它将不起作用:

extern template class vector<Bar>; // declaration ok
template class vector<Bar>; // instantiation: doesn't work?!
  • 那么为什么我的 Foo&lt;Bar&gt; 实例化有效但 vector&lt;Bar&gt; 无效?

  • 在我看来,vector&lt;Bar&gt; 不工作是合乎逻辑的,因为显式实例化定义实例化了类模板的所有成员(甚至是未使用的成员);在这个例子中,Foo&lt;Bar&gt; 的默认构造函数暗示了其元素类型Bar 的默认ctor;后者不提供;毕竟Foo&lt;Bar&gt;() 通常被声明为已删除的成员函数,因为x_ 没有默认构造函数。所以我不知道为什么Foo&lt;Bar&gt; 定义有效?!谢谢。

【问题讨论】:

    标签: c++ template-instantiation


    【解决方案1】:

    在标准中,[temp.explicit] 部分解释了显式实例化中发生的情况。特别是,p12 规定:

    命名类模板特化的显式实例化定义显式实例化类模板特化,并且是仅在实例化点已定义的那些成员的显式实例化定义。

    现在,std::vector&lt;T&gt; 有一个构造函数,它接受一个整数 n 并使用 n 初始化值 T 的值初始化向量。可以假设此构造函数的定义位于 &lt;vector&gt; 标头内的某个位置(请参阅 Why can templates only be implemented in the header file?)。因此std::vector&lt;Bar&gt; 的显式实例化定义将使用T = Bar 实例化该构造函数。

    因为这是一个显式 实例化,所以实例化的不仅是构造函数的签名,而且它的整个主体也是如此。这必须在某处包括对Bar 的默认构造函数的调用(可能作为它调用的另一个函数的一部分,此时也将实例化),因此编译错误作为显式实例化定义的一部分发生std::vector&lt;Bar&gt;。请注意,如果您隐式实例化std::vector&lt;Bar&gt;,它只会实例化(粗略地说)成员函数的签名。这就是为什么实际定义和使用std::vector&lt;Bar&gt; 对象是合法的,只要您不调用任何需要Bar 的默认构造函数存在的函数。

    Foo&lt;Bar&gt; 的显式实例化定义 成功 的原因是,当 Foo&lt;Bar&gt; 被实例化时,编译器会将其默认构造函数标记为已删除(只要存在非默认构造的非静态成员)。因此,它在任何时候都不会尝试编译任何需要Bar 默认构造函数的代码,并且不会发生错误。

    【讨论】:

    • 嗯,所以显式实例化不一定非格式错误?
    • @cigien 是的,我相信假设它可能会成功,如脚注中所述,但实际上您不会看到这种情况发生
    • @cigien 实际上,我改变了主意。我认为 C++20 不再允许这种策略,因为std::vector 的大多数成员现在都是constexpr
    • 我不确定为什么成员的constexprness 会阻止该策略。如果需要,我认为仍然允许拆分声明和定义。
    • @cigien 但是如果它是constexpr,你不能将定义推迟到 TU 的末尾。我的意思是,你可以,但它会导致该函数根本不能在常量表达式中使用,因为所有使用都必须在定义之前。我认为该标准不可能以使结果符合的方式来解释。
    猜你喜欢
    • 1970-01-01
    • 2019-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-27
    • 1970-01-01
    • 1970-01-01
    • 2011-06-23
    相关资源
    最近更新 更多