Document number: N1855=05-0115

David Abrahams
Peter Dimov
Howard E. Hinnant
Andreas Hommel
2005-08-25

A Proposal to Add an Rvalue Reference to the C++ Language

Proposed Wording

Revision 1

Revision 1 Summary and Rationale

This document slightly revises N1770 to fix a problem found in the use of rvalue stream objects.

Consider:

#include <ostream>

class A
{
public:
    A(int i) : i_(i) {}
    int get() const {return i_;}
private:
    int i_;
};

std::ostream&
operator<< (std::ostream& os, const A& a)
{
    return os << a.get();
}

This is ill formed code according to N1770:

    return os << a.get();
Error:  {lval}ostream << {rval}int is ambiguous:
std::ostream::operator<< (int);       // #1
operator<<(std::ostream&, const A&);  // #2

The first overload is a member function of ostream and thus has the following pseudo-signature:

operator<<(std::ostream::operator&&, int);     // #1

To match #1, the first parameter os is converted from an lvalue to rvalue to match the implicit object parameter and the second parameter is an exact match.

To match #2, the first parameter is an exact match, but the second parameter is converted via the A(int) constructor.

According to 13.3.3p1, the call is ambiguous because #1 favors the second implicit conversion sequence while #2 favors the first implicit conversion sequence.

We believe the source of the trouble is that N1770 changed the type of the implicit object parameter from an lvalue-reference to an rvalue-reference (which was motivated by eliminating one exception to the general rules of the language).

This revision corrects that mistake by reverting the type of the implicit object parameter to an lvalue-reference (as it is in C++03), and adding a clarification to the special treatment that the implicit object parameter already receives during overload resolution: An rvalue to lvalue conversion to match the implicit object parameter does not affect overload resolution (which is also true in C++03).

The specific revisions to N1770 are:

Introduction

The purpose of this document is to provide proposed wording for the rvalue reference proposal documented by N1690 and N1377.

The proposed wording herein avoids defining the terms move constructor and move assignment in order to avoid possible conflict or confusion with similar terms which we wish to introduce into the library. N1771 introduces MoveConstructible and MoveAssignable requirements, defining these concepts in terms of expressions and post-conditions, instead of in terms of constructors or function signatures.

3.10 - Lvalues and rvalues

-5- The result of calling a function that does not return a reference an lvalue-reference is an rvalue. User defined operators are functions, and whether such operators expect or yield lvalues is determined by their parameter and return types.

-6- An expression which holds a temporary object resulting from a cast to a nonreference typetype other than an lvalue-reference type is an rvalue (this includes the explicit creation of an object using functional notation (5.2.3)).

4 - Standard conversions

-3- An expression e can be implicitly converted to a type T if and only if the declaration ``T t=e;'' is well-formed, for some invented temporary variable t (dcl.init). The effect of the implicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference an lvalue-reference type (dcl.ref), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.

5 - Expressions

-6- If an expression initially has the type ``reference to T'' (dcl.ref, dcl.init.ref), the type is adjusted to ``T'' prior to any further analysis,and the expression designates the object or function denoted by the reference, and the expression is an lvalue. The expression is considered an lvalue after this adjustment if the expression type was ``rvalue-reference to T'' and the expression is a variable reference, parameter reference, class member access operation (expr.ref), or pointer-to-member operation (expr.mptr.oper), or if the expression type was ``lvalue-reference to T''; otherwise, it is considered an rvalue.

5.2.2 - Function call

-10- A function call is an lvalue if and only if the result type is a reference an lvalue-reference.

5.2.7 - Dynamic cast

-2- If T is a pointer type, v shall be an rvalue of a pointer to complete class type, and the result is an rvalue of type T. If T is a referencean lvalue-reference type, v shall be an lvalue of a complete class type, and the result is an lvalue of the type referred to by T. If T is an rvalue-reference type, v shall be an expression having a complete class type, and the result is an rvalue of the type referred to by T.

