当有可用的右值构造函数时,为什么从右值调用类引用构造函数重载?

Why does the class reference constructor overload get called from an rvalue when there's an rvalue constructor available?

提问人:GT 77 提问时间:6/2/2020 最后编辑:songyuanyaoGT 77 更新时间:6/3/2020 访问量:103

问:

此代码

#include <iostream>

struct A
{
    A(int i) {std::cout << "int received\n";}
    A(A& a) {std::cout << "ref received\n";}
};

int main()
{
    int j = 5;
    A a = j;
}

意外引发以下编译器错误:

error: invalid initialization of non-const reference of type 'A&' from an rvalue of type 'A'
note:   initializing argument 1 of 'A::A(A&)'
note:   after user-defined conversion: A::A(int)

当我删除第二个构造函数重载时,一切都按预期工作。我想编译器错误地调用了第二个构造函数而不是第一个构造函数。A(A& a)

为什么会这样?

如何让同时具有引用构造函数和右值构造函数的类协调工作?

我使用GNU GCC。

注意:我还注意到一些奇怪的事情:显然,如果我用 替换该行,一切都按预期工作。然而,这并不令人满意,因为如果我尝试从函数参数初始化对象(例如:调用 with )。A a = j;A a(j);void f(A a)f(j)

C++ 初始 构造函数 复制 初始化

评论

0赞 underscore_d 6/2/2020
隐式转换似乎是这里的关键。我敢打赌,适当地遵循 0/3/5 的规则会解决这个问题!
1赞 Some programmer dude 6/2/2020
是时候了解 C++ 的值类别了。右值(如通过转换创建的临时对象)不能像构造函数所期望的那样绑定到左值引用。您需要使用对常量对象的引用才能使其正常工作,如 .jA(A&)A(A const&)
1赞 StoryTeller - Unslander Monica 6/2/2020
有趣的例子。展示 C++ 是如何演变的。这从无效的 C++14 变为有效的 C++17。
0赞 Pete Becker 6/2/2020
术语:是一个复制构造函数。这不是最常见的形式; 是你几乎总是会看到的。但它仍然是一个复制构造函数。A(A&)A(const A&)

答:

3赞 songyuanyao 6/2/2020 #1

A a = j;执行复制初始化

直到 C++17,

如果 是类类型,并且 of 类型的 cv 非限定版本不是 或派生自 ,或者如果是非类类型,但类型是类类型,则检查可以从 的类型转换为 (或派生自 if 的类型)的用户定义的转换序列,并通过重载解析选择最佳序列。转换结果(如果使用了转换构造函数)将用于直接初始化对象。TotherTTTotherotherTTTprvalue temporary (until C++17)prvalue expression (since C++17)The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used. (until C++17)

类有一个复制结构,将左值引用为非常量,它不能绑定到临时转换自 .即使是临时的构造也可能被优化:复制构造函数必须可用。AintA

让复制构造函数接受左值引用(或添加移动构造函数)将解决这个问题。const

由于 C++17 由于强制复制省略,代码可以正常工作。

这些对象被直接构造到存储中,否则它们将被复制/移动到其中。复制/移动构造函数不需要存在或可访问:

另一方面,执行直接初始化,从直接初始化,复制构造函数不涉及。A a(j);aj

评论

0赞 dfrib 6/2/2020
如果您想引用标准而不是/除了(因为从标准演进的角度来看这是一个非常有趣的例子)cppreference,相关部分是 N4140 中的 [class.copy]/32C++14 + 编辑修复),并根据 P0135R1 更改为 C++17(部分)。不过,我找不到完全删除 [class.copy]/32 的论文。
0赞 songyuanyao 6/2/2020
@dfri 谢谢。选择 [class.copy]/32 似乎专注于 return 语句的两阶段重载解决,我认为应该有更适合这种情况的,但我现在在标准中找不到。.
0赞 dfrib 6/2/2020
是的,你是对的;我自己似乎找不到相关部分(它可能分布在标准的不同部分,并在 cpppreference 处进行了整齐的总结)。