【问题标题】:How to tell if `constexpr` is evaluated at compile time (without manual inspection)如何判断 `constexpr` 是否在编译时评估(无需手动检查)
【发布时间】:2019-02-04 07:40:43
【问题描述】:

有没有一种标准方法可以找出编译器对constexpr 函数做了什么?

(旁注:对于调试,默认情况下,每个 constexpr 函数都推迟到运行时。为什么这是明智的?有没有办法影响这一点?)

对于发布它取决于上下文。显然,对于小型测试设置,您可以轻松检查生成的机器代码,但这不是真正项目的方式。

我当前的“解决方法”(VC++)是在某个地方中断,转到我的 constexpr 函数并(尝试)检查反汇编。如果没有,我的结论是这一切都是在编译时完成的。 但这种方式并不是 100% 可靠的。 (优化等) 只有反过来是肯定的:如果我确实找到了反汇编(甚至可以在那里中断),我知道它不是在编译时完成的。

【问题讨论】:

  • 这是一个更好的问题:为什么需要?让编译器完成它的工作并优化它认为合适的地方。 constexpr 不是优化工具;它是一种工具,可让您在以前无法编写的地方编写代码。编写在需要常量表达式的上下文中生成常量表达式的代码。
  • 好吧,你可以试试强制,见“constexpr does not work/apply inside function call”。但最后,没有相当的方法来强制它,只是让大多数编译器不要这样做太复杂。除了运行时和评估编译的 cCode 之外,没有办法确定。
  • @Deduplicator 我知道我在不久前看到它,但找不到它。原来是同一个提问者。我想这会让我的答案过时了。
  • @old123987:“所以,是的,这是一种性能优化!”好吧,这不是该功能的用途。就像inline 并不意味着编译器会内联函数一样,constexpr 也不保证编译器只会在编译时评估函数。
  • 您自己here 似乎有一个几乎重复的问题。请说明您对接受的答案不满意的原因。

标签: c++ c++11 compilation c++17 constexpr


【解决方案1】:

这是不可能的。constexpr 不保证值内联,你可以在这里看到这个操纵优化级别:https://godbolt.org/z/dAoiM-

只有从 -O2 开始,所有内容都被内联并且结构被分解。在该编译器下方,即使对于 constexpr 上下文中使用的代码,编译器也会愉快地使用运行时评估。

没有标准的语言工具可以查询编译器是否应用了特定的优化。这一切都归结为as-if rule。如果代码行为相同,编译器可以对其做任何事情。唯一的例外是强制性 RVO 和其他 RVO(它们可以改变观察到的行为。)

话虽如此。 constexpr 是一个有用的提示。在链接的示例中,如果删除 constexpr 说明符,即使 O3(在最近的 clang 和 gcc 上)也无法删除地图。

编写constexpr 函数和数据结构在优化方面是值得的,确保编译器可以优化,尽管你不能强迫它。

您可以强制在 constexpr 上下文中评估函数,还可以保护要抛出的非 constexpr 路径,以防止有保证的运行时评估。

#include <iostream>
#include <vector>
using namespace std;

constexpr int f(int el) {
    return el > 0 ? el : throw "error";
}

int main() {
    // constexpr auto r = f(-1); // #1 compiler errors that throw is forbidden in  
                                 // constexpr, so it went into a non-constexpr path
                                 // and failed

    constexpr auto r = f(1);     // #2 fine - has to be interpreted in constexpr context
    cout << f(1) << '\n';        // #3 fine - can be interpreted in both contexts

    try {
        cout << f(-1) << '\n'; // # 4 // throws - i.e. runtime evaluation
    }
    catch (const char* e) {
        cout << e << '\n';
    }
    return 0;
}

【讨论】:

  • 那么你怎么知道,如果#3 在编译时真的被评估了。可能是,但这仍然取决于编译器。
  • 除了强制复制省略之外,您没有 C++ 标准对编译器强制执行的编译器保证,因此它始终取决于编译器。将f 赋值给constexpr 包装成lambda 并返回值,它强制执行constexpr 路径。但这只是constexpr 上下文。 FWIW 编译器可以根据“as-if”规则将虚假 NOP 和循环放入您的代码中以减慢它的速度,因此无法保证它会发出具体的程序集或使用特定的优化,并且没有语言级别的工具来检查它.
  • @old123987 顺便说一句,如果编译器必须能够在编译时评估返回值,并且在可以时没有这样做,那将是非常奇怪的。即使在 #2 的情况下,编译器也可以使用评估值或发出代码来计算它。将返回值绑定到constexpr 变量是为了确保您作为程序员使用可以在编译时评估的路径,但不会强制进行优化。在 #2 中,您知道如果代码编译编译器可以优化它,而不是它会。在#3中,您不知道是否可以。这是唯一的区别。
  • 您是否对测试进行了优化?因为很明显,很多编译器在没有优化的情况下只能保证可行性,但仍然推迟到运行时。
  • @Deduplicator 是的。 I did so on a slightly more complicated case, taken from SO. 确实同意你说的。只是因为-O2 一切都被内联并且地图被解散了。虽然constexpr 不保证任何事情。另一方面,如果您删除 constexpr 说明符,即使在 O3 clang 和 gcc 上也无法优化地图。所以constexpr 确实有助于优化器。这似乎是一个有用的提示,而不仅仅是语义更改器。 (有些人可能会争辩说,不管constexpr 是什么,编译器都会优化事情,但事实并非如此。)
【解决方案2】:

由于我们现在有当前的 C++20 标准,我们可以使用 consteval。

来自文档:

consteval - 指定一个函数是一个立即函数,也就是说,对函数的每次调用都必须产生一个编译时常量。 doc

这将解决 constexpr 的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-13
    • 1970-01-01
    • 2020-10-01
    • 2019-08-12
    • 2012-12-24
    • 2017-03-29
    • 1970-01-01
    • 2017-05-26
    相关资源
    最近更新 更多