为什么reinterpret_cast不是constexpr?

19

考虑以下代码片段:

static constexpr uint8_t a = 0;
static constexpr const int8_t *b = reinterpret_cast<const int8_t *>(&a);

由于C++标准禁止constexpr 中使用reinterpret_cast ,因此出现错误: error: a reinterpret_cast is not a constant expression

然而,如果我想要将值b存储在PROGMEM(针对AVR微控制器),编译则会成功:

static constexpr uint8_t a = 0;
static const int8_t PROGMEM *const b = reinterpret_cast<const int8_t *>(&a);
在这种情况下,编译器能够证明表达式reinterpret_cast<const int8_t *>(&a)是编译时常量,因为它将其结果(指向某个包含零的字节的地址)插入二进制程序空间中:
在此情况下,编译器能够证明表达式reinterpret_cast<const int8_t *>(&a)是编译时常量,因为它会将其结果(指向某个包含零的字节的地址)插入到程序空间中的二进制代码中。
_ZL1g:
  .zero   1
  .section        .progmem.data,"a",@progbits
  .type   _ZL1b, @object
  .size   _ZL1b, 2
_ZL1b:
  .word   _ZL1g

另外,我的理解是 reinterpret_cast 是一个编译时指令。那么为什么它不能在 constexpr 中使用呢?


1
不是非常好,但一个解决方法是从 void 类型转换... 所以 static_cast<uint8_t*>(static_cast<void*>(ptr)) - Treviño
2个回答

16

C++语言在运行时有"未定义行为"的概念。在某些(明确定义的)情况下,程序会出现未定义行为,这意味着它可以表现出任何行为:它可能崩溃、可能一直挂起、可能打印无意义的字符、可能看起来工作正常或者可以做任何事情。简单地说,造成这种情况的原因是性能。

在运行时,这是一种权衡(妥协),但在编译时是不可接受的。如果标准允许在编译时出现未定义行为,那么不仅可以在编译程序时获得崩溃,或者编译无限期,而且您永远无法确定已编译的可执行文件的有效性。

因此,任何形式的constexpr都必须100%没有未定义行为。毫不例外。没有任何余地。

reinterpret_cast是未定义行为的一个臭名昭著的源头。很少有有效使用reinterpret_cast,其中大多数结果都会导致未定义行为。此外,实际上不可能检查使用是否有效。因此,在编译过程中不允许使用reinterpret_cast,即在constexpr中不允许使用。


1
在运行时,reinterpret_cast 可能会触发未定义行为,但这并不意味着在编译时不能允许其使用的子集。 - Acorn
1
@Acorn,如果允许某些用途,这将使语言变得过于复杂,而且那些用途无论如何都只是所有有效情况的子集。委员会决定反对它,而是选择使用constexpr容器来解决几乎所有需要reinterpret_cast的问题。 - bolov
1
@Acorn:“像OP描述的这样的情况很容易允许。”如果你不是要在编译器中实现它们,那么我不会那么快地说任何这样的事情都是“容易”的。使用reinterpret_cast等效的操作足够简单,并且更易读。您可以根据需要在有符号和无符号之间进行静态转换。这样,当您将值从一种类型转换为另一种类型时,每个人都可以100%清楚,并且实现不必不断处理编译器应该使用哪种解释的问题。 - Nicol Bolas
1
@ConorTaylor:“不复制数据”是谁决定你所做的任何事情的必要限制?这只是一个字节;我认为你可以承受一个字节的复制。我的观点是,你可以更直接地实现相同的目标:当你想以不同的方式访问值时,只需复制它即可。 - Nicol Bolas
1
@NicolBolas 我发布的片段是问题的简化,实际对象比一个字节大得多。而且它旨在应用于嵌入式环境。因此,在我的情况下,这是必要的限制,但并不适用于constexpr reinterpret_cast问题,因为您当然可以进行static_cast并存储在lvalue中。 - Conor Taylor
显示剩余15条评论

2
那为什么它不能在constexpr中使用呢? 简单来说,标准不允许这样做。自C++11以来,constexpr一直是一个不断扩展的特性,因此自然而然地认为reinterpret_cast的子集使用可以起作用。 问题是:是否允许使用实际上有用或者会带来积极的危害。reinterpret_cast的用途非常少,特别是如果您编写和编译代码时假定严格的别名规则保持不变:很容易创建打破它的指针。 另一方面,显然对于嵌入式用户和专门的编译器/标志/环境,它可能在某种程度上是有用的。

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