Temporary objects that are usable in constant expressions

Viewed 219

As a follow-up to this question, clang accepts the code provided there. This question has the following code:

constexpr int func(int const& rf){
    return rf;
}
int main(){
   constexpr int value = func(0);
}

This question has a good answer, but it follows the C++17 standard. As far as I can tell, the wording regarding constant expression rules is relatively changed from C++17 to C++20 and later.

Basically, It has to determine whether the call expression func(0) is a constant expression; so firstly we have to know whether the call expression is core constant expression or not, which is governed by the rules defined in [expr.const]/5:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

  • (5.8) an lvalue-to-rvalue conversion unless it is applied to
    • (5.8.1) a non-volatile glvalue that refers to an object that is usable in constant expressions, or
    • (5.8.2) a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;

The rule (5.8) is applied because the expression E evaluates an lvalue-to-rvalue conversion: that's, the lvalue rf has to be converted to prvalue as the returned value of the function call.

According to (5.8.1), the expression rf is a non-volatile glvalue; but, does it is usable in constant expressions? Per [expr.const]/4:

  • [..]

An object or reference is usable in constant expressions if it is

  • (4.4) a variable that is usable in constant expressions, or
  • [..]
  • (4.7) a temporary object of non-volatile const-qualified literal type whose lifetime is extended ([class.temporary]) to that of a variable that is usable in constant expressions, or

I'm not so sure whether bullet (4.7) is applicable to this case. But I think that it might be applicable because note that rf is bound to a materialized temporary of non-volatile const-qualified literal type whose lifetime is extended and that temporary is usable in constant expression because it's potentially-constant as well as constant-initialized.

Also, I can't find any reason why (4.4) is not applicable: rf is a variable and it's usable in constant expressions because it's potentially-constant and constant-initialized with the value 0.

So that's my confusion: which bullet (if any) is applicable here? and why?

If neither of (4.4) or (4.7) is applicable to this case, that means rf may not be usable in constant expression which also means that (5.8.1) is not satisfied and (5.8.2) is then tried. I'm not having any problem with (5.8.2): if (5.8.1) failed, (5.8.2) succeeded because the lifetime of rf exactly began within the evaluation of func(0). If that's the case, why (5.8.1) is not satisfied? My confusion specifically is why (4.7) is not satisfied.

Also note that [expr.const]/(5.12) is not reached: (5.8) is tried first.

3 Answers

After hard searching and breaking down the rules from the standard, I would add a C++20 answer for this question.

constexpr int func(int const& rf){
    return rf;
}
int main(){
   constexpr int value = func(0);
}

As said above we need to determine whether the expression func(0) is a core constant expression or not.

Per [expr.const]/5, the first bullet we encountered that need to be checked is (5.8) since an lvalue-to-rvalue is applied to rf:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

  • (5.8) an lvalue-to-rvalue conversion unless it is applied to
    • (5.8.1) a non-volatile glvalue that refers to an object that is usable in constant expressions, or
    • (5.8.2) a non-volatile glvalue of a literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;

The bullet (5.8.1) is tried first: The lvalue-to-rvalue is applied to a non-volatile glvalue which is rf; but does this glvalue refers to an object that's usable in constant expressions? In fact, rf refers to a temporary object. In order to know whether this temporary object is usable in constant expression, we've to request [expr.const]/4: Both p4.4 and p4.7 are needed to be checked here:

[..] An object or reference is usable in constant expressions if it is

  • (4.4) a variable that is usable in constant expressions, or
  • [..]
  • (4.7) a temporary object of non-volatile const-qualified literal type whose lifetime is extended ([class.temporary]) to that of a variable that is usable in constant expressions, or [..]

Intuitively, the bullet (4.4) is not applied because rf refers to a temporary object, not to a variable.

On other hand (4.7) is relevant here: rf refers to a temporary object of non-volatile const-qualified (per [dcl.init.ref]/(5.3)) literal type (per [basic.types.general]/10), and its lifetime is extended (per [class.temporary]); but in order for (4.7) to apply completely, the lifetime of the temporary has to be extended to that of a variable usable in constant expressions: In other words, the variable (rf) that extends the lifetime of the temporary has to usable in constant expressions. So does the variable rf usable in constant expressions?

