【问题标题】:A weird issue about converting a reference type to another by using operator reinterpret_cast关于使用运算符 reinterpret_cast 将引用类型转换为另一种的奇怪问题
【发布时间】:2020-12-23 13:01:57
【问题描述】:
#include <iostream>
int main(){
  int& rf = reinterpret_cast<int&>((int&&)1);
}

在此示例中,GCCMSVC 不接受此代码。相反,Clang 编译成功。结果是here

通过研究相关规则,对应的规则为:
expr.reinterpret.cast#11

如果可以使用 reinterpret_cast 将“指向 T1 的指针”类型的表达式显式转换为“指向 T2 的指针”类型,则可以将 T1 类型的泛左值(指定对象 x)强制转换为类型“对 T2 的引用” . 结果是*reinterpret_­cast&lt;T2 *&gt;(p) 的结果,其中 p 是指向“指向 T1 的指针”类型的 x 的指针。 没有创建临时,没有复制,也没有构造函数 ([class.ctor])或调用转换函数 ([class.conv])。

看看这条规则,我觉得这条规则太模糊了。它说结果是*reinterpret_­cast&lt;T2 *&gt;(p)。指针解引用规则定义为:
expr.unary#op-1

一元*运算符执行间接:应用它的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是一个左值引用该对象或表达式指向的函数。

所以,根据规则[expr.unary#op-1],*reinterpret_­cast&lt;T2 *&gt;(p)的值类别是左值。而规则[expr.reinterpret.cast#11]只要求源指针类型可以通过reinterpret_cast转换为目的指针类型,对其操作数没有值类别等其他要求。从这一点来看,我认为 Clang 给出了一个正确的过程。因为结果是左值,所以可以绑定左值引用。

现在,对第一个例子稍作修改

#include <iostream>
int main(){
  int& rf = reinterpret_cast<int&>(1);
}

那么,three compilers 都拒绝这个例子。什么鬼?与第一个示例的不同之处在于reinterpret_cast 的操作数这里是prvalue。只是,这种差异使Clang 不接受此代码?根据expr#10

当一个纯右值表达式作为一个操作数的操作数出现时,该操作数需要一个泛左值,临时实现转换将应用于将该表达式转换为一个 xvalue。

如上述规则中所述,临时实现转换应应用于prvalue 1,使其成为glvalue。经过这个转换,我看不出这个例子和第一个例子有什么不同。为什么Clang 现在拒绝它?

让我们继续考虑第三个例子

#include <iostream>
int main(){
  int&& rf = reinterpret_cast<int&&>(1);
}

这次GCC接受这个例子,而另外两个编译器报错,result就在这里。

如果将声明更改为int&amp;&amp; rf = reinterpret_cast&lt;int&amp;&amp;&gt;((int&amp;&amp;)1);,那么Clang 将接受。

另外,[expr.reinterpret.cast#11] 说过使用reinterpret_cast 将引用类型转换为另一个的结果总是一个左值 (*reinterpret_­cast&lt;T2 *&gt;(p)),它暗示我们不能使用右值引用类型来绑定结果。不是吗?

这些例子是我在阅读 [expr.reinterpret.cast#11] 时想到的。不同的编译器给出非常不同的结果。有点奇怪。我还认为 [expr.reinterpret.cast#11] 给出了非常模糊的措辞。当我们给出右值引用或左值引用类型时,并没有说明操作数的值类别的要求。它只是说只要这些指针类型可以转换。该规则也没有说明结果是什么价值类别。我们只能通过表达式*reinterpret_­cast&lt;T2 *&gt;(p)来推断它的值类别是左值。

如何解释这些问题。是不是[expr.reinterpret.cast#11]的缺陷?

【问题讨论】:

    标签: c++ pointers language-lawyer


    【解决方案1】:

    所以,根据规则[expr.unary#op-1],*reinterpret_­cast&lt;T2 *&gt;(p)的值类别是左值。

    不,因为[expr.reinterpret.cast]/11 并没有说引用转换表达式在各个方面都等同于*reinterpret_cast&lt;T2*&gt;(p)。它表示表达式的 result 与该表达式的相同。这里的“结果”指的是[basic.lval]/5

    glvalue 的 result 是表达式所表示的实体。

    reinterpret_cast表达式的值类别仅由[expr.reinterpret.cast]/1定义,

    表达式reinterpret_­cast&lt;T&gt;(v) 的结果是将表达式v 转换为类型T 的结果。如果T 是左值引用类型或对函数类型的右值引用,则结果为左值;如果T 是对对象类型的右值引用,则结果是一个xvalue;否则,结果是纯右值和 ....

    所以reinterpret_cast&lt;int&amp;&gt;((int&amp;&amp;)1) 具有值类别左值,因为类型int&amp; 是左值引用类型,而不是因为一元* 规则。其结果是prvalue1的临时物化创建的对象。

    reinterpret_cast&lt;int&amp;&gt;(1) ... 临时实现转换应适用于prvalue 1,使其成为glvalue

    否 - [basic.reinterpret.cast] 中唯一可能描述reinterpret_cast&lt;int&amp;&amp;&gt;(1) 的部分将再次是paragraph 11,用于将glvalue 转换为引用类型。但它的最后一句包含“没有创建临时”,因此不允许临时实现。

    所以reinterpret_cast&lt;int&amp;&gt;(1)reinterpret_cast&lt;int&amp;&amp;&gt;(1) 都是格式错误的,gcc 在接受reinterpret_cast&lt;int&amp;&amp;&gt;(1) 而没有任何错误或警告方面是不正确的。

    【讨论】:

    • “我认为意味着临时实现转换永远不会应用于 reinterpret_cast 的操作数”。我不这么认为。正如第 1 段所说,价值类别由目的地类型决定。 otherwise 之后的措辞的意思是,如果reinterpret_cast 的结果是prvalue,那么只有这些列出的转换可以应用于表达式v。它并不是说在任何情况下只有这些列出的转换可以应用于表达式v。毕竟这些列出的转换函数都是用来读取value形成prvalue的。
    • 我同意这并不完全清楚。但有趣的是,第 1 段特别提到了通常的三个 glvalue-to-prvalue 转换,但没有提到通常的 prvalue-to-xvalue 转换。
    • 我认为它应该被视为一个措辞缺陷。从Clang的行为来看,你的意见是对的。但是,老实说,我没有阅读第 1 段中的微妙之处(作为您的解释)。它既没有说允许或禁止临时实现转换。作为一条通用规则,“只要纯右值表达式作为操作数的操作数出现,该操作数需要一个左值”。因此,我不知道规则 [expr.reinterpret.cast#11] 是说它是否需要一个左值还是原始表达式应该是一个左值?这里的正确意图是什么?这里很模糊。
    • 哦,等等。第 11 段说“没有临时创建”。这比我引用的其他所有内容都更相关,我会说绝对排除临时实现。
    • 谢谢。我认为临时意味着两种不同类型之间,reinterprest_cast 不能调用任何用户定义的转换来创建目标类型临时对象。想必,临时的就是你的意思。总之,圣诞快乐。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-02
    • 1970-01-01
    • 2012-03-28
    • 1970-01-01
    • 1970-01-01
    • 2017-08-28
    • 1970-01-01
    相关资源
    最近更新 更多