模板类的特殊成员函数何时被实例化?

时间:2022-01-16 22:52:32

When are the special member functions (specifically, copy/move constructors and copy/move assignment operators) of a template class instantiated? As soon as the class itself is instantiated, or only when they are needed?

模板类的特殊成员函数(具体地说,复制/移动构造函数和复制/移动分配操作符)何时被实例化?一旦实例化了类本身,或者只在需要的时候?

This comes up in the following situation:

这在以下情况出现:

template <class T, class U>
struct pair
{
    T first;                 
    U second;                

    pair() : first(), second() {}

    pair(const pair&) = default;
};

struct S
{
    S() {}
    S(const S&) = delete;
    S(S&&) = default;
};

int main()
{
    pair<int, S> p;
}

Clang refuses to compile this code, with the following errors:

Clang拒绝编译此代码,其错误如下:

test.cpp:9:5: error: the parameter for this explicitly-defaulted copy constructor is const, but a member or base requires it to be
      non-const
    pair(const pair&) = default;
    ^
test.cpp:21:18: note: in instantiation of template class 'pair<int, S>' requested here
    pair<int, S> p;
                 ^

suggesting that it tries to instantiate the copy constructor as soon as the class is instantiated.

建议它在类实例化后立即实例化复制构造函数。

GCC, however, compiles the code just fine, suggesting that it would only try to instantiate the copy constructor if it was actually needed.

然而,GCC编译代码很好,这表明它只在需要复制构造函数时才会尝试实例化它。

Which compiler's behaviour is correct?

哪个编译器的行为是正确的?

(A similar discrepancy is exhibited for assignment operators.)

(作业人员也有类似的差异。)

UPDATE: This has something to do with the fact that the copy constructor of pair in this example is defaulted, because if I change its definition to

UPDATE:这与这个示例中的pair的复制构造函数是默认的有关,因为如果我将其定义更改为

pair(const pair& p) : first(p.first), second(p.second) {}

then the code passes clang as well.

然后代码也通过了clang。

2 个解决方案

#1


2  

The relevant passage of the standard is [dcl.fct.def.default]/1:

标准的相关段落是[dcl.fct.def.default]/1:

A function that is explicitly defaulted shall [...] have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be "reference to non-const T", where T is the name of the member function's class) as if it had been implicitly declared

明确默认的函数应[……])有相同的声明的函数类型(除了可能不同ref-qualifiers和除了拷贝构造函数或复制赋值运算符的情况下,参数类型可能是“参考non-const T”,其中T是成员函数的类)的名字好像是隐式声明

This rule applies even if the defaulted function is never used. Now, [class.copy]/9 says:

即使从未使用默认函数,也要使用此规则。现在,[类。副本)/ 9说:

The implicitly-declared copy constructor will have the form

隐含声明的复制构造函数将具有表单

X::X(const X&)

X::X(const X)

if [...] for all the non-static data members of X that are of a class type M [...], each such class type has a copy constructor whose first parameter is of type const M& or const volatile M&.

如果[…对于类类型为M的X的所有非静态数据成员[…],每个此类类类型都有一个复制构造函数,其第一个参数为const &或const volatile M&类型。

Otherwise the implicitly-declared copy constructor will have the form

否则,隐含声明的复制构造函数将具有表单

X::X(X&)

X::X(X)

Therefore, an example like this is ill-formed (and should produce the diagnostics you are seeing):

因此,这样的示例是不完整的(并且应该生成您所看到的诊断):

struct A {
  A();
  A(A&); // Note, non-const type A in copy constructor
};
template<typename T>
struct B {
  T t;
  B();
  B(const B&) = default;
};
B<A> b; // error, B<A>::B(const B&) is defaulted but has the wrong type

However, in your example, this rule doesn't apply. Due to a clang bug (which is already fixed), deleted copy constructors were incorrectly considered to have non-const parameter types, leading to this error.

然而,在您的示例中,这个规则并不适用。由于clang错误(已经修复),被删除的复制构造函数被错误地认为具有非const参数类型,从而导致此错误。

#2


5  

Have a look at section 14.7.1 of the current C++11 standard. To quote from the n3242 version of the draft:

查看当前c++ 11标准的14.7.1节。引用n3242版草案:

The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, static data members and member templates; and it causes the implicit instantiation of the definitions of member anonymous unions. Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.

类模板专门化的隐式实例化导致类成员函数、成员类、静态数据成员和成员模板的声明的隐式实例化,而不是定义或默认参数的隐式实例化;它导致了成员匿名联合定义的隐式实例化。除非类模板的成员或成员模板的成员已显式实例化或显式专门化,否则在需要成员定义存在的上下文中引用专门化时,该成员的专门化将隐式实例化;特别是,静态数据成员的初始化(以及任何相关的副作用)不会发生,除非静态数据成员本身的使用方式要求存在静态数据成员的定义。

So, this means, when you use a class as a type as above only the declarations are instantiated with it. So the actual (defaulted) implementation of the copy constructor should not be instantiated, as it is not needed in the above code. So GCC is handling this correctly, whereas Clang does not.

因此,这意味着,当您使用一个类作为如上所述的类型时,只有声明与它一起实例化。因此,不应该实例化复制构造函数的实际(默认)实现,因为上面的代码不需要它。GCC正确地处理了这个问题,而Clang没有。

Also your edit suggests, that Clang is generating the implementation for the default copy constructor too early, since your directly implemented copy constructor is faulty as well (you cannot call the copy constructor for S as you are doing in your own implementation). Since the defaulted implementation and your implementation should be the same in all respects (including the time of instantiation), I would consider this a clang bug.

同样,您的编辑建议,Clang过早地为默认复制构造函数生成实现,因为直接实现的复制构造函数也有问题(您不能像在自己的实现中那样为S调用复制构造函数)。由于默认实现和您的实现在所有方面都应该是相同的(包括实例化的时间),我认为这是一个clang bug。

#1


2  

The relevant passage of the standard is [dcl.fct.def.default]/1:

标准的相关段落是[dcl.fct.def.default]/1:

A function that is explicitly defaulted shall [...] have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be "reference to non-const T", where T is the name of the member function's class) as if it had been implicitly declared

明确默认的函数应[……])有相同的声明的函数类型(除了可能不同ref-qualifiers和除了拷贝构造函数或复制赋值运算符的情况下,参数类型可能是“参考non-const T”,其中T是成员函数的类)的名字好像是隐式声明

