3

I am reading an example of cppreference at https://en.cppreference.com/w/cpp/language/constant_expression

Specifically, this one:

constexpr int incr(int& n)
{
    return ++n;
}
 
constexpr int g(int k)
{
    constexpr int x = incr(k); // error: incr(k) is not a core constant
                               // expression because lifetime of k
                               // began outside the expression incr(k)
    return x;
}
 
constexpr int h(int k)
{
    int x = incr(k); // OK: x is not required to be initialized
                     // with a core constant expression
    return x;
}
 
constexpr int y = h(1); // OK: initializes y with the value 2
                        // h(1) is a core constant expression because
                        // the lifetime of k begins inside the expression h(1)

So, as far as I can tell, the issue with this code:

constexpr int x = incr(k);

is that it goes against this core expression rule: "modification of an object, unless the object has non-volatile literal type and its lifetime began within the evaluation of the expression".

But why does this instead work, even if the compiler evaluates it at compile-time the same way? (We can check this with std::is_constant_evaluated()) Isn't the compiler going against the "core expression rules" by evaluating this at compile time anyway?

int x = incr(k); // k lifetime didn't start in this expression, so incr should not be able to mutate it since the "core constant expression" rules disallow it
sweenish
  • 4,793
  • 3
  • 12
  • 23
  • 2
    i suppose it boils down to: `constexpr` does not mean that it will in all cases be evaluated at compile time. It just means it can. And that applies to all `constexpr` individually. If `g` is executed at runtime then `x` inside `g` is still `constexpr` – 463035818_is_not_an_ai Mar 28 '23 at 11:31
  • The problem is you need to guarantee `incr(k)` is a constant in all cases if you use it to initialize a `constexpr` variable. That isn't true if `g` isn't called with a constant. – Passer By Mar 28 '23 at 11:34
  • OK, but I think that doesn't answer my question (maybe it does though). What I'm wondering is why the compiler is able to evaluate that at compile time (even without using constexpr before the variable) even though doing so would go against the rules that I mentioned regarding the "core constant expression"? – SomeoneWithPassion Mar 28 '23 at 11:35
  • fwiw, in your example the compiler has all information it needs. Compilers were able to evaluate functions at compile time already before `constexpr` and they did when they could. Perhaps now there are more ways for compilers, but to a large part `constexpr` is just formalizing what was before already common practice. I think I know what you want to ask, though "Why is the compiler able to evaluate this code at compile time", is simply that the functions are rather simple, easy to be inlined and often easy to be evaluated at compile time – 463035818_is_not_an_ai Mar 28 '23 at 11:40
  • My issue is the fact that while it is evaluated at compile time (by the compiler directly), the rules explicitly state this: "A core constant expression is any expression whose evaluation would not evaluate any one of the following: modification of an object, unless the object has non-volatile literal type and its lifetime began within the evaluation of the expression" In my example, that's what happens. The lifetime of the object began before the evaluation of the expression, and that's what I'm confused about. – SomeoneWithPassion Mar 28 '23 at 11:43
  • 1
    It's either unclear what's confusing, or I am confused. In `int x = incr(k);`, `incr(k)` does not have to be a core constant expression (because `x` is not `constexpr`). Thus, it doesn't matter that it isn't and the rules don't apply. There are no situations where a compiler is *prohibited* from evaluating anything at compile time if it can. – molbdnilo Mar 28 '23 at 12:07
  • This is worth a read [Why can't constexpr just be the default?](https://stackoverflow.com/questions/38879475/why-cant-constexpr-just-be-the-default) – Richard Critten Mar 28 '23 at 12:40

2 Answers2

2

The issue here – albeit quite subtle – is that the constexpr usage error is not for the specifier on either the g() or h() functions (all that states is that it is possible for them to be compile-time evaluated). The error is on the specific line, constexpr int x = incr(k); in the g() function (for the reason given).

In the h() function, the analogous line doesn't have the constexpr specifier … but that doesn't mean that it can't be evaluated at compile time. When calling h(1), the whole function can (and will) be evaluated at compile time, so there is no constexpr conflict.

Change to using a call of h(x) where the argument is not a literal (or other core constant expression) and you will see an error:

#include <iostream>

constexpr int incr(int& n)
{
    return ++n;
}

constexpr int h(int k)
{
    int x = incr(k); // OK: x is not required to be initialized
    // with a core constant expression
    return x;
}


int main()
{
    constexpr int y = h(1); // OK - as explained on cppreference
    int a = 42;
    constexpr int z = h(a); // Illegal - "Variable 'a' cannot be used as a constant"

    std::cout << y << " " << z << "\n"; // To prevent optimizing-away.
    return 0;
}

From the same cppreference page that you linked (bold emphasis mine):

The constexpr specifier declares that it is possible to evaluate the value of the function or variable at compile time. Such variables and functions can then be used where only compile time constant expressions are allowed (provided that appropriate function arguments are given).

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • Copy-paste from previous comment (Thanks for the answer BTW!): "My issue is the fact that while it is evaluated at compile time (by the compiler directly), the rules explicitly state this: "A core constant expression is any expression whose evaluation would not evaluate any one of the following: modification of an object, unless the object has non-volatile literal type and its lifetime began within the evaluation of the expression" In my example, that's what happens. The lifetime of the object began before the evaluation of the expression, and that's what I'm confused about." – SomeoneWithPassion Mar 28 '23 at 11:51
  • Basically, "k"'s lifetime did not start during the evaluation of the expression, but still, we are modifying its value in the function "incr" (since the argument is taken by reference). That's what doesn't click to me... Because the rules disallow that. But at this point I'm sure I'm missing the whole point, probably... – SomeoneWithPassion Mar 28 '23 at 11:52
  • In the words you have quoted: *unless the object has non-volatile literal type* ... The literal `1` seems a good fit. – Adrian Mole Mar 28 '23 at 11:55
  • OK, but right after there's "...and its lifetime began within the evaluation of the expression", that doesn't apply here, I think? Or does it? – SomeoneWithPassion Mar 28 '23 at 11:56
  • It does. See the last paragraph of [this answer](https://stackoverflow.com/a/70655655/10871073). – Adrian Mole Mar 28 '23 at 12:08
1

incr(k) is not a core constant expression, but it can be part of a core constant expression E as long as E is big enough that it contains the definition of the variable k.

When you do constexpr int x = incr(k);, you force the compiler to check whether "initialize int from incr(k)" is a constant expression ([dcl.constexpr]/6). In order for it to be a constant expression, it has to be a core constant expression ([expr.const]/13). It is not a core constant expression because it modifies k, whose lifetime didn't start within it. So this constexpr initialization is not allowed.

It's fine to do

constexpr int y = h(1);

Here, "initialize int from h(1)" can be a constant expression because the lifetime of the k variable, which gets incremented, only starts when h is called.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • It doesn't work even if I don't mutate the variable in incr. Why is that then? An example: constexpr int incr(int k1) { return k1; } constexpr int foo(int k) // runs in compile time { constexpr int x1 = incr(k); // a constant expression: constant rules apply return x1; } – SomeoneWithPassion Mar 29 '23 at 17:34
  • @SomeoneWithPassion That is a different question. Comments should only be used to ask for clarification. – Brian Bi Mar 29 '23 at 18:17