何时在编译时评估constexpr?

时间:2020-06-11 16:37:34

标签: c++ compilation language-lawyer constexpr c++20

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

  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) {}
}

2 个答案:

答案 0 :(得分:7)

调用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

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

这适用于一直返回到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_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)); 
}

答案 1 :(得分:4)

从不; C ++标准几乎允许整个编译在“运行时”进行。某些诊断必须在编译时完成,但是没有什么可以防止编译器方面的精神错乱。

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

您正在查看的是QoI-插图质量-问题。

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

consteval也可以用于标记功能。