This revision of N1855 has improved wording for a few sections based on the invaluable advice of the CWG obtained at the Mont Tremblant meeting.
The specific revisions to N1855 are:
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.
-1- Compound types can be constructed in the following ways:
-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 ``lvalue-reference to T'' (dcl.ref, dcl.init.ref), the type is adjusted to ``T'' prior to any further analysis, the expression designates the object or function denoted by the lvalue-reference, and the expression is an lvalue.
-7- If an expression initially has the type ``rvalue-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 rvalue-reference. If the expression is the result of
calling a function, whether implicitly or explicitly, it is an rvalue;
otherwise, it is an lvalue.
[Example:
struct A {}; A&& operator+(A,A); A&& f(); ... A a; A&& ar = a;
The expressions f() and a + a are rvalues. The expression
ar is an lvalue of type A.
--end example]
-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 has 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.
-11- 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 (and similarly for
reinterpret_cast<T&&>(x)). 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. ]
-5- The conversions performed by
can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply, with the exception that in performing a static_cast in the following situations the conversion is valid even if the base class is inaccessible:
...
-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). A copy operation associated with a return statement may be elided or considered as an rvalue for the purpose of overload resolution in selecting a constructor (12.8). 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.
-9- If a typedef TD names a type ``reference to cv1 S''
that is an lvalue-reference or rvalue-reference to a type T,
an attempt to create the type ``reference to cv2 TD''
``lvalue-reference to cv TD'' creates the
type ``reference to cv12 S'', where cv12 is the
union of the cv-qualifiers cv1 and cv2.
Redundant qualifiers are ignored ``lvalue-reference to T'',
while an attempt to create the type ``rvalue-reference to cv TD''
creates the type TD.
[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 int& const LRI&& r3 = i; // r3 has the type 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 either of the forms
& D1 && 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 type that is declared using && is called an rvalue-reference. Lvalue-references 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 double&& rd4 = i; // OK, reference bound to temporary double
-- 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 a constructor whose first parameter 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 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 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 ...
-16- When the above criteria are met for omitting the copy operation of a class object, and if the class type designated by the expression (say cv1 T) has a constructor whose first parameter is cv2 T&&, where cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and all other parameters have default arguments, then the expression is considered to be an rvalue for the process of overload resolution used to select the constructor for the copy operation.
[Example:
class Thing { public: Thing(); ~Thing(); Thing(Thing&&); private: Thing(const Thing&); }; Thing f(bool b) { Thing t; if (b) throw t; // OK, Thing(Thing&&) used (or elided) to throw t return t; // OK, Thing(Thing&&) used (or elided) to return t } Thing t2 = f(); // OK, Thing(Thing&&) used (or elided) for construction of t2
--- 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 ``reference to cv1 S'' that is an
lvalue-reference or rvalue-reference to a type A, an attempt to
create the type ``reference to cv2 T''
``lvalue-reference to cv T'' creates the type
``reference to cv12 S'', where cv12 is the
union of the cv-qualifiers cv1 and cv2. Redundant
cv-qualifiers are ignored ``lvalue-reference to A'', while
an attempt to create the type ``rvalue-reference to cv T''
creates the type T. [Example:
template <class T> class X { f(const T&); g(T&&); /* ... */ }; X<int&> x1; // X<int&>::f has the parameter typeconstint& // 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]
-3- If P is a cv-qualified type, the top level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. If P is an rvalue-reference type and the argument is an lvalue, the type A& is used in place of A for type deduction. [Example:
template<typename T> int f(T&&); int i; int j = f(i); // calls f<int&>(i)
--- end example]
-8- A template type argument T, a template template argument TT or a template non-type argument i can be deduced if P and A have one of the following forms:
T cv-list T T* T& T&& T[integer-constant] ...
Many thanks to William M. (Mike) Miller, Edison Design Group, Inc, for the excellent review and suggestions.