9

Recently I was surprised that the following code compiles in clang, gcc and msvc too (at least with their current versions).

struct A {
    static const int value = 42;
};

constexpr int f(A a) { return a.value; }

void g() {
    A a;  // Intentionally non-constexpr.
    constexpr int kInt = f(a);
}

My understanding was that the call to f is not constexpr because the argument i isn't, but it seems I am wrong. Is this a proper standard-supported code or some kind of compiler extension?

5
  • how could i not be a compile-time constant? Commented Mar 22, 2022 at 20:50
  • 4
    "My understanding was that the call to f is not constexpr because the argument i isn't" - It isn't that simple. Which is good, because that makes constant evaluation more useful. Constant expressions can be produced so long as a fixed list of rules isn't violated during evaluation. So long as f and i don't violate those rules... nothing stops constant evaluation. Commented Mar 22, 2022 at 20:55
  • @MarcusMüller Because it was not declared as constexpr. For example int x = 5; constexpr int y = x; would not compile, even though the compiler knows the value of x. Commented Mar 22, 2022 at 21:04
  • but it is a compile-time constant! You not calling it constexpr doesn't change that. Commented Mar 22, 2022 at 21:06
  • AFAIK it'll work even with variable, but it'll generate standard runtime function for it. Commented Mar 22, 2022 at 21:10

1 Answer 1

7

As mentioned in the comments, the rules for constant expressions do not generally require that every variable mentioned in the expression and whose lifetime began outside the expression evaluation is constexpr.

There is a (long) list of requirements that when not satisfied prevent an expression from being a constant expression. As long as none of them is violated, the expression is a constant expression.

The requirement that a used variable/object be constexpr is formally known as the object being usable in constant expressions (although the exact definition contains more detailed requirements and exceptions, see also linked cppreference page).

Looking at the list you can see that this property is required only in certain situations, namely only for variables/objects whose lifetime began outside the expression and if either a virtual function call is performed on it, a lvalue-to-rvalue conversion is performed on it or it is a reference variable named in the expression.

Neither of these cases apply here. There are no virtual functions involved and a is not a reference variable. Typically the lvalue-to-rvalue conversion causes the requirement to become important. An lvalue-to-rvalue conversions happens whenever you try to use the value stored in the object or one of its subobjects. However A is an empty class without any state and therefore there is no value to read. When passing a to the function, the implicit copy constructor is called to construct the parameter of f, but because the class is empty, it doesn't actually do anything. It doesn't access any state of a.

Note that, as mentioned above, the rules are stricter if you use references, e.g.

A a;
A& ar = a;
constexpr int kInt = f(ar);

will fail, because ar names a reference variable which is not usable in constant expressions. This will hopefully be fixed soon to be more consistent. (see https://github.com/cplusplus/papers/issues/973)

Sign up to request clarification or add additional context in comments.

2 Comments

I see, so basically, my intuition was that that calling a function would force lvalue-to-rvalue conversion, but it didn't, because the type was empty. Indeed, adding a non-static member to A makes the example fail to compile, but it still works when no function call is present, that is, constexpr int kInt = a.value;. Now it will be easier to rely on that feature. Thank you for explanation!
@dtldarek Yes, and if you let the function take its argument by-reference, then it will work even if you add non-static members, since the only lvalue-to-rvalue conversion on the object happens in the copy constructor, which we can avoid this way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.