79784362

Date: 2025-10-07 08:58:10
Score: 1
Natty:
Report link

Combining and summarising the answers, comments and CWG reports.

  1. Noting from @Nicol Bolas's answer and CWG 616, S().x was initially an rvalue (see the otherwise, it is a prvalue)

    text showing the change

  2. Then, in CWG 240 Mike Miller pointed out an issue with the use of rvalue. Basically, it doesn't participate in lvalue-to-rvalue conversion and will not lead to undefined behaviour error when used in initialization.

    7.3.2 [conv.lval] paragraph 1 says,

    If the object to which the lvalue refers is not an object of type T and is not an 
    object of a type derived from T, or if the object is uninitialized, a program that 
    necessitates this conversion has undefined behavior.
    

    I think there are at least three related issues around this specification: ...

    1. It's possible to get an uninitialized rvalue without invoking the lvalue-to-rvalue conversion. For instance:

      struct A {
      int i;
      A() { } // no init of A::i
      };
      int j = A().i;  // uninitialized rvalue
      

      There doesn't appear to be anything in the current IS wording that says that this is undefined behavior. My guess is that we thought that in placing the restriction on use of uninitialized objects in the lvalue-to-rvalue conversion we were catching all possible cases, but we missed this one.

    This gives a reason to change the value category of A().i to lvalue so that it participates in lvalue-to-rvalue conversion and leads to the expected undefined behaviour

  3. Then in CWG 240 itself, John Max Stalker raised an argument that A().i should be an lvalue

    A().i had better be an lvalue; the rules are wrong. Accessing a member of a structure requires it be converted to an lvalue, the above calculation is 'as if':

    struct A {
    int i;
    A *get() { return this; }
        };
    int j = (*A().get()).i;
    

    and you can see the bracketed expression is an lvalue.

    For me, this argument isn't strong enough. Because following this point, A() can also be written as (*A().get()) and can be said to an lvalue. Following this, there will be very few rvalues.

    The concept of identity (i.e. to say that A() denotes a specific object, which can be later, in next lines of code be retrieved) is important to recognise lvalues.

  4. Finally, as in the comment of Vincent X, the P0135R0 clears the confusion by changing the definitions. It clearly highlights the pain point

    ... for instance, an expression that creates a temporary object designates an object, so why is it not an lvalue? Why is NonMoveable().arr an xvalue rather than a prvalue? This paper suggests a rewording of these rules to clarify their intent. In particular, we suggest the following definitions for glvalue and prvalue:

    • A glvalue is an expression whose evaluation computes the location of an object, bit-field, or function.

    • A prvalue is an expression whose evaluation initializes an object, bit-field, or operand of an operator, as specified by the context in which it appears.

    That is: prvalues perform initialization, glvalues produce locations.

    It gives a code example for the redefinition as well.

    struct X { int n; };
    extern X x;
    X{4};   // prvalue: represents initialization of an X object
    x.n;    // glvalue: represents the location of x's member n
    X{4}.n; // glvalue: represents the location of X{4}'s member n;
    in particular, xvalue, as member is expiring
    

    I didn't get the idea completely (guess, will have to read about temporary materialization) but feel that this is what the new definition of value categories is. As in cppreference as well, the definition of lvalue focuses on identity (or the ability to recognise a particular memory location), while prvalue designates something that either initializes or doesn't have any object related to it.

    • a glvalue (“generalized” lvalue) is an expression whose evaluation determines the identity ...

    • a prvalue (“pure” rvalue) is an expression whose evaluation

      • computes the value ... (such prvalue has no result object), or
      • initializes an object (such prvalue is said to have a result object).

Finally, I think it started with the error in CWG 240 and with the culmination of other errors, it was resolved completely in C++17 by temporary materialization as noted in @HolyBlackCat's answer. There isn't concrete change focused on this particular issue but it was rather covered in a culmination of language changes.

Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • User mentioned (1): @Nicol
  • User mentioned (0): @HolyBlackCat's
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: Dhruv