在引用初始化中使用已删除的复制构造函数复制初始化

jac*_*k X 19 c++ language-lawyer implicit-conversion c++11 reference-binding

考虑以下代码:

#include <iostream>
class Data{
public:
    Data() = default;
    Data(Data const&) = delete;
    Data(int) {

    }
};
int main(){
  int a = 0;
  const std::string& rs = "abc"; // rs refers to temporary copy-initialized from char array
  Data const& d_rf = a;          // #2 but here can be complied
  // accroding to the standard, the reference in #2 is bound to a temporary object, the temporary is copy-initialized from the expression
}
Run Code Online (Sandbox Code Playgroud)

[dcl.init.ref]

如果T1或T2是一个类类型和T1不参考相关到T2,用户定义的转换正在使用的规则考虑复制初始化型“CV1 T1”的对象的由用户定义的转换([dcl.init ], [over.match.copy], [over.match.conv]); 如果相应的非引用复制初始化格式错误,则程序格式错误。调用转换函数的结果,如非引用复制初始化所述,然后用于直接初始化引用。对于这种直接初始化,不考虑用户定义的转换

复制初始化

否则(即,对于剩余的复制初始化情况),如 [over. match.copy],并通过重载决议([over.match])选择最好的一个。如果转换无法完成或不明确,则初始化格式错误。选择的函数以初始化表达式作为参数被调用;如果函数是构造函数,则调用是目标类型的 cv 非限定版本的纯右值,其结果对象由构造函数初始化。该调用用于根据上述规则直接初始化作为复制初始化目标的对象。

根据标准,类型aint,初始化引用的类型是Data,因此从intData用户定义的转换考虑使用通过用户定义转换复制初始化类型“cv1 T1”的对象的规则. 这意味着Data const& d_rf = a;可以翻译为Data temporary = a; Data const& d_rf = temporary;. 对于Data temporary = a;,即使复制省略存在,复制/移动构造函数必须检查其是否可用,但拷贝构造函数class Data已被删除,为什么能不能遵守?

以下是 来自 enseignement 的标准
复制初始化引用的一些引用

从 cppreference 复制引用的初始化

如果引用是左值引用:

如果 object 是左值表达式,并且其类型是 T 或从 T 派生,并且具有相同或更少的 cv 限定,则引用绑定到由左值标识的对象或其基类子对象。
如果 object 是左值表达式,并且其类型可隐式转换为 T 或从 T 派生的类型,同样或更少 cv 限定,则源类型及其返回左值的基类的非显式转换函数参考被考虑并通过重载决议选择最好的。然后将引用绑定到由转换函数返回的左值标识的对象(或其基类子对象)

否则,如果引用是对 const 的右值引用或左值引用:

如果 object 是 xvalue、类纯右值、数组纯右值或 T 或从 T 派生的函数左值类型,同样或更少 cv 限定,则引用绑定到初始化表达式的值或其基础子对象。
如果 object 是可以隐式转换为 xvalue、类纯右值或类型为 T 或从 T 派生的函数值的类类型表达式,同样或更少 cv 限定,则引用绑定到结果的转换或其基础子对象。
否则,将构造一个 T 类型的临时对象并从对象复制初始化。然后将引用绑定到这个临时对象。复制初始化规则适用(不考虑显式构造函数)。
[例子:
const std::string& rs = "abc"; // rs 指的是从 char 数组初始化的临时副本]

更新:

我们考虑N337下的代码

按照标准,值a的类型是int,引用引用的目的类型是Data,所以编译器需要Data通过拷贝初始化的方式生成一个类型的临时对象。这里毫无疑问?所以我们专注于复制初始化。源类型为int,目标类型为Data,这种情况符合:

否则(即,对于剩余的复制初始化情况),可以从源类型转换为目标类型或(当使用转换函数时)到其派生类的用户定义的转换序列被枚举,如 13.3 中所述。 1.4,并通过重载决议(13.3)选择最好的一个。如果转换无法完成或不明确,则初始化格式错误。选择的函数以初始化表达式作为参数被调用;如果该函数是构造函数,则调用会初始化目标类型的 cv 非限定版本的临时版本。临时是一个纯右值。调用的结果(对于构造函数来说是临时的)然后用于根据上述规则直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;

注意粗体部分,这并不意味着该值int直接通过 初始化临时值Data::Data(int)。意思int是,先转换为Databy Data::Data(int),然后这个结果直接初始化临时对象,这里是复制初始化的目标对象。如果我们用代码来表达粗体部分,就像Data temporary(Data(a)).

上面的规则在这里:

— 如果初始化是直接初始化,或者如果是复制初始化,其中源类型的 cv 非限定版本与目标类相同,或者是目标类的派生类,则考虑构造函数。枚举适用的构造函数(13.3.1.3),并通过重载决议(13.3)选择最好的构造函数。调用如此选择的构造函数来初始化对象,并将初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决议不明确,则初始化格式错误。

请回Data temporary(Data(a))。显然,复制/移动构造函数是参数 Data(a) 的最佳匹配。但是,Data(Data const&) = delete;,因此复制/移动构造函数不可用。为什么编译器不报错?

xsk*_*xzr 5

这个问题已由Issue 1604解决,并且提议的解决方案似乎确认此类代码应该是格式错误的,因此我将其视为编译器错误。

幸运的是,从 C++17 开始,由于保证了复制省略,此代码变得格式良好,这与编译器一致。