This rule applies even if the defaulted function is never used. Now, [class.copy]/9 says:

即使从未使用默认函数,也要使用此规则。现在,[类。副本)/ 9说:

The implicitly-declared copy constructor will have the form

隐含声明的复制构造函数将具有表单

X::X(const X&)

X::X(const X)

if [...] for all the non-static data members of X that are of a class type M [...], each such class type has a copy constructor whose first parameter is of type const M& or const volatile M&.

如果[…对于类类型为M的X的所有非静态数据成员[…],每个此类类类型都有一个复制构造函数,其第一个参数为const &或const volatile M&类型。

Otherwise the implicitly-declared copy constructor will have the form

否则,隐含声明的复制构造函数将具有表单

X::X(X&)

X::X(X)

Therefore, an example like this is ill-formed (and should produce the diagnostics you are seeing):

因此,这样的示例是不完整的(并且应该生成您所看到的诊断):

struct A {
  A();
  A(A&); // Note, non-const type A in copy constructor
};
template<typename T>
struct B {
  T t;
  B();
  B(const B&) = default;
};
B<A> b; // error, B<A>::B(const B&) is defaulted but has the wrong type

However, in your example, this rule doesn't apply. Due to a clang bug (which is already fixed), deleted copy constructors were incorrectly considered to have non-const parameter types, leading to this error.

然而,在您的示例中,这个规则并不适用。由于clang错误(已经修复),被删除的复制构造函数被错误地认为具有非const参数类型,从而导致此错误。

#2


5  

Have a look at section 14.7.1 of the current C++11 standard. To quote from the n3242 version of the draft:

查看当前c++ 11标准的14.7.1节。引用n3242版草案:

The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, static data members and member templates; and it causes the implicit instantiation of the definitions of member anonymous unions. Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.

类模板专门化的隐式实例化导致类成员函数、成员类、静态数据成员和成员模板的声明的隐式实例化,而不是定义或默认参数的隐式实例化;它导致了成员匿名联合定义的隐式实例化。除非类模板的成员或成员模板的成员已显式实例化或显式专门化,否则在需要成员定义存在的上下文中引用专门化时,该成员的专门化将隐式实例化;特别是,静态数据成员的初始化(以及任何相关的副作用)不会发生,除非静态数据成员本身的使用方式要求存在静态数据成员的定义。

So, this means, when you use a class as a type as above only the declarations are instantiated with it. So the actual (defaulted) implementation of the copy constructor should not be instantiated, as it is not needed in the above code. So GCC is handling this correctly, whereas Clang does not.

因此,这意味着,当您使用一个类作为如上所述的类型时,只有声明与它一起实例化。因此,不应该实例化复制构造函数的实际(默认)实现,因为上面的代码不需要它。GCC正确地处理了这个问题,而Clang没有。

Also your edit suggests, that Clang is generating the implementation for the default copy constructor too early, since your directly implemented copy constructor is faulty as well (you cannot call the copy constructor for S as you are doing in your own implementation). Since the defaulted implementation and your implementation should be the same in all respects (including the time of instantiation), I would consider this a clang bug.

同样,您的编辑建议,Clang过早地为默认复制构造函数生成实现,因为直接实现的复制构造函数也有问题(您不能像在自己的实现中那样为S调用复制构造函数)。由于默认实现和您的实现在所有方面都应该是相同的(包括实例化的时间),我认为这是一个clang bug。