1

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.

mada
  • 1,646
  • 1
  • 15

2 Answers2

-1

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.
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Thank you I made sure that (4.4) is not satisfied because as you already explained, `rf` refers to a temporary **not** a variable. Right? – mada Sep 19 '22 at 04:36
  • _"p4.7 is not satisfied"_ I thought you would also give the reason for not applying the p4.7. As you've already said `rf` refers to a temporary and that temporary is of non-volatile const-qualified (per [dcl.init.ref]) literal type whose lifetime is extended (per [class.temporary]). Isn't the rule p4.7 match? – mada Sep 19 '22 at 04:46
  • @John in order for 4.7 to apply, the lifetime must be "extended ([class.temporary]) to that of a variable that is usable in constant expressions" (not merely extended) and I believe I explained why `rf` itself is not a variable that is usable in constant expressions. – Brian Bi Sep 19 '22 at 13:21
  • _"the lifetime must be extended to that of a variable that is usable in constant expressions"_ That's what's actually happened. After the lifetime of the temporary is extended, we ended up with a temporary of const-qualified integral type and constant-initialized with value zero. So, I don't see a real reason that prohibits that temporary from being usable in constant expressions. Please, let me understand this point _specifically_ instead of posting a new dupe. – mada Sep 19 '22 at 14:40
  • @John The lifetime of the temporary object is extended to that of `rf`. So in order to determine whether the lifetime "is extended to that of a variable that is usable in constant expressions" we need to look at whether `rf` is "a variable that is usable in constant expressions". The answer is that it is not, because it has no initializing declaration. – Brian Bi Sep 20 '22 at 15:09
  • _"The answer is that it is not, because it has no initializing declaration."_ Oh that's correct - the argument `0` is an _initializer-clause_ not an _initializer_. Right? – mada Sep 21 '22 at 12:25
  • @John That's true, but it's not relevant what kind of grammar production it is. The function parameter is only declared once in this case, but it's initialized every time the function is called. The declaration and initialization don't occur together, so the declaration is not an initializing declaration. – Brian Bi Sep 21 '22 at 16:21
  • So if I provide a default argument for the parameter, Will the declaration be now initializing declaration? – mada Sep 21 '22 at 16:45
  • @John I don't think so but that needs to be a separate question. – Brian Bi Sep 21 '22 at 17:27
  • _" but that needs to be a separate question"_ I already opened an [separate question](https://stackoverflow.com/q/73801082/19495502) for that – mada Sep 22 '22 at 04:46
-1

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.

xmh0511
  • 7,010
  • 1
  • 9
  • 36
  • In the first part of the answer, Why do you immediately jump to (5.8.2)? (5.8.1) is tried first: This is the point of my question: Why you left off _"a non-volatile glvalue that refers to an object that is usable in constant expressions"_? If because it's not satisfied, then why? – mada Sep 21 '22 at 05:05
  • @John So, which the involved evaluation of expressions do you think in the evaluation of `func(0)`? Aren't they just these bullets I listed in the answers? – xmh0511 Sep 21 '22 at 05:09
  • @John I would say even though `5.8.1` is false, we also have the `5.8.2` to be true, either is true is enough here. Second, for your confusion to `5.8.1`, the temporary object that is created by temporary materialization conversion is a constant-initialized temporary object because it satisfies [expr.const] p2. According to [dcl.init.ref] p5.3, the temporary object will have type `int const`, so it is also *potentially-constant*. Eventually, we just need to check [expr.const] p4, and [expr.const] p4.7 is relevant here, which requires that the variable `rf` is usable in constant expression... – xmh0511 Sep 21 '22 at 05:21
  • *that refers to an object (rf variable)*. No, `rf` is not an object, it is a reference that is bound to **a temporary object**, the bullet `5.8.1` actually requires the referred temporary object to be usable in constant expressions. In other words, we just need to check whether the temporary object satisfies *An object or reference is usable in constant expressions if it is* in expr.const#4 – xmh0511 Sep 21 '22 at 05:25
  • I said in the above comment, bullet `5.8.1` **does not** require `rf` to be usable in constant expressions, it exactly says: a non-volatile glvalue that refers to **an object that is usable in constant expressions** – xmh0511 Sep 21 '22 at 05:31