为什么不允许优化器在“恒定上下文”中折叠?

Tyk*_*ker 5 c++ optimization c++20

__builtin_is_constant_evaluated是用于std::is_constant_evaluated在clang和gcc的标准库中实现的内置函数。

在常量上下文中无效的代码通常也使优化程序难于常量折叠。

例如:

int f(int i) {
    if (__builtin_is_constant_evaluated())
        return 1;
    else {
        int* ptr = new int(1);
        int i = *ptr;
        delete ptr;
        return i;
    }
}
Run Code Online (Sandbox Code Playgroud)

发出gcc -O3为:

f(int):
        sub     rsp, 8
        mov     edi, 4
        call    operator new(unsigned long)
        mov     esi, 4
        mov     rdi, rax
        call    operator delete(void*, unsigned long)
        mov     eax, 1
        add     rsp, 8
        ret
Run Code Online (Sandbox Code Playgroud)

所以使用了优化器 __builtin_is_constant_evaluated() == 0

clang将其折叠为常数,但这是因为clang的优化器可以删除不需要的动态分配,而不是因为使用了__builtin_is_constant_evaluated() == 1

我知道这将__builtin_is_constant_evaluated()定义实现的返回值,因为优化因一个编译器而异。但是仅当两个路径具有相同的可观察行为时,才应该使用is_constant_evaluated。

如果无法折叠,为什么优化器不使用__builtin_is_constant_evaluated() == 1并回退__builtin_is_constant_evaluated() == 0

L. *_* F. 5

每个[meta.const.eval]

constexpr bool is_constant_evaluated() noexcept;
Run Code Online (Sandbox Code Playgroud)

返回: true当且仅当调用的评估发生在明显为常量评估的表达式或转换的评估之内([expr.const])。

f永远不能在常量求值的表达式或转换中调用,因此std::is_constant_evaluated()return false。这是由编译器决定的,与优化器无关。

当然,如果优化器可以证明分支是等效的,则它可以进行恒定折叠。但这毕竟是优化 –超出了C ++语言本身的范围。

但是为什么会这样呢?引入的建议std::is_constant_evaluatedP0595。它很好地解释了这个想法:

constexpr double power(double b, int x) {
  if (std::is_constant_evaluated() && x >= 0) {
    // A constant-evaluation context: Use a
    // constexpr-friendly algorithm.
    double r = 1.0, p = b;
    unsigned u = (unsigned)x;
    while (u != 0) {
      if (u & 1) r *= p;
      u /= 2;
      p *= p;
    }
    return r;
  } else {
    // Let the code generator figure it out.
    return std::pow(b, (double)x);
  }
}

// ...
double thousand() {
  return power(10.0, 3);  // (3)
}
Run Code Online (Sandbox Code Playgroud)

[...]

调用(3)是一个核心常量表达式,但是不需要在编译时对其进行求值的实现。因此,我们指定它导致std::is_constant_evaluated()产生false。人们很容易把它不确定是否truefalse在这种情况下产生,但引发显著语义的担忧:那么答案可能成为整个编制的各个阶段不一致。例如:

int *p, *invalid;
constexpr bool is_valid() {
  return std::is_constant_evaluated() ? true : p != invalid;
}
constexpr int get() { return is_valid() ? *p : abort(); }
Run Code Online (Sandbox Code Playgroud)

此示例尝试依靠constexpr评估检测到未定义行为的事实,以避免abort()在编译时对constexpr不友好的调用 。但是,如果std::is_constant_evaluated() 可以返回true,我们现在将面临一种情况,即绕过了重要的运行时检查。