8

As a followup to this question, gcc and clang both consider this program ill-formed:

int main() {
    const int& ri = 0;
    constexpr int i = ri;
}

The error is about the value of ri being unusable in a constant expression. 0 is certainly a core constant expression, and as a prvalue core constant expression seems to satisfy these constraints (trivially, since int isn't of class, pointer, or array type). So shouldn't ri satisfy this criteria?

The same is true if I use a prvalue literal of class type:

struct X { };
int main() {
    const X& rx = X{};
    constexpr X x = rx;
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • An object can't be of class, pointer and array type simultaneously. No object can satisfy all the constraints. It must satisfy the appropriate one. An object of type `int` satisfies neither. So no, `ri` wouldn't apply. – StoryTeller - Unslander Monica Dec 20 '17 at 17:07
  • D'oh. Thank you for making me aware that I misread [\[expr.const\]/6](http://eel.is/c++draft/expr.const#6.sentence-2). It should read "an object with static storage duration that is (either not a temporary object or is a temporary object whose value satisfies the above constraints)", not "(an object with static storage duration that is either not a temporary object) or (is a temporary object whose value satisfies the above constraints)". – cpplearner Dec 20 '17 at 17:08
  • @StoryTeller The constraint `P -> Q` is satisfied by `!P`. – Barry Dec 20 '17 at 18:05
  • @Barry - I'm reading it more like `P1 || P2 || P3`. Where it just so happens all three are mutually exclusive. – StoryTeller - Unslander Monica Dec 20 '17 at 18:06
  • @StoryTeller They're not mutually exclusive. – Barry Dec 20 '17 at 18:08
  • @Barry - The bullets are not mutually exclusive? How so? Only one can apply to any type, at most. – StoryTeller - Unslander Monica Dec 20 '17 at 18:09
  • @StoryTeller If the object is of class type, we check both [non-static data members of reference type](http://eel.is/c++draft/expr.const#6.1) and [subobjects, recursively](http://eel.is/c++draft/expr.const#6.3) – Barry Dec 20 '17 at 18:14
  • @Barry - Checking the members recursively doesn't mean we apply more than one bullet to the super-object itself. But w/e, that's beside the point. Even if, for the sake of argument, a type satisfies more then one bullet, the satisfaction of *at least* one is still required. That paragraph is an only-if relationship. Which seems to suffer from some natural language pitfalls. – StoryTeller - Unslander Monica Dec 20 '17 at 18:19
  • 5
    The reference is initialized with the glvalue result of the temporary materialization conversion applied to the prvalue `0` adjusted to type `const int`. You can't ignore the conversions and consider only the syntactic form of the initializer. – T.C. Dec 20 '17 at 18:25
  • @StoryTeller What? We have to satisfy **all** the bullets, all "the following constraints." There is no "or", there is no "at least one". – Barry Dec 20 '17 at 18:26

2 Answers2

6

In this statement:

const int& ri = 0;

0 is a prvalue, but ri isn't initialized from that prvalue. The prvalue first undergoes a temporary materialization conversion and the reference is bound to the resulting glvalue. Since ri is bound to this materialized glvalue, and not directly to the prvalue like you (I) suspected, the relevant restrictions are not the prvalue core constant expression restrictions (which 0 does satisfy) but rather the glvalue core constant expression restrictions - that the entity be a permitted result of a constant expression. That restriction, spelled with slightly improved clarity, is:

either an object with static storage duration that is:

  • not a temporary object, or
  • a temporary object whose value satisfies the above constraints,

or it is a function.

Our glvalue is a temporary object whose value satisfies "the above constraints" ("above" here referring to the prvalue core constant restrictions, which int trivially satisfies), but it does not have static storage duration.

Not having static storage duration → the entity is not a permitted result of a constant expression → the glvalue expression not a constant expression → ri wasn't initialized with a constant expression → ri can't be used in a core constant expression → the declaration of i is ill-formed.

The same argument holds for appropriate class types as well.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    What does it mean for a temporary have static storage duration? – Passer By Dec 22 '17 at 07:59
  • @PasserBy something like `static int const &r = 0;` The reference `r` is bound to materialized temporary of a const-qualified type having static storage duration holding the value `0`. – mada Sep 22 '22 at 08:29
  • @John See https://stackoverflow.com/questions/47945299/what-is-a-temporary-object-with-static-storage-duration – Passer By Sep 22 '22 at 09:03
1

As you pointed out, 2.11 states that a core constant expression must not evaluate to:

  • 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 initialized with a constant expression or

And further expr.const#6:

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:

...

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

From my reading of this, it means that the RHS of const X& r = (substitute X for some type) must either be an object with static storage duration or a temporary object that meets the above criteria. Since an int does not fit either an object of class type, a pointer type or class/array type, it does not qualify.