9

What assurances do I have that a core constant expression (as in [expr.const].2) possibly containing constexpr function calls will actually be evaluated at compile time and on which conditions does this depend?

  1. The introduction of constexpr implicitly promises runtime performance improvements by moving computations into the translation stage (compile time).
  2. However, the standard does not (and presumably cannot) mandate what code a compiler produces. (See [expr.const] and [dcl.constexpr]).

These two points appear to be at odds with each other.

Under which circumstances can one rely on the compiler resolving a core constant expression (which might contain an arbitrarily complicated computation) at compile time rather than deferring it to runtime?

At least under -O0 gcc appears to actually emit code and call for a constexpr function. Under -O1 and up it doesn't.


Do we have to resort to trickery such as this, that forces the constexpr through the template system:

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) {}
}
bitmask
  • 32,434
  • 14
  • 99
  • 159
  • 2
    related/dupe: https://stackoverflow.com/questions/57084483/is-there-a-non-indirection-non-hack-way-to-guarantee-that-a-constexpr-function – NathanOliver Jun 11 '20 at 16:38
  • @NathanOliver It addresses the same problem, but doesn't directly answer the question. – bitmask Jun 11 '20 at 16:45
  • 3
    Possible duplicate of: https://stackoverflow.com/questions/14248235/when-does-a-constexpr-function-get-evaluated-at-compile-time – realestLink Jun 11 '20 at 16:45
  • Note C++20 has `consteval` that forces this per-function, so it can help in some cases if you want guarantees without altering use. – chris Jun 11 '20 at 16:48
  • @realestLink Interesting, but that answer asserts that it will be executed at compile time if its result is used in a constexpr. However there is zero evidence to support that claim (unless you count Herb Sutter's edit as official confirmation). – bitmask Jun 11 '20 at 16:50
  • @bitmask I'm not sure if there is anything in the standard. When you use a `constexpr` function in something that needs its value at compile time, then the compiler has to call it at compile time because it needs the value. Otherwise it's up to the compiler when it wants to do the call. – NathanOliver Jun 11 '20 at 16:52
  • Dup of [When does a constexpr function get evaluated at compile time?](https://stackoverflow.com/questions/14248235/when-does-a-constexpr-function-get-evaluated-at-compile-time) – Language Lawyer Jun 11 '20 at 16:56
  • @LanguageLawyer See my comment above. – bitmask Jun 11 '20 at 16:57
  • 1
    @bitmask Ok, how do you distinguish, using only the Standard, when something was evaluated during compile time and when it wasn't? – Language Lawyer Jun 11 '20 at 17:35
  • @LanguageLawyer That is a loaded question. You distinguish based on the generated executable. The standard is allowed to impose rules on the compiler's output, even it's diagnostic behaviour. – bitmask Jun 11 '20 at 17:38
  • @bitmask _The standard is allowed to impose rules on the compiler's output_ Interesting, but I'm not sure it does and the content of "generated executable" is somehow covered in the Standard. – Language Lawyer Jun 11 '20 at 17:40

2 Answers2

10

When a constexpr function is called and the output is assigned to a constexpr variable, it will always be run at compiletime.

Here's a minimal example:

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

When compiled at -O0, gcc outputs the following assembly for main:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 55
        mov     eax, 55
        pop     rbp
        ret

Despite all optimization being turned off, there's never any call to fib in the main function itself.

This applies going all the way back to C++11, however in C++11 the fib function would have to be re-written to use conversion to avoid the use of mutable variables.

Why does the compiler include the assembly for fib in the executable sometimes? A constexpr function can be used at runtime, and when invoked at runtime it will behave like a regular function.

Used properly, constexpr can provide some performance benefits in specific cases, but the push to make everything constexpr is more about writing code that the compiler can check for Undefined Behavior.

What's an example of constexpr providing performance benefits? When implementing a function like std::visit, you need to create a lookup table of function pointers. Creating the lookup table every time std::visit is called would be costly, and assigning the lookup table to a static local variable would still result in measurable overhead because the program has to check if that variable's been initialized every time the function is run.

Thankfully, you can make the lookup table constexpr, and the compiler will actually inline the lookup table into the assembly code for the function so that the contents of the lookup table is significantly more likely to be inside the instruction cache when std::visit is run.

Does C++20 provide any mechanisms for guaranteeing that something runs at compiletime?

If a function is consteval, then the standard specifies that every call to the function must produce a compile-time constant.

This can be trivially used to force the compile-time evaluation of any constexpr function:

template<class T>
consteval T run_at_compiletime(T value) {
    return value;
}

Anything given as a parameter to run_at_compiletime must be evaluated at compile-time:

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)); 
}
Alecto Irene Perez
  • 10,321
  • 23
  • 46
  • This seems to be an example of where it works as expected. The question wants to know when one can *rely* on this and some evidence for this (perhaps in form of an official document such as the C++ standard, compiler documentation, etc.). – bitmask Jun 11 '20 at 16:55
  • This works in every compiler I tested, including IBM PowerPC, gcc, clang, intel C++ compiler, and the MSVC C++ compiler. I've never seen a compiler *not* run it at compiletime, even cases where running the constexpr function is extremely expensive. Aside from that, why do you need a guarantee that this optimization occurs? – Alecto Irene Perez Jun 11 '20 at 17:05
  • 2
    "why do you need a guarantee that this optimization occurs?" I don't like to gamble. – bitmask Jun 11 '20 at 17:07
  • 1
    So I did some more research and `consteval` will do what you want. You can use it to write a function that forces any other `consteval` or `constexpr` function to run at compiletime. – Alecto Irene Perez Jun 11 '20 at 17:27
5

Never; the C++ standard permits almost the entire compilation to occur at "runtime". Some diagnostics have to be done at compile time, but nothing prevents insanity on the part of the compiler.

Your binary could be a copy of the compiler with your source code appended, and C++ wouldn't say the compiler did anything wrong.

What you are looking at is a QoI - Quality of Implrmentation - issue.

In practice, constexpr variables tend to be compile time computed, and template parameters are always compile time computed.

consteval can also be used to markup functions.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524