微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

C2440:“正在初始化”:无法从“A<double>”转换为“A<double>”

如何解决C2440:“正在初始化”:无法从“A<double>”转换为“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,程序将编译并运行。

This question 没有回答我的问题。根据 cppreference.com,可以使用非常量参数的复制构造函数

类 T 的复制构造函数一个非模板构造函数,其第一个参数是 T&‍、 const T&‍、volatile T&‍ 或 const volatile T&‍,并且要么没有其他参数,要么其余的参数都有认值。

当我编写时,带有非常量参数的复制构造函数无论如何都可以工作

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

仍然返回相同的错误:

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 在他们的评论中所做的那样),因为没有名字,它没有生命周期,所以它几乎立即被破坏(有一个警告,如下所述)。在您的示例中,rvalueA<double>(a2),而在我的示例中为 2.71

链接的 cppreference 页面有两部分与此处相关的 rvalue 相关:

  • 内置地址运算符无法获取右值的地址:&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 优化将删除其中一个调用,乍一看可能会令人困惑,因为这两个复制构造函数调用中的哪一个导致了问题可能并不明显。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。