C2440:'initializing':无法从 'A<double>' 转换为 'A<double>'
C2440: 'initializing': cannot convert from 'A<double>' to 'A<double>'
此代码在 Visual Studio 2017 中引发编译错误:
#include <iostream>
#include <string>
using std::cin;
using std::cout;
template<class T>
class A
{
public:
A(T a);
~A() {}
#if 0
A(const A<T>&);
#else
A(A<T>&);
#endif
T t;
};
template<class T>
A<T>::A(T a) : t(a) {}
template <class T>
#if 0
A<T>::A(const A<T>& a)
#else
A<T>::A(A<T>& a)
#endif
{
t = a.t;
std::cout << "In A copy constructor.\n";
}
int main()
{
std::string s;
A<int> a1(11);
A<double> a2(2.71);
#if 1
A<double> a3 = A<double>(a2); //gives C2440 when copy constructor argument is not const.
//compiler message is: 'initializing': cannot convert from 'A<double>' to 'A<double>'
#else
A<double> a3{a2}; //works when copy constructor argument is not const.
#endif
std::cout << a3.t << "\n";
std::cout << "Press ENTER to exit.\n";
std::getline(std::cin, s);
}
C2440 编译失败:'initializing': cannot convert from 'A<double>' to 'A<double>'.
当前两个 #if 0
更改为 #if 1
时(选择带有 const 参数的复制构造函数),程序编译并运行。同样,如果所有条件编译都选择#if 0
,程序编译运行。
没有回答我的问题。根据 cppreference.com,带有非常量参数的复制构造函数是可能的:
A copy constructor of class T is a non-template constructor whose first parameter is T&, const T&, volatile T&, or const volatile T&, and either there are no other parameters, or the rest of the parameters all have default values.
并且当我写
时,带有非常量参数的复制构造函数仍然有效
A<double> a3{a2};
那么为什么用
初始化
A<double> a3 = A<double>(a2);
当复制构造函数的参数不是 const 时不起作用?
在这种情况下,MSVC的报错信息特别少;如果你 运行 GCC 超过这个你会得到以下错误:
main.cpp: In function ‘int main()’:
main.cpp:42:20: error: cannot bind non-const lvalue reference of type ‘A<double>&’ to an rvalue of type ‘A<double>’
42 | A<double> a3 = A<double>(a2); //gives C2440 when copy constructor argument is not const.
| ^~~~~~~~~~~~~
main.cpp:28:15: note: initializing argument 1 of ‘A<T>::A(A<T>&) [with T = double]’
28 | A<T>::A(A<T>& a)
| ~~~~~~^
make: *** [<builtin>: main] Error 1
如果您删除示例中所有不相关的部分,您将得到:
int main() {
double &a = 2.71;
}
仍然returns同样的错误:
main.cpp: In function ‘int main()’:
main.cpp:2:13: error: cannot bind non-const lvalue reference of type ‘double&’ to an rvalue of type ‘double’
2 | double &a = 2.71;
| ^~~~
make: *** [<builtin>: main] Error 1
要解压缩,我们需要查看 C++ 中的不同 value categories 以及每个有哪些限制:
lvalue
广义地说,是有名字的东西。在您最初的失败示例中, lvalue
是复制构造函数的 A<T> &a
参数,而在我精简的示例中,它是 double &a
.
rvalue
有点棘手,但它实际上是没有名字的东西。通常它被称为 temporary
(正如@Eljay 在他们的评论中所做的那样)因为没有名字,它没有生命周期,所以它几乎立即被破坏(警告如下)。在你的例子中,rvalue
是 A<double>(a2)
,在我的例子中是 2.71
.
关于 rvalue
的链接 cppreference 页面有两部分与此处相关:
- 右值的地址不能被内置的地址运算符获取:
&int()
、&i++
、&42
和 &std::move(x)
无效
- 右值可用于初始化 const 左值引用,在这种情况下,由右值标识的对象的生命周期会延长,直到引用范围结束
第一点是在没有 const
传递 rvalue
的情况下导致复制构造函数出现问题的原因;根据语言规则,被引用的对象在构造函数中不再有效。
第二点是什么允许带有const
的复制构造函数在传递rvalue
时运行;生命周期被延长,直到构造函数内部的引用结束,这允许构造函数复制出成员变量等
复制消除皱纹
您初始化的两种方式 a3
与第一种不同:
A<double> a3{a2};
只调用一次复制构造函数,使用 lvalue
(a2
) 而第二次:
A<double> a3 = A<double>(a2);
调用复制构造函数两次,第一次调用 lvalue
(a2
),第二次调用 rvalue
(第一次调用构造函数的结果)。但是,copy elision 优化将删除其中一个调用,乍一看可能会造成混淆,因为可能不清楚是这两个复制构造函数调用中的哪一个导致了问题。
此代码在 Visual Studio 2017 中引发编译错误:
#include <iostream>
#include <string>
using std::cin;
using std::cout;
template<class T>
class A
{
public:
A(T a);
~A() {}
#if 0
A(const A<T>&);
#else
A(A<T>&);
#endif
T t;
};
template<class T>
A<T>::A(T a) : t(a) {}
template <class T>
#if 0
A<T>::A(const A<T>& a)
#else
A<T>::A(A<T>& a)
#endif
{
t = a.t;
std::cout << "In A copy constructor.\n";
}
int main()
{
std::string s;
A<int> a1(11);
A<double> a2(2.71);
#if 1
A<double> a3 = A<double>(a2); //gives C2440 when copy constructor argument is not const.
//compiler message is: 'initializing': cannot convert from 'A<double>' to 'A<double>'
#else
A<double> a3{a2}; //works when copy constructor argument is not const.
#endif
std::cout << a3.t << "\n";
std::cout << "Press ENTER to exit.\n";
std::getline(std::cin, s);
}
C2440 编译失败:'initializing': cannot convert from 'A<double>' to 'A<double>'.
当前两个 #if 0
更改为 #if 1
时(选择带有 const 参数的复制构造函数),程序编译并运行。同样,如果所有条件编译都选择#if 0
,程序编译运行。
A copy constructor of class T is a non-template constructor whose first parameter is T&, const T&, volatile T&, or const volatile T&, and either there are no other parameters, or the rest of the parameters all have default values.
并且当我写
时,带有非常量参数的复制构造函数仍然有效A<double> a3{a2};
那么为什么用
初始化A<double> a3 = A<double>(a2);
当复制构造函数的参数不是 const 时不起作用?
在这种情况下,MSVC的报错信息特别少;如果你 运行 GCC 超过这个你会得到以下错误:
main.cpp: In function ‘int main()’:
main.cpp:42:20: error: cannot bind non-const lvalue reference of type ‘A<double>&’ to an rvalue of type ‘A<double>’
42 | A<double> a3 = A<double>(a2); //gives C2440 when copy constructor argument is not const.
| ^~~~~~~~~~~~~
main.cpp:28:15: note: initializing argument 1 of ‘A<T>::A(A<T>&) [with T = double]’
28 | A<T>::A(A<T>& a)
| ~~~~~~^
make: *** [<builtin>: main] Error 1
如果您删除示例中所有不相关的部分,您将得到:
int main() {
double &a = 2.71;
}
仍然returns同样的错误:
main.cpp: In function ‘int main()’:
main.cpp:2:13: error: cannot bind non-const lvalue reference of type ‘double&’ to an rvalue of type ‘double’
2 | double &a = 2.71;
| ^~~~
make: *** [<builtin>: main] Error 1
要解压缩,我们需要查看 C++ 中的不同 value categories 以及每个有哪些限制:
lvalue
广义地说,是有名字的东西。在您最初的失败示例中,lvalue
是复制构造函数的A<T> &a
参数,而在我精简的示例中,它是double &a
.rvalue
有点棘手,但它实际上是没有名字的东西。通常它被称为temporary
(正如@Eljay 在他们的评论中所做的那样)因为没有名字,它没有生命周期,所以它几乎立即被破坏(警告如下)。在你的例子中,rvalue
是A<double>(a2)
,在我的例子中是2.71
.
关于 rvalue
的链接 cppreference 页面有两部分与此处相关:
- 右值的地址不能被内置的地址运算符获取:
&int()
、&i++
、&42
和&std::move(x)
无效 - 右值可用于初始化 const 左值引用,在这种情况下,由右值标识的对象的生命周期会延长,直到引用范围结束
第一点是在没有 const
传递 rvalue
的情况下导致复制构造函数出现问题的原因;根据语言规则,被引用的对象在构造函数中不再有效。
第二点是什么允许带有const
的复制构造函数在传递rvalue
时运行;生命周期被延长,直到构造函数内部的引用结束,这允许构造函数复制出成员变量等
复制消除皱纹
您初始化的两种方式 a3
与第一种不同:
A<double> a3{a2};
只调用一次复制构造函数,使用 lvalue
(a2
) 而第二次:
A<double> a3 = A<double>(a2);
调用复制构造函数两次,第一次调用 lvalue
(a2
),第二次调用 rvalue
(第一次调用构造函数的结果)。但是,copy elision 优化将删除其中一个调用,乍一看可能会造成混淆,因为可能不清楚是这两个复制构造函数调用中的哪一个导致了问题。