使用 g++ 编译器,这两个都可以编译:

constexpr double d1 = 2.0 / 1.0;
const double d2 = 2.0 / 0.0;

但事实并非如此:

constexpr double d2 = 2.0 / 0.0;

编译时错误是:

error: ‘(2.0e+0 / 0.0)’ is not a constant expression

请注意,,确认这是不允许的。

我不明白为什么不允许这样做。请记住,constexpr,编译器在编译时清楚地知道在 内constexpr是否允许除以零。那么,在允许的情况下……为什么在 内不允许呢constexpr

编辑:有人认为这个问题类似于。我在这里的问题旨在更加集中(为什么前两个表达式似乎被允许,但第三个表达式却不允许)?另一个问题的答案似乎并没有阐明这一点:一些答案似乎暗示所有三个表达式都是 UB(因此是不允许的?我不认为我相信这一点;但无论如何,constexpr 似乎有一些特定的东西会产生影响,因此这个问题的重点是 constexpr)。

这个问题也类似于其接受的答案(本质上“它是 UB 所以任何事情都可以”)根本没有帮助。

24

  • 2
    在 C++ 中,除以零是未定义的行为,即使您的编译器使用定义此类行为的 IEEE754。


    – 

  • 11
    我无法说出 cppreference 的意思,但 C++ 标准明确指出这是未定义的行为,没有条件。eel.is/c++draft/expr.mul#4 常量表达式中不允许使用 UB。


    – 

  • 4
    @DonHatch 链接的答案指的是C文档。我猜这是 C++ 和 C 分歧的地方。


    – 


  • 1
    @DonHatch Richard 注意到,你找到了一个 C 语言页面。C 说“如果rhs为零,则行为未定义” – 然后它继续解释除以零的行为,如果std::numeric_limits::is_iec559为真… 我不确定这里的规则是什么,但规则是,你的编译器正确地拒绝了 constexpr 中的除以零。


    – 

  • 2
    另外:“7.7 常量表达式… 5 表达式 E 是核心常量表达式,除非对 E 的求值遵循抽象机规则 (6.9.1),将求出下列之一:… (5.7) — 具有未定义行为的操作,如本文档第 4 至第 15 条所述 [注:包括例如有符号整数溢出 (7.2)、某些指针算法 (7.6.6)、除以零 (7.6.5) 或某些移位运算 (7.6.7) — 结束注释];” 非常明确


    – 


最佳答案
1

ISO C++ 标准明确规定,对于整数和浮点类型来说,除以零都有未定义的行为。

参见

如果根据核心语言规范,表达式的求值具有未定义的行为,则其求值涉及该求值的表达式将被指定为不是(核心)常量表达式。

因此,在编译时任何除以零都是不可能的。

尽管按照 C++ 标准,除以零是未定义的,但浮点类型的实现通常遵循 IEEE 754,该标准定义了除以零的结果。这当然没问题。如果程序根据 C++ 标准具有未定义的行为,那么编译器可以自由地为其赋予任何语义。特别是它可以承诺遵守更严格的规则,例如另一个规范。

然而,在常量表达式中,语言被有意限制为标准明确定义的内容。编译器无法constexpr像在运行时那样扩展行为。如果constexpr变量的初始化不是常量表达式,则程序格式不正确。

虽然标准从未要求编译器编译失败。如果程序格式不正确,那么唯一的要求是编译器应发出一个诊断。这也可能只是一个警告,然后编译器可以简单地在编译时执行除法,就像在运行时一样。

在某些情况下,程序的语义不仅受格式正确性的影响,还受表达式是否为常量表达式的影响。在这种情况下,编译器需要为程序提供正确的语义。在这种情况下,警告和不同的行为是不可能的。

2

  • 1
    它不是常量表达式这一事实可以被格式良好的 SFINAE 程序利用。符合规范的编译器可以允许它,否则会发出警告(GCC 会针对某些形式上缩小但显然安全的转换执行此操作),但这种不一致可能会令人恐惧。


    – 

  • “那当然没问题。” 🙂 这实际上是一个关键点,而且根本没有被普遍理解。常见的(错误)理解似乎是,这实际上并不好——也就是说,UB 意味着符合规范的编译器无法保证更严格的规范。因此,许多关于 UB 的对话过早地以“这是 UB,所以什么都可以”结束,这非常无益 感谢您更细致入微的解释;接受。


    –