为什么标准不将模板构造函数视为复制构造函数?

Why doesn't the standard consider a template constructor as a copy constructor?

这里是拷贝构造函数的定义,[class.copy.ctor/1]:

A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments ([dcl.fct.default]).

为什么标准将模板排除在复制构造函数之外?

在这个简单的例子中,两个构造函数都是复制构造函数:

struct Foo {
    Foo(const Foo &); // copy constructor
    Foo(Foo &);       // copy constructor
};

看这个类似的例子:

struct Foo {
     Foo() = default;

     template <typename T>
     Foo(T &) {
         printf("here\n");
     }
};

int main() {
    Foo a;
    Foo b = a;
}

在此示例中,将打印 here。所以看起来我的模板构造函数是一个复制构造函数,至少它的行为像一个(它是在通常调用复制构造函数的上下文中调用的)。

为什么文中有"non-template"要求?

复制构造函数的形式为 X(X& ) 或 (X const&),如果您没有自己声明,编译器会为您提供。如果您使用模板 classes.

可能会出现非模板问题

假设有一个模板 class,它有一个模板复制构造函数。问题是,当您使用具有相同模板类型的此 class 的另一个实例实例化 class 时,您的模板复制构造函数将不会被调用。

问题不在于您的复制构造函数模板不匹配。问题是隐式复制构造函数不是函数模板,在重载解析方面,非模板优于模板特化。

来源:

让我们暂时搁置模板。如果 class 没有声明复制构造函数,则会生成一个隐式默认的复制构造函数。它可能被定义为已删除,但它仍然是默认值。

成员模板不是成员函数。成员仅在需要时从中实例化。

那么,编译器如何仅根据 class 定义就知道是否需要 T = Foo 的特化?它不能。但这正是它需要决定如何处理对隐式默认复制构造函数(和移动构造函数)的潜在需求的基础。那变得凌乱。

最简单的方法是排除模板。无论如何,我们总会有一些复制构造函数,它会默认做正确的事情TM,并且会受到重载决议的青睐,因为它不是从模板实例化的。

Why is the "non-template" requirement there in the text?

鉴于不同,复制构造函数可以是模板。在存在复制构造函数模板的情况下,非复制构造函数如何不歧义?考虑一下:

struct Foo {
   // ctor template: clearly useful and necessary
   template <typename T>
      Foo(const T&) {}

   // copy ctor: same signature! can't work
   template <typename T>
      Foo(const T &) {}
};

此外,从不是 Foo 的对象构造 Foo 可以通过转换或普通构造来实现,但允许从非 [=13= 复制构造]对象将复制的概念更改为包括转换的复制。但这已经可以用现有方案实现(转换或非复制构造)。

In this example, here will be printed. So it seems that my template constructor is a copy constructor

您展示的示例未调用复制构造,而是普通的隐式构造。如果将构造函数模板更改为

template <typename T>
Foo(const T &) {
//  ^^^^^
    printf("here\n");
}

然后 Foo b = a; 导致调用编译器生成的复制构造函数。请注意,编译器生成的复制构造函数具有此签名:

Foo(const Foo&);

这需要在 Foo b = a; 中向 a 添加一个 const 限定符。您代码段中的原始构造函数模板 Foo(T&) 更匹配,因为没有添加 const-qualifier。