In order to know whether the variable rf is usable in constant expression, we have to know whether it's potentially-constant and constant-initialized. So per [const.expr]/3:

A variable is potentially-constant if it is constexpr or it has reference or const-qualified integral or enumeration type.

So, the variable rf is potentially-constant because it's of reference type.

But, per [const.expr]/2:

A variable or temporary object o is constant-initialized if

  • (2.1) either it has an initializer [..] and
  • (2.2) the full-expression of its initialization is a constant expression [..]

I'm not so sure whether (2.1) is satisfied or not: That's, I can't tell whether the variable rf has an initializer or not. I will assume that the rf has an initializer which is the materialized temporary even though I made a question on that.

For (2.2) to be applied, the full-expression of the initialization of rf has to be a constant expression. But, the variable rf is bound to a temporary that's not a constant expression per [const.expr]/11:

A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:

  • (11.1) if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
  • (11.2) - if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object ([expr.add]), the address of a non-immediate function, or a null pointer value,
  • (11.3) - if the value is of pointer-to-member-function type, it does not designate an immediate function, and
  • (11.4) - if the value is an object of class or array type, each subobject satisfies these constraints for the value.

An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.

That's the value of the temporary is neither of class type nor pointer type nor array type. But even though the temporary object satisfies those constraints, it's not a permitted result of a constant expression because it's not an object with static storage duration. Hence, the temporary is not glvalue core constant expression and therefore is not a constant expression.

Therefore, the variable rf is not constant-initialized and therefore is not usable in constant expression, therefore (4.7) is failed and therefore the temporary bound by rf is also not usable in constant expressions, and therefore (5.8.1) is failed.

Then, (5.8.2) is tried: (5.8.2) is succeeded because the lifetime of rf exactly began within the evaluation of func(0). Checking the subsequent bullets is not relevant to the question. But since the above program is not ill-formed, this means all subsequent bullets are satisfied, and therefore func(0) is a prvalue core constant expression and therefore a constant expression.

p5.8.1 is not satisfied. rf does not refer to "an object that is usable in constant expressions", because:

  • p4.4 is not satisfied: the object that rf refers to is not a variable (it is a temporary object). (Notice that whether or not rf itself is a "variable that is usable in constant expressions" is not relevant for p4.4; the test of p5.8.1 is whether the object that the glvalue refers to is a variable that is usable in constant expressions. But see p4.7, below.)
  • p4.5, 4.6, and 4.8 are obviously not applicable.
  • p4.7 is not satisfied. Leaving aside the question of whether the temporary object of type int is const-qualified or not (which has been discussed in a previous question), the reference rf is not "a variable that is usable in constant expressions" anyway, because a function parameter doesn't have an initializing declaration.

I would answer this question from the perspective of the c++20 standard since the bullet [expr.const] p5.12 is removed and is taken place by the new concept in the c++23 draft.

First, the constexpr specifier requires the full-expression of the initialization of the variable value to be a constant expression, and a constant expression is either a glvalue core constant expression or a prvalue core constant expression, which is regulated by [expr.const] p5, where the bullet [expr.const] p5.2, [expr.const] p5.8, and [expr.const] p5.12 are relevant here.

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

  • [...]
  • an invocation of a non-constexpr function
  • [...]
  • an lvalue-to-rvalue conversion unless it is applied to
  • a non-volatile glvalue that refers to an object that is usable in constant expressions, or
  • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
  • an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
  • it is usable in constant expressions or
  • its lifetime began within the evaluation of E;

In your example, func is a constexpr function, hence, it does not violate the first bullet in the list. The variable rf that appears in the return statement requires lvalue-to-rvalue conversion to apply to rf, where rf is a glvalue that refers to the object that is of literal type(int), and the lifetime of the object began within the evaluation of the full-expression of the initialization, see [class.temporary] p1 and [intro.execution] p5, hence the whole full-expression of the initialization still does not violate the second bullet in the list.

For the third bullet, since the id-expression rf is of reference type, however, its lifetime still began within the evaluation of the full-expression of the initialization, see [basic.life] p1, because the initialization of rf occurs in the evaluation of the full-expression of the initialization. So, eventually, the full-expression of the initialization of the variable value is a constant expression.


For the concept preceding initialization, it basically means it has been initialized. Anyway, it is CWG2186. However, as I said in the initial, the bullet is just removed in c++23 standard.

Related