22

Why is a constexpr function no evaluated at compile time but in runtime in the return statement of main function?

It tried

template<int x>
constexpr int fac() {
    return fac<x - 1>() * x; 
} 

template<>
constexpr int fac<1>() {
    return 1; 
} 

int main() {
    const int x = fac<3>();
    return x;
} 

and the result is

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

with gcc 8.2. But when I call the function in the return statement

template<int x>
constexpr int fac() {
    return fac<x - 1>() * x; 
} 

template<>
constexpr int fac<1>() {
    return 1; 
} 

int main() {
    return fac<3>();
} 

I get

int fac<1>():
        push    rbp
        mov     rbp, rsp
        mov     eax, 1
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        call    int fac<3>()
        nop
        pop     rbp
        ret
int fac<2>():
        push    rbp
        mov     rbp, rsp
        call    int fac<1>()
        add     eax, eax
        pop     rbp
        ret
int fac<3>():
        push    rbp
        mov     rbp, rsp
        call    int fac<2>()
        mov     edx, eax
        mov     eax, edx
        add     eax, eax
        add     eax, edx
        pop     rbp
        ret

Why is the first code evaluated at compile time and the second at runtime?

Also I tried both snippets with clang 7.0.0 and they are evaluated at runtime. Why is this not valid constexpr for clang?

All evaluation was done in godbolt compiler explorer.

Thomas Sablik
  • 16,127
  • 7
  • 34
  • 62
  • 18
    You should use `-O3` for optimal behaviour, the question is meaningless without it. As posted the question is really about "why does the compiler without optimization do this thing" to which the answer is usually "to make debugging easier" – M.M Jan 14 '19 at 12:47
  • 6
    And both compilers in question do exactly what you would expect with optimizations enabled: https://godbolt.org/z/cCbFDX – Max Langhof Jan 14 '19 at 12:49
  • 17
    `constexpr` exists to let you write natural code for producing constant expressions in contexts that need them. It's not a "optimize this to heck always" hint to the compiler. It's best not to confuse it for one. – StoryTeller - Unslander Monica Jan 14 '19 at 12:51
  • Always turn on optimizations, regardless of the compiler. Too many questions on "why is C++ slow compared to ", or "why C++ generates this code when ...", get closed due to not testing or building with optimizations turned on. You should also post the compiler options used to test the code. – PaulMcKenzie Jan 14 '19 at 12:54
  • @PaulMcKenzie: I didn't use any options since I didn't read that optimization is necessary for `constexpr`. With `-O3` it works as expected. – Thomas Sablik Jan 14 '19 at 12:57
  • Optimisation is not "necessary for `constexpr`". See StoryTeller's comment. You're misunderstanding what `constexpr` means. – Lightness Races in Orbit Jan 14 '19 at 13:01
  • 2
    @StoryTeller that comment helped me more than thousands books. I think you should make it an answer. I always thought `constexpr` is to tell the compiler "this should be evaluated at compile time", while I now understand that it is rather "this must be evaluatable at compile time". Suddenly the confusion about whether it does get evaluated at compile time or not feels much less severe. – 463035818_is_not_an_ai Jan 14 '19 at 13:02
  • 1
    It's worth mentioning that in this particular example there is no need to pass `x` as a template parameter. An "ordinary" parameter will suffice here. So, you can use `constexpr` functions as a more readable replacement for some old style meta-programming-template-code that previously had to be used to generate compile time integers. – sebrockm Jan 14 '19 at 16:01
  • A good compiler hasn't, but should :/ – dgrat Jan 15 '19 at 08:43

2 Answers2

29

A common misconception with regard to constexpr is that it means "this will be evaluated at compile time"1.

It is not. constexpr was introduced to let us write natural code that may produce constant expressions in contexts that need them. It means "this must be evaluatable at compile time", which is what the compiler will check.

So if you wrote a constexpr function returning an int, you can use it to calculate a template argument, an initializer for a constexpr variable (also const if it's an integral type) or an array size. You can use the function to obtain natural, declarative, readable code instead of the old meta-programming tricks one needed to resort to in the past.

But a constexpr function is still a regular function. The constexpr specifier doesn't mean a compiler has2 to optimize it to heck and do constant folding at compile time. It's best not to confuse it for such a hint.


1 - Thanks user463035818 for the phrasing.
2 - and consteval is a different story however :)

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    Actually, it means "This must be evaluatable at compile time for at least one input (arguments and template-arguments).", which is weaker still. Yes, that should hold for any useful input, but expectations are often disappointed. – Deduplicator Jan 14 '19 at 20:54
  • Do you know why `constexpr` is even necessary? Why can't the compiler just assume everything has `constexpr` in front of it? – user541686 Jan 14 '19 at 22:05
  • @Mehrdad - It's by no means authoritative, just my 2c. The standard prohibits certain constructs in constexpr functions, less so now than in C++11, but still. I guess for ease translation, and for keeping programmers sane, it's better to make it an opt-in feature. Which is kinda also inline with the general philosophy of C++. – StoryTeller - Unslander Monica Jan 15 '19 at 07:07
  • 1
    @StoryTeller-UnslanderMonica Not a misconception. Bjarne Stroustrup in Section 1.7 of "A tour of C++" says: " constexpr: meaning roughly "to be evaluated at compile time." " – doubleE Sep 29 '20 at 20:30
  • 1
    @doubleE - "A tour of C++" is *not* the standard. And with all due respect to Bjarne, `consteval` was added for a reason. So yes, **a misconception** – StoryTeller - Unslander Monica Sep 29 '20 at 20:46
14

StoryTeller's answer is good, but I think there's a slightly different take possible.

With constexpr, there are three situations to distinguish:

  1. The result is needed in a compile-time context, such as array sizes. In this case, the arguments too must be known at compile time. Evaluation is probably at compile time, and at least all diagnosable errors will be found at compile time.

  2. The arguments are only known at run time, and the result is not needed at compile time. In this case, evaluation necessarily has to happen at run time.

  3. The arguments may be available at compile time, but the result is needed only at run time.

The fourth combination (arguments available only at runtime, result needed at compile time) is an error; the compiler will reject such code.

Now in cases 1 and 3 the calculation could happen at compile time, as all inputs are available. But to facilitate case 2, the compiler must be able to create a run-time version, and it may decide to use this variant in the other cases as well - if it can.

E.g. some compilers internally support variable-sized arrays, so even while the language requires compile-time array bounds, the implementation may decide not to.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Shouldn't case 2 lead to a compilation error instead? The `constexpr` indicates that the expression must be evaluable at compile time. – fishinear Jan 14 '19 at 18:03
  • 1
    @fishinear No. The answer is referring to a the uses of a `constexpr` function. E.g. if I have `constexpr int factorial(int n)`, and I have an `int input` I only know at runtime, `int output = factorial(input);` is perfectly fine. The error would be claiming `constexpr int output = factorial(input);`, because `input` is not `constexpr`, so neither can `output` be `constexpr`. – HTNW Jan 14 '19 at 18:32