什么时候在编译时对constexpr进行评估?

问题描述 投票:3回答:1

我如何保证一个 核心常数 (如[expr.const].2中的)可能包含constexpr函数调用,在编译时是否会被实际执行,这取决于哪些条件?

  1. 引入 constexpr 隐含地承诺通过将计算移入DeepL 翻译(编译时)来提高运行时性能。
  2. 然而,标准并没有(大概也不能)规定编译器生成什么代码。参见 [expr.const] 和 [dcl.constexpr])。

这两点似乎是相互矛盾的。

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

至少在 -O0 gcc似乎真的发出代码并调用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) {}
}
c++ compilation language-lawyer constexpr c++20
1个回答
5
投票

当一个 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

尽管所有的优化都被关闭了,但从来没有任何调用到 fibmain 函数本身。

这适用于所有的方式来追溯到 C++11然而,在C++11中 fib 函数必须重新编写,以使用转换来避免使用可变变量。

为什么编译器包含了 fib 在可执行文件中,有时? A 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_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)); 
}

4
投票

从来没有;C++标准允许几乎整个编译过程都在 "运行时 "进行。 有些诊断必须在编译时进行,但没有什么能阻止编译器的疯狂。

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

你所看到的是一个QoI--Implrmentation的质量问题。

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

consteval 也可以用来标记函数。

© www.soinside.com 2019 - 2024. All rights reserved.