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

Consider the above code, the variable value shall be initialized by a constant expression, which is func(0), which firstly shall be a core constant expression. To determine whether the expression func(0)is a core constant expression, the following rules will be applied to it, that is:
expr.const#2.7

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

an lvalue-to-rvalue conversion unless it is applied to

[...], or

(2.7.4) a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

Despite the lvalue-to-rvalue conversion is applied to rf and such conversion satisfied the bullet (2.7.4), however, take a look to the next paragraph, that is:
expr.const#2.11

an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either

  • (2.11.1) it is initialized with a constant expression or,
  • (2.11.2) its lifetime began within the evaluation of e;

I don't know what actually the phrase preceding initialization mean? Does it mean that a variable should be initialized before using it, or it means that in a declaration of a variable shall have an initializer. Anyhow, before applying the lvalue-to-rvalue conversion to glvalue rf, the glvalue rf should be evaluated to determine the identity of an object, which is ruling by:

A glvalue is an expression whose evaluation determines the identity of an object, bit-field, or function.

That means not only bullet [expr.const#2.7] shall be obeyed but also [expr.const#2.11] shall be obeyed.

Because id-expression rf is of reference type. So, in order to make the expression func(0) be a core constant expression, the id-expression rf must have a preceding initialization, and satisfies at least one of bullet (2.11.1) and (2.11.2). In my example, the bullet (2.11.2) is obeyed by rf and the compiler agree that func(0) is a constant expression. The outcome is here, the outcome seems to evidence that preceding initialization means be initialized rather than have an initializer due to the parameter declaration does not have an initializer in the first example.

In order to check such a thought, I test the below code:

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

The outcomes of the compilers point out rf is not a constant expression. I'm confuse at this outcome. According to the above supposition. rf has a preceding initialization, and the bullet (2.11.1) is obeyed because data is a constant expression, even if the bullet (2.11.2) does not be satisfied.

So, I wonder what actually does the phrase preceding initialization mean? If it means that a declaration for a variable has an initializer, how could the expression func(0) in the first example be a constant expression?

xmh0511
  • 7,010
  • 1
  • 9
  • 36
  • `constexpr int value = func(value);` is `constexpr int value = func(data);` – nop666 Aug 27 '20 at 06:54
  • @nop666 yes, a typo. I have modified it. – xmh0511 Aug 27 '20 at 06:58
  • 1
    uh.. a local reference's initializer based on local variable is never a a constant expression afaik, because of `rf`s life span <.<. And technically `rf` had not been initialized as far as that scope is concerned. Both `rf` and `v` are local variables. If you initialize `rf`, there will be no error, for that it should not be a function parameter. – Swift - Friday Pie Aug 27 '20 at 07:09
  • @Swift-FridayPie So why in the definition of `func` in the first example, `rf` does not violate the bullet [expr.const#2.11]? – xmh0511 Aug 27 '20 at 07:17
  • My understanding is that it is what called "decay" in linguistics. If first case expression `rf` doesn't refer to a reference type contextually but to related lvalue, because return value matches life span of `rf`. Attempting to use it as constexpr reference by initializing a reference fails because of 2.11. And mpark said, the key is visibility and life span. `constexpr` for `v` extended lifespan of `v` to point before function would be used. – Swift - Friday Pie Aug 27 '20 at 07:23
  • It would fare fine if you initialize it with namespace-scope expression. – Swift - Friday Pie Aug 27 '20 at 07:29

2 Answers2

6

The outcome seems to evidence that "preceding initialization" means "be initialized" rather than have an initializer due to the parameter declaration does not have an initializer in the first example.

It does mean "be initialized", but it's more importantly about the visibility of a preceding initialization within the context of the expression being evaluated. In your example, in the context of evaluating func(0) the compiler has the context to see the initialization of rf with 0. However, in the context of evaluating just the expression rf within func, it doesn't see an initialization of rf. The analysis is local, as in it doesn't analyze every call-site. This leads to expression rf itself within func not being a constant expression while func(0) is a constant expression.

If you were to write this instead:

constexpr int func(int const& rf) {
  /* constexpr */ int const& v = rf;
  return v;
}

int main() {
  static int const data = 0;
  constexpr int value = func(data);
}

This is okay again because in the context of func(data), rf has a preceding initialization with a constant expression data, and v has a preceding initialization with rf which is not a constant expression but its lifetime began within the evaluation of func(data).

mpark
  • 7,574
  • 2
  • 16
  • 18
  • I still can't understand what the `context` is, you say that `rf` has a preceding initialization in the context of `func(data)`, why change `/* constexpr */ int const& v = rf;` to `constexpr int const& v = rf;`, `rf` gonna have not a preceding initialization in the context of `func(data)`? – xmh0511 Aug 27 '20 at 07:35
  • I'm saying `rf` does have a preceding initialization in the context of initializing `value`, but it doesn't have a preceding initialization within the context of initializing `v`. – mpark Aug 27 '20 at 07:48
  • @jackX by limiting `v` to be `constexpr` you literally told compiler that it always should be `constexpr`.In first variant you are free to call `func` with non-const argument, so no part of that would be `constexpr` in context of the call. In context of `constexpr int value = func(data);` func is a constexpr, every object within it got lifetime beginning with that call and is `constexpr`. Similarly static variables are not allowed within constexpr function as they have to be initialized within first call of function but continue exist across all the calls. – Swift - Friday Pie Aug 27 '20 at 07:50
  • @mpark For this `int const& v = rf;` sentence, why `rf` be considered as having a preceding initialization? – xmh0511 Aug 27 '20 at 07:54
  • @jackX In less formal way, from `constexpr` `int value` point of view it is ok to be initialized by a `constexpr` function feed with a static const param. From `constexpr int const& v` restricted narrow-minded point of view, it is not ok to be initialized by a non-constexpr expression (it's just see a local `int const&` variable). From `int const& v` restricted but less narrow-minded point of view, it is ok to be initialized with `rf`. – nop666 Aug 27 '20 at 07:56
  • @Swift-FridayPie No, `constexpr int value = func(0);` is claimed that `func(0)` must be a constant expression. – xmh0511 Aug 27 '20 at 07:58
  • @jackX `rf` is **not** considered as having a preceding initialization in `int const& v = rf;`. Neither `v` nor `rf` are constant expressions themselves. – mpark Aug 27 '20 at 07:58
  • @mpark Despite `rf is not considered as having a preceding initialization in int const& v = rf; Neither v nor rf are constant expressions themselves.`, however the evaluation of `rf` occurred within the evaluation of `func(data)`, it shall obey the bullet [expr.const#2.11] – xmh0511 Aug 27 '20 at 08:06
  • By [expr.const#2], the expression `func(data)` is a core constant expression unless it would evaluate, among other exprs, [expr.const#2.11]. The evaluation includes calling `func` with `data`, initializing `rf` with `data`, initializing `v` with `rf`, and return `v`. During this sequence we see that `rf` has an initialization with a constant expression before being used to initialize `v`, so we obey [expr.const#2.11]. Now, according to [expr.const#2], it is `func(data)` that is a constant expression. It does not say anything about whether `rf` is a constant expression. – mpark Aug 27 '20 at 08:35
  • In contrast, by [expr.const#2], the expression `rf` is a core constant expression unless it would evaluate among other exprs, [expr.const#2.11]. The evaluation includes the _id-expression_ `rf` that refers to a variable of reference type. During this sequence we do not see a preceding initialization of `rf`, which does not satisfy [expr.const#2.11] and therefore `rf` is not a constant expression. But again, `func(data)` including `rf` which is not in and of itself a constant expression doesn't prevent `func(data)` from being a constant expression. – mpark Aug 27 '20 at 08:39
  • @mpark All right. Remove the comment out, then in your way: By [expr.const#2], the expression `func(data)` is a core constant expression unless it would evaluate, among other exprs, [expr.const#2.11]. The evaluation includes calling `func` with `data`, initializing `rf` with `data`, initializing v with `rf`, and return `v`. why this time, during this sequence, we **can not** see that `rf` has an initialization with a constant expression before being used to initialize `v`, please interpret this, I just kind of don't understand here, thanks. – xmh0511 Aug 27 '20 at 08:43
  • Ah, okay. If you remove the comment (I assume you mean add the `constexpr` to `int const& v`), you've made `v` a `constexpr` variable. By [dcl.constexpr#10](https://eel.is/c++draft/dcl.constexpr#10.sentence-3), `rf` shall be a constant expression. As I walked through above, `rf` is not a constant expression. Therefore `constexpr int const& v = rf;` is ill-formed before we even get to asking your question of whether `func(data)` is a core constant expression. – mpark Aug 27 '20 at 08:51
  • @mpark I'm saying that. what make the `rf` not be a constant expression is that it violates `[expr.const#2.11]`, that means, `rf` does not have a preceding initialization, the key point is here, I just add the `constexpr` to `int const& v`, why before adding the `constexpr`, we can see the `preceding initialization`, however, after adding the `constexpr`, we can not? – xmh0511 Aug 27 '20 at 08:58
  • @mpark By understanding your comment carefully, Do you mean that the analysis of these constant expressions occur in the order of lexical in the translate unit? It's irrelevant to whether a constant expression gonna evaluate within another expression? For example, the evaluation of `rf` in `constexpr int const& v = rf;` occurs before the function call `func(data)`, therefore, at this point, `rf` has not been initialized? – xmh0511 Aug 27 '20 at 09:16
  • In the expression `rf`, we cannot see a preceding initialization whether we add the `constexpr` on `v` or not. `rf` is also not a constant expression whether we add the `constexpr` on `v` or not. The only difference adding `constexpr` on `v` does is [dcl.constexpr#10](https://eel.is/c++draft/dcl.constexpr#10.sentence-3) which requires `rf` to be a constant expression, which it is not. I'm not really sure if I'm helping – mpark Aug 27 '20 at 09:17
  • @mpark Sorry, you said that `During this sequence we see that rf has an initialization with a constant expression before being used to initialize v, so we obey [expr.const#2.11].` in your above [comment](https://stackoverflow.com/questions/63610256/what-is-the-meaning-of-the-phrase-preceding-initialization-in-section-expr-co/63611081?noredirect=1#comment112486483_63611081). – xmh0511 Aug 27 '20 at 09:20
  • Right, but that is with respect to the expression `func(data)`, not the expression `rf`. – mpark Aug 27 '20 at 09:21
  • @mpark Oh, Do you mean that every analysis of determining whether an expression is a constant expression(when it appear in a context that require a constant expression) is individually? that is, when we analyze `rf` in `constexpr int const& v = rf;`, `rf` has not been initialized. when we analyze `func(data)`,enter the function body, now the `rf` is initialized. Right? – xmh0511 Aug 27 '20 at 09:29
  • @mpark So, although the declaration `constexpr int const& v = rf;` appears within the function body of `func`, when we analyze `rf`, we shouldn't assume that at this point, it's in the context of evaluation of `func`. – xmh0511 Aug 27 '20 at 09:32
  • That's right... but assuming that it'll be in the context of evaluating `func` doesn't help anyway. Even if we were to assume that `rf` must have a preceding initialization because it's a reference, [expr.const#2.11.1] says it needs to be initialized with a constant expression. From within `func` we don't know whether the argument provided will be a constant expression or not. We certainly can't assume `func(data)` from within `func`. – mpark Aug 27 '20 at 09:41
  • @mpark Maybe I mix the `evaluation` and `execution`. For example `void func(){printf("execution");}`, If I don't call `func`, then the program never prints `execution`. It seems that `evaluation` shall be applied to every `expression` that written in the translate unit. However that's my understanding. I haven't find any definition for what is the difference between `evaluation` and `execution` in the [standard](https://timsong-cpp.github.io/cppwp/n4659/intro.execution). – xmh0511 Aug 27 '20 at 10:23
3

This is CWG2186:

2186. Unclear point that “preceding initialization” must precede

Similar to the concern of issue 2166, the requirement of 8.20 [expr.const] bullet 2.7.1 for

— a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or

does not specify the point at which the determination of “preceding initialization” is made: is it at the point at which the reference to the variable appears lexically, or is it the point at which the outermost constant evaluation occurs? There is implementation divergence on this point.

But the meaning should be "lexically", because ODR.

Language Lawyer
  • 3,378
  • 1
  • 12
  • 29
  • ARe there implementation which allow that? Sounds like it'd be problematic to implement, but `preceding initialization` indeed is a vague clause – Swift - Friday Pie Aug 27 '20 at 07:37
  • @LanguageLawyer If the determination of “preceding initialization” is at the point at which the reference to the variable appears lexically, that means, the first example in my question would be ill-formed?(the parameter declaration does not have an initializer) – xmh0511 Aug 27 '20 at 08:13