-5- If T is ``pointer to cv1 B'' and v has type ``pointer to cv2 D'' such that B is a base class of D, the result is a pointer to the unique B sub-object of the D object pointed to by v. Similarly, if T is ``reference to cv1 B'' and v has type ``cv2 D'' such that B is a base class of D, the result is an lvalue for the unique B sub-object of the D object referred to by v. The result is an lvalue if T is an lvalue-reference, or an rvalue if T is an rvalue-reference.

...

5.2.9 - Static cast

-1- The result of the expression static_cast<T>(v) is the result of converting the expression v to type T. If T is a reference an lvalue-reference type, the result is an lvalue; otherwise, the result is an rvalue. Types shall not be defined in a static_cast. The static_cast operator shall not cast away constness (expr.const.cast).

-2- An lvalue of type ``cv1 B'', where B is a class type, can be cast to type ``reference to cv2 D'', where D is a class derived (clause class.derived) from B, if a valid standard conversion from ``pointer to D'' to ``pointer to B'' exists (conv.ptr), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is not a virtual base class of D. The result is an lvalue of type ``cv2 D.'' if the type cast to is an lvalue reference; otherwise, the result is an rvalue. An rvalue of type ``cv1 B'' can be cast to type ``rvalue-reference to cv2 D'' with the same constraints as noted for the lvalue of type ``cv1 B,'' resulting in an rvalue. If the lvalue object of type ``cv1 B'' is actually a sub-object of an object of type D, the lvalue result refers to the enclosing object of type D. Otherwise, the result of the cast is undefined.

...

-3- Otherwise, an expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration ``T t(e);'' is well-formed, for some invented temporary variable t (dcl.init). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference an lvalue-reference type (dcl.ref), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.

5.2.10 - Reinterpret cast

-1- The result of the expression reinterpret_cast<T>(v) is the result of converting the expression v to type T. If T is a reference an lvalue-reference type, the result is an lvalue; otherwise, the result is an rvalue and the lvalue-to-rvalue (conv.lval), array-to-pointer (conv.array), and function-to-pointer (conv.func) standard conversions are performed on the the expression v. Types shall not be defined in a reinterpret_cast. Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.

-10- An lvalue expression of type T1 can be cast to the type ``reference to T2'' if an expression of type ``pointer to T1'' can be explicitly converted to the type ``pointer to T2'' using a reinterpret_cast. That is, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type. The result is an lvalue for lvalue-references or an rvalue for rvalue-references. No temporary is created, no copy is made, and constructors (class.ctor) or conversion functions (class.conv) are not called.

5.2.11 - Const cast

-1- The result of the expression const_cast<T>(v) is of type T. If T is a reference an lvalue-reference type, the result is an lvalue; otherwise, the result is an rvalue and, the lvalue-to-rvalue (conv.lval), array-to-pointer (conv.array), and function-to-pointer (conv.func) standard conversions are performed on the expression v. Types shall not be defined in a const_cast. Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.

-4- An lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_cast<T2&> (where T1 and T2 are object types) if a pointer to T1 can be explicitly converted to the type pointer to T2 using a const_cast. Similarly, an expression of type T1 can be explicitly converted to an rvalue of type T2 using the cast const_cast<T2&&> (where T1 and T2 are object types) if a pointer to T1 can be explicitly converted to the type pointer to T2 using a const_cast. The result of a reference const_cast refers to the original object.

5.4 - Explicit type conversion (cast notation)

-1- The result of the expression (T) cast-expression is of type T. The result is an lvalue if T is a reference an lvalue-reference type, otherwise the result is an rvalue. [Note: if T is a non-class type that is cv-qualified, the cv-qualifiers are ignored when determining the type of the resulting rvalue; see basic.lval. ]

5.16 - Conditional operator

-3- Otherwise, if the second and third operand have different types ...

