【发布时间】:2020-05-06 06:36:50
【问题描述】:
考虑以下代码:
#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 没有引用相关,则使用“cv1 T1”类型对象的复制初始化规则考虑用户定义的转换通过用户定义的转换([dcl.init]、[over.match.copy]、[over.match.conv]);如果相应的非参考复制初始化格式错误,则程序格式错误。调用转换函数的结果,如针对非引用复制初始化所描述的,然后用于直接初始化引用。对于这种直接初始化,不考虑用户定义的转换
否则(即,对于剩余的复制初始化情况),可以从源类型转换到目标类型或(当使用转换函数时)到其派生类的用户定义转换被枚举,如中所述[over.match.copy],通过重载决议([over.match])选择最佳的。如果转换无法完成或不明确,则初始化格式错误。以初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用是目标类型的 cv 非限定版本的纯右值,其结果对象由构造函数初始化。该调用用于根据上述规则直接初始化作为复制初始化目标的对象。
按照标准,a的类型是int,初始化引用的类型是Data,所以从int到Data,考虑自定义转换通过用户定义的转换使用复制初始化类型“cv1 T1”对象的规则。这意味着Data const& d_rf = a; 可以翻译成Data temporary = a; Data const& d_rf = temporary;。对于Data temporary = a;,即使复制省略存在,也必须检查复制/移动构造函数是否可用,但复制构造函数的class Data已被删除,为什么还能遵守?
这里有一些标准的引用
Copy initialization of reference from enseignement
Copy initialization of reference 来自 cppreference
如果引用是左值引用:
如果对象是左值表达式,并且它的类型是 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 数组中的临时 copy-initialized ]
更新:
我们考虑N337下的代码
根据标准,值a的类型是int,引用所指的目的类型是Data,所以编译器需要通过Data类型的临时值strong>复制初始化。这里毫无疑问,所以我们重点关注复制初始化。源类型为int,目标类型为Data,这种情况符合:
否则(即,对于剩余的复制初始化情况),用户定义的转换序列 可以从源类型转换为目标类型或(当转换函数 使用)对其派生类进行枚举,如 13.3.1.4 中所述,最好的一个是 通过重载决议(13.3)选择。如果转换无法完成或不明确,则 初始化格式不正确。选择的函数以初始化表达式作为其调用 争论;如果函数是构造函数,则调用初始化 cv-unqualified 的临时 目标类型的版本。临时是prvalue。 调用的结果(即 根据上面的规则,构造函数情况下的临时)然后用于直接初始化, 作为复制初始化目标的对象。在某些情况下,实现 允许通过构造 中间结果直接放入正在初始化的对象中;
注意粗体部分,不代表int值直接用Data::Data(int)初始化临时值。也就是说,int首先被Data::Data(int)转换为Data,然后这个结果直接初始化了这里的复制初始化目标对象的临时对象。如果我们用代码来表示粗体部分,就像Data temporary(Data(a))。
上面的规则在这里:
——如果初始化是直接初始化,或者如果是复制初始化,其中 cv-unqualified 源类型的版本与目标类是同一类或派生类, 构造函数被考虑。列举了适用的构造函数(13.3.1.3),最好的 一个是通过重载决议(13.3)选择的。调用如此选择的构造函数来初始化对象,使用初始化表达式或表达式列表作为其参数。如果没有构造函数 适用,或重载决议不明确,初始化格式不正确。
请回Data temporary(Data(a))。显然,复制/移动构造函数是参数 Data(a) 的最佳匹配。但是,Data(Data const&) = delete;,所以复制/移动构造函数不可用。为什么编译器不报错?
【问题讨论】:
-
为什么你认为复制初始化是为第 2 行完成的。我认为充其量 operator& 被调用,但在你的情况下没有定义。
-
请注意,
#1自 C++17 起有效 -
@Klaus,源类型与目标类型不兼容
-
@LukaRahne 从 int 到 Data ,需要自定义转换
-
@Mannoj 注意,源类型与目标类型不兼容
标签: c++ c++11 language-lawyer implicit-conversion reference-binding