让我们考虑以下代码:
#include <type_traits>
int foo(int arg) {
if (std::is_constant_evaluated()) {
return 1;
} else {
return 0;
}
}
int main() {
const auto b = foo(0);
return b;
}
Run Code Online (Sandbox Code Playgroud)
它用gcc 和 clang返回 0 。我原以为它会返回 1 。
如果foo()是 made constexpr, whileb被简单地保留const,那么它确实返回 1 。
我在这里缺少什么?谢谢!
std::is_constant_evaluated()当且仅当[meta.const.eval]返回真:
调用的求值发生在明显是常量求值的表达式或转换的求值中
这个术语,“明显的常量评估”(在此处定义),指的是必须进行常量评估的上下文。对非constexpr函数(这里最近的封闭上下文)的调用永远不会被评估为常量,因为它是 non- constexpr,所以这直接不是“明显的常量评估”。
constexpr但是,一旦我们成功了,我们就会陷入这种奇怪的遗产怪癖中。在引入 的 C++11 之前constexpr,我们仍然可以做这样的事情:
template <int I> void f();
const int i = 42; // const, not constexpr
f<i>(); // ok
Run Code Online (Sandbox Code Playgroud)
基本上,我们为声明为 const 且使用常量表达式初始化的特定整型(和枚举)类型进行了划分。那些仍然算作常量表达式。
所以这:
const auto b = foo(0);
Run Code Online (Sandbox Code Playgroud)
Iffoo(0)是一个完整的常量表达式,然后b是可以用作编译时常量的东西(如果它在命名空间范围内,则会被常量初始化†)。所以这里发生的是我们做一个两步解析。我们首先尝试将其foo(0)视为常量表达式进行评估,然后如果失败,则退回不这样做。
在第一个解析中,foo(0)计算为常量,is_constant_evaluated()is (根据定义) true,所以我们得到1. 这个解析成功了,所以我们最终得到了b一个编译时常量。
†对于命名空间范围的变量,常量初始化也是一个重要的概念 - 避免静态初始化顺序失败。它导致了其他粗糙的例子(见P0595)。
这里重要的事情基本上是:is_constant_evaluated()只应该打开以选择编译时安全算法与运行时算法,而不是实际影响结果的语义。
您必须小心使用的位置和方式is_constant_evaluated。C++中有3种函数,is_constant_evaluated只有其中一种才有意义。
// a strictly run-time function
int foo(int arg)
{
if (std::is_constant_evaluated()) // pointless: always false
// ...
}
// a strictly compile time function
consteval int foo(int arg)
{
if (std::is_constant_evaluated()) // pointless: always true
// ...
}
// both run-time and compile-time
constexpr int foo(int arg)
{
if (std::is_constant_evaluated()) // ok: depends on context in
// which `foo` is evaluated
// ...
}
Run Code Online (Sandbox Code Playgroud)
另一个值得指出的常见错误是,is_constant_evaluated在if constexpr条件中也没有任何意义:
{
if constexpr (std::is_constant_evaluated()) // pointless: always true
// regardless of whether foo
// is run-time or compile-time
}
Run Code Online (Sandbox Code Playgroud)