【问题标题】:When is a constexpr evaluated at compile time?什么时候在编译时评估 constexpr?
【发布时间】:2020-10-01 09:08:22
【问题描述】:

我有什么保证,可能包含 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) {}
}

【问题讨论】:

  • @NathanOliver 它解决了同样的问题,但没有直接回答问题。
  • 注意 C++20 有 consteval 强制执行每个函数,因此在某些情况下,如果您想要保证而不改变使用,它可以提供帮助。
  • @bitmask 好的,你如何区分,仅使用标准,在编译时评估什么时候,什么时候没有?

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


【解决方案1】:

当调用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 函数必须重新编写以使用转换以避免使用可变变量。

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

【讨论】:

  • 这似乎是它按预期工作的一个例子。问题想知道何时可以依靠这一点以及一些证据(可能以官方文档的形式,如 C++ 标准、编译器文档等)。
  • 这适用于我测试的每个编译器,包括 IBM PowerPC、gcc、clang、intel C++ 编译器和 MSVC C++ 编译器。我从未见过编译器 not 在编译时运行它,即使在运行 constexpr 函数非常昂贵的情况下也是如此。除此之外,您为什么需要保证会发生这种优化?
  • “为什么需要保证这种优化发生?”我不喜欢赌博。
  • 所以我做了更多的研究,consteval 会做你想做的事。您可以使用它编写一个函数,强制任何其他 constevalconstexpr 函数在编译时运行。
【解决方案2】:

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

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

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

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

consteval也可用于标记函数。

【讨论】:

    猜你喜欢
    • 2016-08-30
    • 2012-12-24
    • 2017-05-26
    • 2021-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-04
    • 1970-01-01
    相关资源
    最近更新 更多