【问题标题】:std::is_constant_evaluated behaviorstd::is_constant_evaluate 行为
【发布时间】:2019-06-12 13:46:51
【问题描述】:

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 (std::is_constant_evaluated()) 始终评估 true 分支。因此,使用这种结构是没有意义的。

  • 如果编译器在编译时计算一个变量, std::is_constant_evaluated())true,不管那个 变量是否显式注释constexpr

我说的对吗?

【问题讨论】:

  • 您为此使用了哪些头文件和编译器选项?
  • @P.W 标头<type_traits> 和编译器选项-std=c++2a

标签: c++ constexpr c++20 if-constexpr


【解决方案1】:

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

它适用于普通的if。目的是在常量表达式中求值时不进入 constexpr 函数中非法的代码路径。但是让它在运行时执行。它并不是要从函数中完全消除这些代码路径。

【讨论】:

  • constexpr 函数中可能存在无法在编译时评估的代码路径?我还有很多东西要学:)
  • @user463035818 constexpr 表示“此函数存在可能允许编译时评估的参数”。这并不意味着“此函数将始终在编译时进行评估”。这意味着例如std::abs 可以是 constexpr:当使用 constexpr 参数调用时,它可以在编译时进行评估,但在运行时仍然可以正常使用。
  • @MaxLanghof 但是你真的可以实现一个constexpr 函数,其逻辑永远不会在编译时执行吗?我认为这就是 user463035818 的意思。我也很惊讶。函数模板的东西显然不同。我认为相关的示例 - 无论您在何处以及如何调用它,您都不能拥有一个将返回 std::stringconstexpr 函数。
  • @MaxLanghof 啊,谢谢,明白了。如果您传递非编译时参数,则要求可以在编译时评估函数是没有意义的
  • 我希望编译器在生成函数的运行时版本时优化掉常量评估代码路径,这无论如何都可能发生。所以if constexpr 对我来说很有意义。
【解决方案2】:

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


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

基本上,控制流(从代码路径的角度来看)在“完整运行时”模式和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 是一种不幸的语言特例——你可以使用常量整数变量作为常量表达式,因此我们以同样的方式处理它们的初始化。)

【讨论】:

  • 感谢您的详细解释。我对test6 的情况很好。这样做是很合乎逻辑的。我发现if constexpr 的情况更违反直觉。
  • @metalfox 我怀疑这将是一个非常普遍的误解。我将在答案中添加注释
  • GCC 10+ 将警告 if constexpr 案例:gcc.gnu.org/ml/gcc-patches/2019-08/msg01841.html
  • 常量整数的情况更奇怪,因为它们可以是常量表达式,但不一定是。 (当然,任何动态初始化都可以静态完成,但这通常是不可观察的。)
  • GCC 91428 说 is_constant_evaluate 可能会被删除。会被什么取代?
猜你喜欢
  • 2020-11-02
  • 2020-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-02
  • 2012-04-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多