初始化忽略构造函数模板。

3

在查找一些错误时,我发现了初始化的以下行为,这似乎有些奇怪:在初始化检查现有构造函数的同时,似乎存在忽略适合构造函数模板的情况。例如,请考虑以下程序:

#include <iostream>

template<class T>
struct A {
 A() {};
 template<class S>
 A(const A<S>& a) {std::cout << "constructor template used for A" << std::endl;};
};

template<class T>
struct B{
 B() {};
 B(const B<int>& b) {std::cout << "constructor used for B" << std::endl;};
};

int main() {
 A<int> a;
 B<int> b;
 A<int> aa = a;
 B<int> bb = b;
 A<double> aaa = a;
}

对我来说,这将产生输出结果。
constructor used for B
constructor template used for A

这意味着它不使用main函数第三行的构造函数。为什么呢?有原因吗?还是我的语法哪里出了问题?模板似乎很好用,因为在最后一行它被成功地使用了。

我知道这个例子似乎过于复杂,但是各种简化会导致我想要展示的行为消失。此外:一个模板特化将被初始化使用,并且是我目前防止出现错误的方法。(这是一开始导致错误的地方)。

如果我的问题有任何偏差,请您原谅,我不是程序员,也不是母语使用者,这是我的第一个问题。


第三行使用的是复制构造函数,而不是默认构造函数后跟赋值。 - Peter Wood
当询问“为什么它执行X而不是Y”时,说明为什么你认为它应该执行Y会有所帮助。对于许多人来说,执行X可能是如此显然以至于他们从未考虑过Y可能会发生。 - PlasmaHH
3个回答

4
编译器提供了一个隐式声明的非模板复制构造函数,其签名等效于
A(const A& a);

因为模板构造函数不被视为用户定义的复制构造函数,即复制构造函数必须是非模板的。

隐式声明的复制构造函数比模板版本更适合重载决策,并且在从A<T>复制构造A<T>时调用。这可以通过一个简单的例子来说明,其中有一个用户定义的A(const A&)

#include <iostream>

template<class T>
struct A 
{
 A() {};

 A(const A& a) {
   std::cout << "copy constructor used for A" << std::endl;
 }

 template<class S>
 A(const A<S>& a) {
   std::cout << "constructor template used for A" << std::endl;
 }
};

int main()
{
  A<int> ai;
  A<double> ad = ai; /  calls template conversion contructor
  A<int> ai2 = ai;   // calls copy constructor A(const A&);
}

1
这个解释可能适用于任何函数,但我认为它并没有深入到这个特定情况的核心:为什么编译器会自动生成一个独立的复制构造函数,而这个函数可以从模板构造函数中生成。 - Gorpik
好的,看起来OP持不同意见,对我来说也没关系 :) - Gorpik
1
@Gorpik 你说得对,我没有解释清楚。我添加了几行文字来(希望)澄清。 - juanchopanza
我仍然很感兴趣,为什么他们选择不接受将构造函数作为模板的复制构造函数。 - user2115777
@user2115777,这可能是由于模板函数(成员或其他)直到实例化后才存在。因此,在使用前,复制构造函数并不存在。编译器无法知道是否有这个函数,因此必须提供隐式生成的复制构造函数。还有元编程情况,需要在编译时知道类型是否具有复制构造函数。 - juanchopanza
@juanchopanza 实际上,拷贝构造函数在使用前不会被隐式定义(§12.8/4)。我猜编程语言的设计者认为一个类可能有一个非平凡的模板转换构造函数,该函数执行一些对于拷贝构造函数来说是不必要的转换。但这只是我的猜测。 - Gorpik

4
根据C++11标准第12.8/7段,如果类的定义没有显式声明复制构造函数,则会隐式声明一个。此外,根据第12.8/2段,如果类X的非模板构造函数的第一个参数是X&, const X&, volatile X&或const volatile X&类型,并且没有其他参数或所有其他参数都有默认值(8.3.6),则该构造函数为复制构造函数。因此,在执行此行代码期间,编译器将生成一个隐式复制构造函数。
A<int> aa = a;

这就解释了为什么你看不到相应的输出。

感谢您的回答。由于我只被允许接受一个答案,而另一个先到了,您将不得不没有机会了。非常抱歉。 - user2115777
@user2115777:没问题,但是没有什么强制你接受第一个答案,你可以随时改变主意 :-) 然而,这并不意味着你应该接受这个答案:你已经接受的那个是正确的和好的。 - Andy Prowl
我尝试接受两者...所以我知道我可以改变主意。但仍然有一个问题,为什么不允许将模板用作复制构造函数是有意义的(请参见已接受答案中的讨论)。如果你碰巧知道... - user2115777
我猜原因是编译器无法预先知道你的类是否有复制构造函数。在你的特定情况下,你的构造函数模板接受任何模板参数,所以没问题,但通常情况下,模板的实例化可能会因为某些参数而失败,而这些参数可能正是在使用时实例化复制构造函数的参数。编译器无法进行语义分析并在一般情况下确定是否存在有效的替换来生成复制构造函数。 - Andy Prowl
@user2115777:可能还有其他(更好的?)原因。我只是在猜测。 - Andy Prowl

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接