prvalues的Cv资格(重访)

77
这是对我的之前的问题的跟进,显然共识是cv限定符的变化只是一个相当小的、不重要的变化,旨在解决一些不一致性(例如返回prvalues并声明了带有cv限定符的返回类型的函数)。
然而,我看到标准中另一个似乎依赖于prvalues具有cv限定类型的地方:通过临时材料化转换用prvalues初始化const引用。相关措辞可以在9.3.3/5中的多个位置找到。

[...] 如果转换后的初始值是一个prvalue,则其类型T4将调整为类型“cv1 T4”([conv.qual]),并应用临时材料化转换([conv.rval])[...]

[...] 否则,初始值表达式会被隐式转换为类型为“cv1 T1”的prvalue。应用临时材料化转换并将引用绑定到结果。

明显的意图是确保当我们到达实际的临时材料化转换时,
7.3.4 临时材料化转换
1. 类型为T的prvalue可以转换为类型为T的xvalue。此转换通过使用临时对象([class.temporary])初始化类型为T的prvalue,将prvalue评估为其结果对象,并生成表示临时对象的xvalue来完成。[...]
类型T作为输入接收到的cv限定包括所需的cv限定。
但在非类非数组prvalue的情况下,7.2.2/2中的cv限定如何保留?
类型2:如果prvalue最初具有类型“cv T”,其中T是cv未限定的非类非数组类型,则在进一步分析之前将表达式的类型调整为T。
或者不会吗?
例如,在此示例中,我们会得到什么样的临时对象?
const int &r = 42;

这个临时的{{const}}还是非{{const}}的?我们可以这样做吗?

const_cast<int &>(r) = 101; // Undefined or not?

不触发未定义行为?如果我没记错,最初的意图是在这种情况下获得一个const int临时对象。这仍然正确吗?(对于类类型,答案很明确-我们会得到一个const临时对象。)


9
在当前的草案中看起来有一个错误。我猜想推断临时对象7.3.4的类型的过程是在进行7.2.2调整之前使用prvalue类型执行的。否则,9.3.3/5就没有任何意义。 - Oliv
根据N4664中GB 19的评论,似乎意图是使临时对象即使是非类非数组类型也具有cv限定符。 - xskxzr
我认为在你的例子中没有必要使用一个临时变量,因为没有需要将类型T隐式转换为xvalue。此外,也许会发生复制省略。我认为它指的是当你CV限定的T引用了类似于函数返回类型为T的r值的xvalue时的情况。 - Gabriel Grigoras
2个回答

1
为什么你会怀疑7.2.2的语言?这似乎非常明确,cv限定符在非类、非数组prvalue上被丢弃,因此临时材料化中的类型T是非const、非volatile类型。 如果不是这种情况,那么你就不能将prvalue绑定到非const右值引用。然而,标准似乎旨在接受这样的程序。
#include <type_traits>

template<typename T> void
f(T &&t)
{
  static_assert(std::is_same_v<decltype(t), int&&>);
  ++t;
}

int
main()
{
  f(5);
}

1
例如,在这个例子中,我们得到了什么样的临时变量:const int &r = 42; 这个临时变量是const吗?
让我们分析一下您的示例,看看它是否是const。根据这个例子。
const int &r = 42;

标准中适用的措辞为[dcl.init.ref]/5

类型“cv1 T1”的引用通过以下方式由类型为“cv2 T2”的表达式初始化:
  • (5.1) [..]
  • (5.2) [..]
  • (5.3) 否则,如果初始化表达式
    • (5.3.1) 是rvalue(但不是位域)或函数lvalue,并且“cv1 T1”与“cv2 T2”兼容,或者
    • (5.3.2) [..]

那么 在第一种情况下,初始化表达式的值 和在第二种情况下转换的结果被称为转换后的初始化器。如果转换后的初始化器是prvalue,则其类型T4将调整为类型“cv1 T4”([conv.qual]),并应用临时材料化转换([conv.rval])。无论哪种情况,引用都绑定到生成的glvalue(或适当的基类子对象)

已知初始化表达式42是一个prvalue,而const int (cv1 T1)与int (cv2 T2)兼容。这里转换的初始化器是int (T4)类型;然后通过[conv.qual]进行调整为const int (cv1 T4)类型;然后应用临时材料化([conv.rval])。请注意,[expr.type]/2在此处不适用,因为prvalue的初始类型是cv-unqualified类型:

如果一个 prvalue 最初具有类型“cv T”,其中 T 是一个 cv-未限定的非类、非数组类型,则在进一步分析之前,表达式的类型将调整为 T
请注意,它不能应用于调整为 cv1 T4 的情况,因为 cv1 T4 不是 prvalue 的初始类型。
因此,调整后的 prvalue 具有类型 const intcv1 T4)。然后,对这个 prvalue 应用临时材料化;[conv.rval]

类型为 T 的 prvalue 可以转换为类型为 T 的 xvalue。此转换会初始化一个类型为 T 的临时对象([class.temporary])。

const int prvalue会被转换为类型为const int的xvalue。并且将初始化一个类型为const int的临时变量。因此,您有一个表示类型为const int的临时变量的xvalue。引用r绑定到结果glvalue(即,r绑定到表示类型为const int的临时变量的xvalue)。

据我所知,已创建的临时变量是const-qualified

我们能否执行const_cast<int &>(r) = 101;而不触发未定义行为?

不行,这是未定义行为,因为您正在尝试修改(写入)只读内存位置:

[expr.const.cast]/6: 根据对象的类型,通过指针、左值或数据成员指针进行的写操作,如果使用const_­cast去除const限定符,则可能导致未定义行为。 [dcl.type.cv]/4: 除了任何声明为mutable(7.1.1)的类成员可以被修改之外,在其生命周期(3.8)内尝试修改const对象都会导致未定义行为。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接