何时在编译时评估 constexpr?

bit*_*ask 9 c++ compilation language-lawyer constexpr c++20

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

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

这两点似乎相互矛盾。

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

至少在-O0gcc下似乎实际上发出代码并调用 constexpr 函数。在-O1最多不。


难道我们不得不求助于挂羊头卖狗肉,如这个,那个力量通过模板系统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) {}
}
Run Code Online (Sandbox Code Playgroud)

J. *_*rez 8

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;
}
Run Code Online (Sandbox Code Playgroud)

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

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 55
        mov     eax, 55
        pop     rbp
        ret
Run Code Online (Sandbox Code Playgroud)

尽管关闭了所有优化,但函数本身从未调用fibmain

这适用于一直回到C++11,但是在 C++11 中fib,必须重新编写该函数以使用转换以避免使用可变变量。

为什么编译器fib有时会在可执行文件中包含程序集?一个constexpr函数可以在运行时使用,当在运行时调用时,它的行为就像一个普通函数。

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

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

值得庆幸的是,您可以创建查找表constexpr,编译器实际上会将查找表内联到函数的汇编代码中,以便在运行时查找表的内容更有可能位于指令缓存中std::visit

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

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

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

template<class T>
consteval T run_at_compiletime(T value) {
    return value;
}
Run Code Online (Sandbox Code Playgroud)

作为参数给出的任何东西都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)); 
}
Run Code Online (Sandbox Code Playgroud)

  • “为什么需要保证这种优化的发生?” 我不喜欢赌博。 (2认同)

Yak*_*ont 5

绝不; C++ 标准允许几乎整个编译发生在“运行时”。一些诊断必须在编译时完成,但没有什么可以阻止编译器的疯狂行为。

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

您看到的是 QoI(实施质量)问题。

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

consteval也可用于标记函数。