std::is_constant_evaluated 行为

std::is_constant_evaluated behavior

GCC9 已经实现 std::is_constant_evaluated。我玩了一点,我意识到它有点棘手。这是我的测试:

constexpr int Fn1()
{
  if constexpr (std::is_constant_evaluated())
    return 0;
  else
    return 1;
}

constexpr int Fn2()
{
  if (std::is_constant_evaluated())
    return 0;
  else
    return 1;
}

int main()
{
  constexpr int test1 = Fn1(); // Evaluates to 0
  int test2 = Fn1();           // Evaluates to 0
  int const test3 = Fn1();     // Evaluates to 0

  constexpr int test4 = Fn2(); // Evaluates to 0
  int test5 = Fn2();           // Evaluates to 1
  int const test6 = Fn2();     // Evaluates to 0
}

根据这些结果,我提炼出以下结论:

我说得对吗?

if constexpr 需要条件的常量表达式。所以 is_constant_evaluated 在这种情况下当然总是正确的。

它适用于常规 if。目的是在常量表达式中计算时不进入 constexpr 函数中非法的代码路径。而是让它在运行时执行。它不是为了从函数中完全消除这些代码路径。

以下是我对此的看法,也许您会发现这有帮助……也许不会。请注意,我认为写 if constexpr (std::is_constant_evaluated()) 将是一个非常常见的错误,而且很容易掉入陷阱。但希望编译器能够诊断出这种情况。已提交 91428,已为 gcc 10.1 修复。


我们基本上有两种不同的代码规则 - 正常运行时代码的典型规则,以及用于 constexpr 编程的常量表达式的限制。这些是 expr.const 限制:没有 UB,没有 reinterpret_cast,等等。这些限制从语言标准到语言标准不断减少,这很好。

基本上,控制流(从代码路径的角度来看)在 "full runtime" 模式和 constexpr 模式之间交替。一旦我们进入 constexpr 模式(无论是通过初始化 constexpr 对象还是评估模板参数或...),我们会一直呆在那里直到完成...然后我们回到完整的运行时模式。

is_constant_evaluated() 的作用很简单:我是否处于 constexpr 模式?它会告诉您是否处于需要常量表达式的上下文中。

在那个视图中,让我们看看 if constexpr (is_constant_evaluated())。不管我们以前处于什么状态,if constexpr 都需要一个常量表达式作为它的初始化,所以如果我们还没有,这会将我们提升到 constexpr 模式。因此,is_constant_evaluated() 是正确的 - 无条件地。

然而,对于 if (is_constant_evaluated()),一个简单的 if 不会改变我们在运行时和 constexpr 之间的状态。所以这里的值取决于调用它的上下文。初始化 test4 使我们进入 constexpr 模式,因为它是一个 constexpr 对象。在其初始化期间,我们遵循常量表达式规则...因此 is_constant_evaluated() 为真。但是一旦我们完成了,我们就回到了运行时规则……所以在 test5 的初始化中,is_constant_evaluated() 是假的。 (然后 test6 是一个不幸的语言特例 - 您可以使用常量整数变量作为常量表达式,因此我们出于这些目的以相同的方式处理它们的初始化。)