【问题标题】:Second standard conversion sequence of user-defined conversion自定义转换之二标准转换序列
【发布时间】:2014-11-16 05:53:26
【问题描述】:
我对标准转换序列术语有误解。我遇到过以下引用 N3797 §8.5.3/5 [dcl.init.ref]:
——如果初始化表达式
— 是一个 xvalue(但不是位域)、类纯右值、数组纯右值或
函数左值和“cv1 T1”与“cv2”引用兼容
T2”,或
——具有类类型(即 T2 是类类型),其中 T1 不是
与 T2 相关的引用,并且可以转换为 xvalue,类
prvalue,或“cv3 T3”类型的函数左值,其中“cv1 T1”是
与“cv3 T3”(参见 13.3.1.6)的引用兼容,
[..]
第二种情况,如果引用是右值引用和用户自定义转换的第二个标准转换序列
序列包括左值到右值的转换,程序是
格式不正确。
这里的秒标准转换顺序是什么?我认为有一个标准转换序列,包括所有必要的标准转换(用户定义的和隐式的)。
【问题讨论】:
标签:
c++
c++11
language-lawyer
implicit-conversion
overload-resolution
【解决方案1】:
[over.ics.user]:
用户定义的转换由初始标准转换序列和用户定义的转换 (12.3) 和 第二个标准转换序列组成。 […]
第二个标准转换序列转换结果
用户定义转换为序列的目标类型。
例如对于
struct A
{
operator int();
};
void foo(short);
foo(A());
第二个标准转换序列将intprvalue转换为foo的参数,即short。第一个标准转换序列将A对象转换为A(operator int的隐含对象参数),这构成了身份转换。
供参考,你引用的规则提供了一个例子:
struct X {
operator B();
operator int&();
} x;
int&& rri2 = X(); // error: lvalue-to-rvalue conversion applied to
// the result of operator int&
这里,operator int& 由重载决议选择。返回值是对int 的左值引用。为了初始化引用,需要进行左值到右值的转换,而这正是引用所阻止的:左值不应绑定到右值引用。
【解决方案2】:
标准转换这个术语的含义包含在 C++ 标准的以下条款中:
4 标准转换[conv]
-
标准转换是具有内置含义的隐式转换。第 4 条列举了所有此类转换。 标准转换序列是按以下顺序进行的标准转换序列:
- 从以下集合进行零次或一次转换:左值到右值转换,数组到指针转换,
以及函数到指针的转换。
- 来自以下集合的零个或一个转换:整数提升、浮点提升、整数转换、浮点转换、浮点整数转换、指针转换、指向成员的指针转换和布尔转换。
— 零个或一个资格转换。
[ 注意:一个标准的转换序列可以是空的,也就是说,它可以不包含任何转换。 — 尾注 ]
如果需要将标准转换序列应用于表达式以将其转换为所需的
目的地类型。
换句话说,标准转换是一组内置规则,编译器可以在将一种类型转换为另一种类型时应用。这些内置转换包括:
- 没有转化
- 左值到右值的转换
- 数组到指针的转换
- 函数到指针的转换
- 资格转换
- 整体促销
- 浮点推广
- 积分转换
- 浮点转换
- 浮点积分转换
- 指针转换
- 指向成员转化的指针
- 布尔转换
标准转换序列可以在用户定义的转换序列中出现两次 - 之前和/或之后 strong>用户自定义转换:
§ 13.3.3.1.2 用户定义的转换序列[over.ics.user]
用户定义的转换序列由初始标准转换序列、用户定义的转换 (12.3) 和第二个标准转换序列组成。如果用户定义的转换由构造函数 (12.3.1) 指定,则初始标准转换序列会将源类型转换为构造函数参数所需的类型。如果用户定义的转换由转换函数(12.3.2)指定,则初始标准转换序列将源类型转换为转换函数的隐式对象参数。
-
第二个标准转换序列将用户定义转换的结果转换为序列的目标类型。由于隐式转换序列是一种初始化,因此在为用户定义的转换序列选择最佳的用户定义转换时,适用于通过用户定义的转换进行初始化的特殊规则(参见 13.3.3 和 13.3.3.1)。
话虽如此,对于以下转换:
A a;
B b = a;
-
编译器将在B 中搜索转换构造函数,该构造函数可以通过一些初始标准转换获取A(源类型)的实例序列,以便它可以通过选定的构造函数执行该用户定义的转换,然后应用另一个标准转换 - 第二次标准转换 - 用于将用户定义的转换的结果类型转换为目标类型;
或:
编译器将在A 中搜索转换函数,该函数可在隐式上下文的一些初始标准转换序列之后调用,然后可以转换A 的实例可通过另一个标准转换 - 第二次标准转换 - 转换为目标类型 B。
作为一个具体的例子,让我们考虑以下转换:
struct A
{
operator int() const;
};
A a;
bool b = a;
编译器考虑以下用户定义的转换序列:
-
初始标准转换:资格转换 A* 到 const A* 调用 const-qualified operator int() const。
自定义转换:通过自定义转换函数将A转换为int。
第二个标准转换:布尔转换 int 到 bool。
您所询问的案例可以拆分如下:
struct A
{
operator int&();
};
int&& b = A();
-
源类型是
A。
-
目标类型是
int&&。
-
用户定义的转换顺序是
A到int&&的转换。
-
初始标准转换序列是无转换。
-
用户定义的转换是
A到int&的转换。
-
第二个标准转换序列(将用户定义转换的结果转换为目标类型),它是整个用户定义的转换序列将是
int&到int&&的标准转换 - 左值到右值的转换。由于int& 和int&& 是引用兼容 类型,因此考虑了这种转换。根据以下声明§8.5.3 [dcl.init.ref]/p5:
[...] 如果引用是右值引用并且用户定义的转换序列的第二个标准转换序列包括左值到右值的转换,则程序格式错误。
该转换不适用于整个用户定义的转换序列。