如果假设,即 [[assume]] 在常量表达式中失败,会发生什么?

Jan*_*tke 1 c++ language-lawyer constant-expression c++23 assumption

在 C++23 中,该[[assume(conditonal-expression)]]属性使得如果条件表达式的计算结果不为true,则行为未定义。例如:

int div(int x, int y) {
    [[assume(y == 1)]];
    return x / y;
}
Run Code Online (Sandbox Code Playgroud)

y这会编译成与始终相同的代码1

div(int, int):
        mov     eax, edi
        ret
Run Code Online (Sandbox Code Playgroud)

正如评论者所指出的,这不是必需的优化;这正是 GCC 碰巧处理的信息,除了y == 1UB 之外什么都没有。

编译器完全忽略所有假设是有效的。

但是常量表达式呢?

编译器需要诊断常量表达式1)中所有未定义的行为,但这合理吗?例如:

constexpr bool extremely_complicated(int x) {
    bool result;
    // 10,000 lines of math ...
    return result;
}

constexpr int div(int x, int y) {
    // This should result in a compiler error when the compiler is unable to prove
    // what extremely_complicated(x) returns.
    // extremely_complicated(x) is not evaluated, so it needs to be able to
    // prove the result without evaluating the function.
    [[assume(extremely_complicated(x))]];
    return x / y;
}

constexpr int quotient = div(4, 2);
Run Code Online (Sandbox Code Playgroud)

即使编译器无法证明假设是否会计算为,这仍然是一个问题吗?true?显然它不能解决停机问题。

假设和常量表达式究竟如何相互作用?[dcl.attr.assume]对此没有任何措辞。


1) 注意:extremely_complicated(x)不是常量表达式,但它位于一个假设中,该假设失败将导致 UB 位于 的常量求值内div(4, 2),这是一个常量表达式。一般认为,UB持续表达就需要诊断。

HTN*_*TNW 5

最终的 C++23 草案assume中有一个特定的例外。

[expr.const]/5.8(参考文献已破译)

表达式E是核心常量表达式E,除非 的计算遵循抽象机 ( ) 的规则[intro.execution],将计算以下其中一项:

  • ...
  • 具有[intro]通过[cpp]、排除中指定的未定义行为的操作[dcl.attr.assume]

因此编译器不需要为了判断表达式的恒定性而判断假设的准确性。如果你写

constexpr int g() {
    [[assume(false)]];
    return 5;
}
Run Code Online (Sandbox Code Playgroud)

theng()可能是也可能不是核心常量表达式(如果上下文中允许两者并且不返回,则未指定是否[[assume(E)]];取消表达式为常量的资格)。如果你进一步写Econstexprtrue

int main() {
    constexpr int x = g();
}
Run Code Online (Sandbox Code Playgroud)

有两种情况。如果实现已确定g()不是核心常量表达式(因为它可以自由地这样做),则需要给出诊断。如果实现已确定g()是核心常量表达式,则程序具有未定义的行为。

所以你看到编译器已经被淘汰了。上下文中的错误假设constexpr可能是未定义的行为,而不是实现选择的诊断。实现可以选择从不检查常量表达式中的假设。如果一个假设被证明是错误的,那么看似成功编译和运行的程序(如果不正确)只是所产生的未定义行为的表现。