什么时候在编译时评估 constexpr?

When is a constexpr evaluated at compile time?

我有什么保证 核心常量表达式(如 [expr.const].2)可能包含 constexpr 函数调用实际上将在编译时求值这取决于哪些条件?

  1. constexpr 的引入隐含地承诺通过将计算移至翻译阶段(编译时)来提高运行时性能。
  2. 但是,该标准没有(而且大概不能)强制要求编译器生成什么代码。 (参见 [expr.const] 和 [dcl.constexpr])。

这两点似乎相互矛盾。

在什么情况下可以依赖编译器在编译时解析核心常量表达式(可能包含任意复杂的计算)而不是推迟它到运行时?

至少在 -O0 下,gcc 似乎实际发出代码并调用 constexpr 函数。 -O1 及以上没有。


我们是否必须求助于 trickery 这样的强制 constexpr 通过模板系统的方法:

template <auto V>
struct compile_time_h { static constexpr auto value = V; };
template <auto V>
inline constexpr auto compile_time = compile_time_h<V>::value;

constexpr int f(int x) { return x; }

int main() {
  for (int x = 0; x < compile_time<f(42)>; ++x) {}
}

当调用 constexpr 函数并将输出分配给 constexpr 变量时,在编译时它将始终为 运行。

这是一个最小的例子:

// Compile with -std=c++14 or later
constexpr int fib(int n) {
    int f0 = 0;
    int f1 = 1;
    for(int i = 0; i < n; i++) {
        int hold = f0 + f1;
        f0 = f1;
        f1 = hold;
    }
    return f0; 
}

int main() {
    constexpr int blarg = fib(10);
    return blarg;
}

-O0 编译时,gcc 为 main 输出以下程序集:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 55
        mov     eax, 55
        pop     rbp
        ret

尽管所有优化都已关闭,但 main 函数本身从未调用过 fib

这适用于一直追溯到 C++11,但是在 C++11 中,必须重写 fib 函数以使用转换来避免使用 mutable 个变量。

为什么编译器有时会在 executable 中包含 fib 的程序集? 一个 constexpr 函数 可以 在 运行 时使用,并且在 运行 时调用时它的行为将像常规函数一样。

如果使用得当,constexpr 可以在特定情况下提供一些性能优势,但推动一切 constexpr 更多的是编写编译器可以检查未定义行为的代码。

什么是 constexpr 提供性能优势的示例? 在实现像 std::visit 这样的函数时,您需要创建一个查找 table的函数指针。每次调用 std::visit 时都创建查找 table 的成本很高,并且将查找 table 分配给 static 局部变量仍会导致可衡量的开销,因为程序必须每次函数 运行 时检查该变量是否已初始化。

值得庆幸的是,您可以进行查找 table constexpr,编译器实际上会 将查找 table 内联到函数的汇编代码中 ,因此当 std::visit 为 运行 时,查找 table 的内容更有可能位于指令缓存内。

C++20 是否提供任何机制来保证某些东西在编译时 运行s?

如果一个函数是consteval,那么标准规定每次调用该函数都必须产生一个编译时常量。

这可以简单地用于强制对任何 constexpr 函数进行编译时评估:

template<class T>
consteval T run_at_compiletime(T value) {
    return value;
}

作为参数给 run_at_compiletime 的任何东西都必须在编译时求值:

constexpr int fib(int n) {
    int f0 = 0;
    int f1 = 1;
    for(int i = 0; i < n; i++) {
        int hold = f0 + f1;
        f0 = f1;
        f1 = hold;
    }
    return f0; 
}

int main() {
    // fib(10) will definitely run at compile time
    return run_at_compiletime(fib(10)); 
}

从不; C++ 标准允许几乎整个编译发生在 "runtime"。一些诊断必须在编译时完成,但没有什么能阻止编译器的疯狂。

您的二进制文件可能是编译器的副本,附加了您的源代码,C++ 不会说编译器做错了什么。

您正在查看的是 QoI - 实施质量 - 问题。

实际上,constexpr 变量往往是在编译时计算的,而模板参数总是在编译时计算的。

consteval也可用于标记函数。