【问题标题】:When are special member functions of a template class instantiated?模板类的特殊成员函数何时实例化?
【发布时间】:2012-05-09 09:17:24
【问题描述】:

模板类的特殊成员函数(特别是复制/移动构造函数和复制/移动赋值运算符)何时实例化?一旦类本身被实例化,还是仅在需要它们时?

这出现在以下情况:

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 拒绝编译这段代码,出现以下错误:

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;
                 ^

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

但是,GCC 可以很好地编译代码,这表明它只会在实际需要时尝试实例化复制构造函数。

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

(赋值运算符也有类似的差异。)

更新:这与此示例中pair 的复制构造函数是defaulted 的事实有关,因为如果我将其定义更改为

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

然后代码也会通过 clang。

【问题讨论】:

  • 使用 -std=c++11 的 clang3.0 编译良好。

标签: c++ templates c++11 clang copy-constructor


【解决方案1】:

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

显式默认的函数应 [...] 具有相同的声明函数类型(除了可能不同的引用限定符以及在复制构造函数或复制赋值运算符的情况下,参数类型可能是“引用非常量 T”,其中 T 是成员函数的类的名称)就好像它已经被隐式声明了

即使从不使用默认功能,此规则也适用。现在,[class.copy]/9 说:

隐式声明的复制构造函数将具有以下形式

X::X(const X&amp;)

如果 [...] 对于 X 的所有非静态数据成员属于类类型 M [...],则每个此类类型都有一个复制构造函数,其第一个参数是类型const M&amp;const volatile M&amp;

否则隐式声明的复制构造函数将具有以下形式

X::X(X&amp;)

因此,这样的示例格式不正确(应该会产生您所看到的诊断结果):

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

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

【讨论】:

    【解决方案2】:

    查看当前 C++11 标准的第 14.7.1 节。引用n3242版本的草稿:

    类模板特化的隐式实例化导致 声明的隐式实例化,但不是 类成员函数的定义或默认参数, 成员类、静态数据成员和成员模板;它 导致成员定义的隐式实例化 匿名工会。除非是类模板的成员或成员 模板已被显式实例化或显式特化, 成员的特化被隐式实例化,当 在需要成员的上下文中引用专业化 存在的定义;特别是,初始化(和任何 静态数据成员的相关副作用)不会发生,除非 静态数据成员本身的使用方式需要 要存在的静态数据成员的定义。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多