Title
Rvalue-ness check for rvalue reference binding is wrong
Status
c++11
Section
9.4.4 [dcl.init.ref]
Submitter
US

Created on 2010-08-02.00:00:00 last changed 122 months ago

Messages

Date: 2010-11-15.00:00:00

[Voted into the WP at the November, 2010 meeting.]

Date: 2010-08-15.00:00:00

Proposed resolution (August, 2010):

Change 9.4.4 [dcl.init.ref] paragraph 5 as follows:

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

  • If the reference is an lvalue reference and the initializer expression

    • is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or

    • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3105 (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])),

    then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [Note: the usual lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done. —end note]

    [Example:

      double d = 2.0;
      double& rd = d;          // rd refers to d
      const double& rcd = d;   // rcd refers to d
      struct A { };
      struct B : A { operator int&(); } b;
      A& ra = b;               // ra refers to A subobject in b
      const A& rca = b;        // rca refers to A subobject in b
      int& ir = B();           // ir refers to the result of B::operator int&
    

    end example]

  • Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference and the initializer expression shall be an rvalue or have a function type. [Example:

  •   double& rd2 = 2.0;       // error: not an lvalue and reference not const
      int  i = 2;
      double& rd3 = i;         // error: type mismatch and reference not const
      double&& rd4 = i;        // error: rvalue reference cannot bind to lvalue
    

    end example]

    • If the initializer expression

      • is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or

      • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3”,

      then the reference is bound to the value of the initializer expression in the first case and to the result of the conversion in the second case (or, in either case, to an appropriate base class subobject). In the second case, if the reference is an rvalue reference and the second standard conversion sequence of the user-defined conversion sequence includes an lvalue-to-rvalue conversion, the program is ill-formed.

    • If T1 is a function type, then

      • if T2 is the same type as T1, the reference is bound to the initializer expression lvalue;

      • if T2 is a class type and the initializer expression can be implicitly converted to an lvalue of type T1 (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])), the reference is bound to the function lvalue that is the result of the conversion;

      • otherwise, the program is ill-formed.

    • Otherwise, if T2 is a class type and

      • the initializer expression is an rvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or

      • T1 is not reference-related to T2 and the initializer expression can be implicitly converted to an rvalue of type “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (12.2.2.7 [over.match.ref]) and choosing the best one through overload resolution (12.2 [over.match])),

      then the reference is bound to the initializer expression rvalue in the first case and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).

      [Example:

        struct A { };
        struct B : A { } b;
        extern B f();
        const A& rca = f();      // bound to the A subobject of the B rvalue.
        A&& rcb rra = f();       // same as above
        struct X {
        operator B();
        operator int&();
        } x;
        const A& r = x;                     // bound to the A subobject of the result of the conversion
        int&& rri = static_cast<int&&>(i);  // bound directly to i
        B&& rrb = x;                        // bound directly to the result of operator B
        int&& rri2 = X();                   // error: lvalue-to-rvalue conversion applied to result of operator int&
      

      end example]

    • If the initializer expression is an rvalue, with T2 an array type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound to the object represented by the rvalue (see 7.2.1 [basic.lval]).

    • Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization (9.4 [dcl.init]). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 must shall be the same cv-qualification as, or greater cv-qualification than, cv2; otherwise, the program is ill-formed. If T1 is reference-related to T2 and the reference is an rvalue reference, the initializer expression shall not be an lvalue. [Example:

    •   const double& rcd2 = 2;     // rcd2 refers to temporary with value 2.0
        double&& rcd3 rrd = 2;      // rcd3 rrd refers to temporary with value 2.0
        const volatile int cvi = 1;
        const int& r = cvi;         // error: type qualifiers dropped
        double&& rrd2 = d;          // error: copying lvalue of related type
        double&& rrd3 = i;          // rrd3 refers to temporary with value 2.0
      

      end example]

In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.

This resolution also resolves issue 1139.

Date: 2022-02-18.07:47:23
N3092 comment US 48
N3092 comment GB 37
N3092 comment DE 10

The requirement that an rvalue reference must be bound to an rvalue is found in 9.4.4 [dcl.init.ref] bullet 5.2:

  • Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference and the initializer expression shall be an rvalue or have a function type.

This is not quite correct, as it is phrased in terms of the value category of the initializer expression itself rather than that of the result of any conversions applied to the initializer. It should be permitted to bind an rvalue reference to a temporary created from an lvalue, for instance, or to the rvalue result of a conversion function for an lvalue object of class type. Also, it should not be permitted to bind an rvalue reference to the lvalue result of a conversion function for a class rvalue.

History
Date User Action Args
2014-03-03 00:00:00adminsetstatus: fdis -> c++11
2011-04-10 00:00:00adminsetstatus: dr -> fdis
2010-11-29 00:00:00adminsetmessages: + msg3178
2010-11-29 00:00:00adminsetstatus: ready -> dr
2010-08-23 00:00:00adminsetmessages: + msg2787
2010-08-02 00:00:00admincreate