模板显式实例化如何工作以及何时工作?

How template explicit instantiation works and when?

这是 C++ primer 第 5 版的练习:

“练习 16.26:假设 NoDefault 是一个没有默认构造函数的 class,我们可以显式实例化 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 不是默认构造的。

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

extern template class vector<Bar>; // declaration ok
template class vector<Bar>; // instantiation: doesn't work?!

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

An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is an explicit instantiation definition of only those members that have been defined at the point of instantiation.

现在,std::vector<T> 有一个构造函数,它接受一个整数 n 并用 n value-initialized T 初始化向量。可以假定此构造函数的定义位于 <vector> header 中的某处(请参阅 Why can templates only be implemented in the header file?)。因此 std::vector<Bar> 的显式实例化定义将使用 T = Bar.

实例化该构造函数

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

Foo<Bar>显式实例化定义成功的原因是,当Foo<Bar>被实例化时,编译器将其默认构造函数标记为已删除(只要有 non-default-constructible non-static 成员,这种情况就会发生)。因此,它在任何时候都不会尝试编译任何需要 Bar 默认构造函数的代码,并且不会发生错误。