How to tell if `constexpr` is evaluated at compile

2020-05-06 22:46发布

问题:

Is there a standard way to find out what the compiler does to constexpr functions?

(Side note: For debug, every constexpr function is deferred to runtime by default. Why is this sensible? Is there a way to influence this?)

For release it depends on the context. Obviously, for small test settings you can easily inspect the generated machine code, but this cannot be the way to go for a real project.

My current 'workaround' (VC++) is to break somewhere, go to my constexpr function and (try to) inspect the disassembly. If none is there, I conclude that it was all done at compile time. But it is not 100% reliable this way. (Optimization, etc.) Only the other way around is certain: If I do find disassembly (and can even break there), I know that it was NOT done at compile time.

回答1:

It's impossible. constexpr does not guarantee value inlining, you can see this manipulating optimization level here: https://godbolt.org/z/dAoiM-

Only since -O2 everything is inlined and the structure gets dissolved. Below that compiler happily uses runtime evaluation even for code used in constexpr context.

There are no standard language tools to inquire whether compiler applies particular optimization. It all boils down to the as-if rule. If the code behaves the same compiler can do anything to it. The only exception is mandatory RVO and other RVOs (they are allowed to changed observed behaviour.)

That being said. The constexpr is a useful hint. In the linked example if one removes constexpr specifiers even O3 (on recent clang and gcc) does not manage to remove the map.

It's worthwhile optimization-wise to write constexpr functions and data structure, making sure the compiler can optimize, though you cannot force it to.

You can force function to be evaluated in constexpr context, and you can also guard non-constexpr paths to throw, to prevent guaranteed run-time evaluation.

#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;
}


回答2:

As we now have the current C++20 standard, we can use consteval.

From the docs:

consteval - specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant. doc

This will fix the problem with constexpr.