考虑以下代码:
#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
}
如果 T1 或 T2 是类类型,并且 T1 与 T2 没有引用相关,则使用通过用户定义转换([ dcl.init ], [over.match.copy], [over.match.conv]); 如果相应的非参考复制初始化格式错误,则程序格式错误。调用转换函数的结果,如针对非引用复制初始化所描述的,然后用于直接初始化引用。对于这种直接初始化,不考虑用户定义的转换
否则(即,对于剩余的复制初始化情况),可以从源类型转换到目标类型或(当使用转换函数时)到其派生类的用户定义转换被枚举,如 [over. match.copy],并通过重载决议([over.match])选择最佳的。如果转换无法完成或不明确,则初始化格式错误。以初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用是目标类型的 cv 非限定版本的纯右值,其结果对象由构造函数初始化。该调用用于根据上述规则直接初始化作为复制初始化目标的对象。
按照标准, 的类型a
是int
,初始化引用的类型是Data
,所以 fromint
到Data
,用户定义的转换被认为是使用通过用户定义转换复制初始化类型为“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 类型的临时对象并从对象复制初始化。然后引用绑定到这个临时。应用复制初始化规则(不考虑显式构造函数)。
[例子:
常量 std::string& rs = "abc"; // rs 指的是从 char 数组 中初始化的临时复制]
更新:
我们考虑N337下的代码
根据标准,值a
的类型是int
,引用所指的目的类型是Data
,所以编译器需要Data
通过拷贝初始化生成一个临时的类型。这里毫无疑问,所以我们专注于拷贝初始化。源类型是int
目标类型是Data
,这种情况符合:
否则(即,对于剩余的复制初始化情况),可以从源类型转换到目标类型或(当使用转换函数时)到其派生类的用户定义转换序列被枚举,如 13.3 中所述。 1.4,最好的一个是通过重载决议(13.3)选择的。如果转换无法完成或不明确,则初始化格式错误。以初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用初始化目标类型的 cv 非限定版本的临时版本。临时是prvalue。然后根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;
注意粗体部分,并不表示该值int
直接将临时值初始化为Data::Data(int)
。这意味着,int
首先转换为Data
by Data::Data(int)
,然后此结果直接初始化临时对象,即此处复制初始化的目标对象。如果我们用代码来表达粗体部分,就像Data temporary(Data(a))
.
上面的规则在这里:
— 如果初始化是直接初始化,或者如果是复制初始化,其中源类型的 cv 非限定版本与目标类相同或派生类,则考虑构造函数。枚举了适用的构造函数(13.3.1.3),并通过重载决议(13.3)选择最佳构造函数。调用如此选择的构造函数来初始化对象,使用初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决议不明确,则初始化格式错误。
请重新返回Data temporary(Data(a))
。显然,复制/移动构造函数是参数 Data(a) 的最佳匹配。但是,Data(Data const&) = delete;
,所以复制/移动构造函数不可用。为什么编译器不报错?