6.6.3 - The return statement

-2- A return statement without an expression can be used only in functions that do not return a value, that is, a function with the return type void, a constructor (class.ctor), or a destructor (class.dtor). A return statement with an expression of non-void type can be used only in functions returning a value; the value of the expression is returned to the caller of the function. The expression is implicitly converted to the return type of the function in which it appears. A return statement can involve the construction and copy of a temporary object (class.temporary). Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.

If all of the following conditions are satisfied:

the return expression is considered to be an rvalue for the process of overload resolution used to determine the constructor to be called to return the function result. [Note: Since the local variable is about to be destroyed anyway, this allows the (probably more efficient) move constructor to be called as an optimization; there is no need to preserve the local variable's contents by calling a copy constructor. An implementation is allowed to elide the construction in certain cases (class.copy) --- end note].

[Example:


struct A {
    A();
    A(A&);
    A(A&&);
};

A f()
{
    A a;
    return a;  // calls A::A(A&&) to copy result
               // (possibly omitted)
}

--- end example]

7.1.3 - The typedef specifier

-9- If a typedef TD names a type "lvalue-reference to cv1 S," an attempt to create the type "(lvalue or rvalue) reference to cv2 TD" creates the type "lvalue-reference to cv12 S," where cv12 is the union of the cv-qualifiers cv1 and cv2. If a typedef TD names a type "rvalue-reference to cv1 S," an attempt to create the type "lvalue-reference to cv2 TD" creates the type "lvalue-reference to cv12 S," where cv12 is the union of the cv-qualifiers cv1 and cv2. If a typedef TD names a type "rvalue-reference to cv1 S," an attempt to create the type "rvalue-reference to cv2 TD" creates the type "rvalue-reference to cv12 S," where cv12 is the union of the cv-qualifiers cv1 and cv2. Redundant qualifiers are ignored in all three of the above cases.
[Example:


int i;
typedef int & RI;
RI & r1 = i; / / r1 has the type int&
const RI & r2 = i; / / r2 has the type const int&


int i;
typedef int&  LRI;
typedef int&& RRI;
LRI& r1 = i;            // r1 has the type int&
const LRI& r2 = i;      // r2 has the type const int&
const LRI&& r3 = i;     // r3 has the type const int&
RRI& r4 = i;            // r4 has the type int&
RRI&& r5 = i;           // r5 has the type int&&

--end example]

8 - Declarators

-4- Declarators have the syntax

declarator:
    direct-declarator
    ptr-operator declarator

direct-declarator:
    declarator-id
    direct-declarator ( parameter-declaration-clause ) cv-qualifier-seqopt exception-specificationopt
    direct-declarator [ constant-expressionopt ]
    ( declarator )

ptr-operator:
    * cv-qualifier-seqopt
    &
    &&
    ::opt nested-name-specifier * cv-qualifier-seqopt

cv-qualifier-seq:
    cv-qualifier cv-qualifier-seqopt
...

8.3.2 - References

-1- In a declaration T D where D has the form

& D1
or
&& D1

and the type of the identifier in the declaration T D1 is ``derived-declarator-type-list T,'' then the type of the identifier of D is ``derived-declarator-type-list reference to T.'' Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef (dcl.typedef) or of a template type argument (temp.arg), in which case the cv-qualifiers are ignored. [Example: in

typedef int& A;
const A aref = 3;               //  ill-formed;
                //  non-const  reference initialized with rvalue

the type of aref is ``reference to int'', not ``const reference to int''.] [Note: a reference can be thought of as a name of an object. ] A declarator that specifies the type ``reference to cv void'' is ill-formed.

A reference type that is declared using & is called an lvalue-reference and a reference that is declared using && is called an rvalue-reference. Lvalue- and rvalue-references are distinct types. Except where explicitly noted they are semantically equivalent and commonly referred to as references.

8.5.3 - References

Note: The changes proposed for core issue 391 are reflected in the "old" text below.

-1- A variable declared to be a T& or T&&, that is ``reference to type T'' (dcl.ref), shall be initialized by an object, or function, of type T or by an object that can be converted into a T.

-5- A reference to type ``cv1 T1'' is initialized by an expression of type ``cv2 T2'' as follows:

12.8 - Copying class objects

-2- A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments (dcl.fct.default).*

[Footnote: Because a template constructor or constructor with a first parameter that is an rvalue reference is never a copy constructor, the presence of such a template constructor does not suppress the implicit declaration of a copy constructor. TemplateSuch constructors participate in overload resolution with other constructors, including copy constructors, and, if selected, will a template constructor may be used to copy an object if it provides a better match than other constructors. --- end foonote]

[Example: X::X(const X&) and X::X(X&, int=1) are copy constructors.

class X {
    //  ...
public:
    X(int);
    X(const X&, int = 1);
};
X a(1);                         //  calls  X(int);
X b(a, 0);                      //  calls  X(const   X&,   int);
X c = b;                        //  calls  X(const   X&,   int);

--- end example] [Note: all forms of copy constructor may be declared for a class. [Example:

class X {
        //  ...
public:
        X(const X&);
        X(X&);                  //  OK
};

--- end example]
--- end note] [Note: if a class X only has a copy constructor with a parameter of type X&, an initializer of type const X or volatile X cannot initialize an object of type (possibily cv-qualified) X. [Example:

struct X {
        X();                    //  default constructor
        X(X&);                  //  copy constructor with a nonconst parameter
};
const X cx;
X x = cx;                       //  error -  X::X(X&)  cannot copy  cx  into  x

--- end example]
--- end note]

-9- A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.*

[Footnote: Because a template assignment operator or an assignment operator taking an rvalue reference parameter is never a copy assignment operator, the presence of such a template an assignment operator does not suppress the implicit declaration of a copy assignment operator. TemplateSuch assignment operators participate in overload resolution with other assignment operators, including copy assignment operators, and, if selected will a template assignment operator may be used to assign an object if it provides a better match than other assignment operators. --- end foonote]

Such assignment operators participate in overload resolution with other assignment operators, including copy assignment operators, and, if selected, will be used to assign an object.

[Note: an overloaded assignment operator must be declared to have only one parameter; see over.ass. ] [Note: more than one form of copy assignment operator may be declared for a class. ] [Note: if a class X only has a copy assignment operator with a parameter of type X&, an expression of type const X cannot be assigned to an object of type X. [Example:

struct X {
        X();
        X& operator=(X&);
};
const X cx;
X x;
void f() {
        x = cx;                 //  error:
                                //   X::operator=(X&)  cannot assign  cx  into  x
}

--- end example]
--- end note]

-15- When certain criteria are met, an implementation is allowed to omit the copy construction (or other constructor chosen by overload resolution) of a class object, even if the copy omitted constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy operation construction as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.*

[Footnote: Because only one object is destroyed instead of two, and one copy constructor is not executed, there is still one object destroyed for each one constructed. --- end foonote]

This elision of copy operations constructions is permitted in the following circumstances (which may be combined to eliminate multiple copies constructions):

[Example:

class Thing {
public:
    Thing();
    ~Thing();
    Thing(const Thing&);
};

Thing f() {
    Thing t;
    return t;
}

Thing t2 = f();

Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing: the copying of the local automatic object t into the temporary object for the return value of function f() and the copying of that temporary object into object t2. Effectively, the construction of the local object t can be viewed as directly initializing the global object t2, and that object's destruction will occur at program exit.
--- end example]

[Example:


class Thing {
public:
    Thing();
    ~Thing();
    Thing(Thing&&);
    Thing(const Thing&);
};

Thing f() {
    Thing t;
    return t;
}

Thing t2 = f();

In this example overload resolution chooses the Thing(Thing&&) constructor (see [stmt.return]), and it is this constructor, not the copy constructor, that may be omitted.
--- end example]

13.3.1 - Candidate functions and argument lists

-5- During overload resolution, the implied object argument is indistinguishable from other arguments. The implicit object parameter, however, retains its identity since conversions on the corresponding argument shall obey these additional rules:

13.3.1.4 - Copy-initialization of class by user-defined conversion

-1- Under the conditions specified in dcl.init, as part of a copy-initialization of an object of class type, a user-defined conversion can be invoked to convert an initializer expression to the type of the object being initialized. Overload resolution is used to select the user-defined conversion to be invoked. Assuming that ``cv1 T'' is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:

13.3.1.5 - Initialization by conversion function

-1- Under the conditions specified in dcl.init, as part of an initialization of an object of nonclass type, a conversion function can be invoked to convert an initializer expression of class type to the type of the object being initialized. Overload resolution is used to select the conversion function to be invoked. Assuming that ``cv1 T'' is the type of the object being initialized, and ``cv S'' is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

13.3.1.6 Initialization by conversion function for direct reference binding

-1- Under the conditions specified in dcl.init.ref, a reference can be bound directly to an lvalue that is the result of applying a conversion function to an initializer expression. Overload resolution is used to select the conversion function to be invoked. Assuming that ``cv1 T'' is the underlying type of the reference being initialized, and ``cv S'' is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

13.3.3.1.4 - Reference binding

-3- A standard conversion sequence cannot be formed if it requires binding a reference an lvalue-reference to non-const to an rvalue (except when binding an implicit object parameter; see the special rules for that case in over.match.funcs). [Note: this means, for example, that a candidate function cannot be a viable function if it has a non-const lvalue-reference parameter (other than the implicit object parameter) and the corresponding argument is a temporary or would require one to be created to initialize the lvalue-reference (see dcl.init.ref). ]

-4- Other restrictions on binding a reference to a particular argument do not affect the formation of a standard conversion sequence, however. [Example: a function with a ``lvalue-reference to int'' parameter can be a viable candidate even if the corresponding argument is an int bit-field. The formation of implicit conversion sequences treats the int bit-field as an int lvalue and finds an exact match with the parameter. If the function is selected by overload resolution, the call will nonetheless be ill-formed because of the prohibition on binding a non-const lvalue-reference to a bit-field (dcl.init.ref). ]

13.3.3.2 Ranking implicit conversion sequences

-3- Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules apply:

...

14.3.1 - Template type arguments

-4- If a template-argument for a template-parameter T names a type "lvalue-reference to cv1 S," an attempt to create the type "(lvalue or rvalue) reference to cv2 T" creates the type "lvalue-reference to cv12 S," where cv12 is the union of the cv-qualifiers cv1 and cv2. If the template-argument names a type "rvalue-reference to cv1 S," an attempt to create the type "lvalue-reference to cv2 T" creates the type "lvalue-reference to cv12 S." If the template-argument names a type "rvalue-reference to cv1 S," an attempt to create the type "rvalue-reference to cv2 T" creates the type "rvalue-reference to cv12 S." Redundant cv-qualifiers are ignored. [Example:

template <class T> class X {
    f(const T&);
    g(T&&);
    /* ... */
};
X<int&> x1;         // X<int&>::f has the parameter type const int&
                    // X<int&>::g has the parameter type int&
X<const int&&> x2;  // X<const int&&>::f has the parameter type const int&
                    // X<const int&&>::g has the parameter type const int&&

--end example]

14.8.2.1 - Deducing template arguments from a function call

-1- Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below.

If P is an rvalue-reference type of the form cv T&& where T is a template type-parameter, and the argument is an lvalue, the deduced template argument value for T is A&. [Example:


template<typename T> int f(T&&);
int i;
int j = f(i);    // calls f<int&>(i)

--- end example]

-2- If P is not a reference type: ...