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:
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.
-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)).
-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.
-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.
-10- A function call is an lvalue if and only if the result type is a
reference an lvalue-reference.
-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.
...
-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.
-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.
-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.
-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. ]
-3- Otherwise, if the second and third operand have different types ...
-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]
-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]
-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 ...
-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.
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:
[Footnote: This requires a conversion function (class.conv.fct) returning a reference type. --- end foonote](this conversion is selected by enumerating the applicable conversion functions (over.match.ref) and choosing the best one through overload resolution (over.match)),
-- end example]double d = 2.0; double& rd = d; // rd refers to d const double& rcd = d; // rcd refers to d struct A { }; struct B : public A { } b; A& ra = b; // ra refers to A sub-object in b const A& rca = b; // rca refers to A sub-object in b
-- end 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
-- end example]struct A { }; struct B : public A { } b; extern B f(); const A& rca = f(); // Bound to the A sub-object of the B rvalue A&& rcb = f(); // Same as above
-- end example]const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0 double&& rcd3 = 2; // rcd3 refers to temporary with value 2.0 const volatile int cvi = 1; const int& r = cvi; // error: type qualifiers dropped
-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
templateconstructor 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, willa template constructor maybe used to copy an objectif 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&.*
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.[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 templatean 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 willa template assignment operator maybe used to assign an objectif it provides a better match than other assignment operators. --- end foonote]
[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
copyconstructor 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]
-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:
-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:
-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:
-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:
-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). ]
-3- Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules apply:
...
--- end example] or, if not that,int f(const int *); int f(int *); int i; int j = f(&i); // Calls f(int *)
--- end example] or, if not that,int i; int f(); int g(const int &); int g(const int &&); int j = g(i); // Calls g(const int &) int k = g(f()); // Calls g(const int &&) struct A {A& operator<<(int);}; A& operator<<(A&&, char); A() << 1; // Calls A::operator<<(int) A() << 'c'; // Calls operator<<(A&&, char) A a; a << 1; // Calls A::operator<<(int) a << 'c'; // Calls operator<<(A&&, char)
-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]
-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: ...