用户定义转换的第二个标准转换序列

Second standard conversion sequence of user-defined conversion

本文关键字:转换 标准 定义 用户 第二个      更新时间:2023-10-16

我对标准转换序列术语有一个误解。我看到了以下引用N3797§8.5.3/5[dcl.init.ref]:

如果初始值设定项表达式

是一个xvalue(但不是位字段)、类prvalue、数组prvalue或函数左值和">cv1T1"是与">cv2T2"或兼容的参考

--具有类类型(即,T2是类类型),其中T1不是与T2相关的引用,并且可以转换为xvalue、classprvalue,或类型为">cv3T3"的函数左值,其中">cv1T1"为与">cv3T3"兼容的参考(见13.3.1.6),

[..]在第二种情况下,如果引用是右值引用,并且是用户定义转换的第二个标准转换序列序列包括一个左值到右值的转换,程序是不正规。

这里的第二个标准转换序列是什么?我认为有一个标准转换序列,包括所有必要的标准转换(用户定义的和隐式的)。

[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的左值引用。为了初始化引用,需要进行左值到右值的转换,而这正是引号所阻止的:lvalue不应绑定到右值引用。

标准转换术语的含义包含在C++标准的以下条款中:

4标准转换[conv]

  1. 标准转换是具有内置含义的隐式转换。第4条列举了这类转换的全部内容。标准转换序列是以下顺序的标准转换序列:

    --从以下集合进行零或一次转换:左值到右值的转换,数组到指针的转换,以及函数到指针的转换。

    --以下集合的零或一转换:积分提升、浮点提升、积分转换、浮点转换、浮点积分转换、指针转换、指针到成员转换和布尔转换。

    --零个或一个资格转换。

    [注意:标准转换序列可以为空,也就是说,它可以不包含转换。-->em>结束注释]

如果需要,将标准转换序列应用于表达式,以将其转换为必需的目的地类型。

换句话说,标准转换是编译器在将一种类型转换为另一种类型时可以应用的一组内置规则。这些内置转换包括:

  • 没有转换
  • 左值到右值的转换
  • 数组到指针的转换
  • 函数到指针的转换
  • 资格转换
  • 整体促销
  • 浮点促销
  • 积分转换
  • 浮点转换
  • 浮点积分转换
  • 指针转换
  • 指向成员转换的指针
  • 布尔转换

标准转换序列可以在用户定义的转换序列-期间出现两次,在自定义转换之前和/或之后:

§13.3.3.1.2用户定义的转换序列[over.ics.user]

  1. 用户定义的转换序列由初始标准转换序列、用户定义的转化(12.3)和第二个标准转换序列组成。如果用户定义的转换是由构造函数指定的(12.3.1),则初始标准转换序列会将源类型转换为构造函数参数所需的类型。如果用户定义的转换是由转换函数(12.3.2)指定的,则初始标准转换序列会将源类型转换为转换函数的隐式对象参数。

  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;

编译器考虑以下用户定义的转换序列

  1. 初始标准转换:A*const A*的资格转换调用const-合格operator int() const

  2. 自定义转换:通过自定义转换函数A转换为int

  3. 第二个标准转换:intbool布尔转换

您询问的案例可以分为以下几种:

struct A
{
operator int&();
};
int&& b = A();
  • 源类型A
  • 目标类型int&&
  • 用户定义的转换序列Aint&&的转换
  • 初始标准转换序列根本没有转换
  • 用户定义的转换Aint&的转换
  • 第二个标准转换序列(将用户定义转换的结果转换为目标类型)是整体用户定义转换序列的一部分,此处为int&int&&标准转换-一个Lvalue到rvalue的转换。由于int&int&&引用兼容的类型,因此考虑了该转换。根据以下陈述§8.5.3[dcl.init.ref]/p5:

[…]如果引用是右值引用,并且用户定义的转换序列的第二个标准转换序列包括左值到右值的转换,则程序格式错误。

该转换不适用于整个用户定义的转换序列