Document number: N1821=05-0081
Date: 2005-08-24
Daveed Vandevoorde daveed@vandevoorde.com
Bronek Kozicki brok@rubikon.pl
 
 

Extending Move Semantics To *this (Revision 1)


Introduction

The this expression is really bound as a hidden *this parameter of reference type (WP 13.3.1/4). For example, a const member function of class X is a member function whose *this parameter has type X const&. A special case overload resolution rule also specifies that non-const member functions can bind *this to rvalues (even though the reference is otherwise an ordinary reference; WP 13.3.1/5, 3rd bullet). This occasionally produces surprises.

N1855 proposes the addition of "rvalue-references" (in addition to ordinary "lvalue-references") in support of move semantics and perfect forwarding. However, N1855 does not extend the binding choice implied by the two distinct kinds of references to *this. This proposal endeavours to extend the binding choice to *this. Doing so provides a mechanism to prevent the surprises alluded to, and also extends the benefits of move semantics to the implicit *this parameter.

Proposal (informal)

Allow the following two overloadable member function forms:

struct X {
    R f(...) cv &;   // Bind *this as an lvalue reference
    R f(...) cv &&;  // Bind *this as an rvalue reference
};

In either case, the *this parameter is bound exactly as an ordinary parameter. The & and && suffixes are allowed only on members that allow a cv-qualifier in the same location (e.g., not constructors!). Additionally, neither of these forms can be overloaded with the existing form (the latter retains its binding semantics, including the exception that allows rvalues to be bound). Suffixes && and & affect the type of hidden *this object parameter, enabling overloading based on lvalueness the target expression. For example:

struct Y {
  R f(...);         // Okay
  R g(...) &;       // Error: Cannot mix forms
  R g(...) const;   // Error: Cannot mix forms
  R h(...);         // Error: Cannot mix forms
  R h(...) &&;      // Error: Cannot mix forms
  R i(...) &&;      // Okay, hidden *this parameter will be Y&&
  R i(...) &;       // Okay, hidden *this parameter will be Y&
  R i(...) const &; // Okay, hidden *this parameter will be const Y&
  R j(...) &;       // Okay, hidden *this parameter will be Y&
};

Examples

Prevent surprises:

struct S {
  S* operator &() &;            // Selected for lvalues only
  S& operator=(S const&) &;     // Selected for lvalues only
};
int main() { S* p = &S();                  // Error! S() = S();                    // Error! }

Enable move semantics:

class X {
   std::vector<char> data_;
public:
   // ...
   std::vector<char> const & data() const & { return data_; }
   std::vector<char> && data() && { return data_; }
};

X f();

// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move

Proposed wording (formal)

As this proposal is based on rvalue-references proposal, following refers to wording proposed by document N1855, not the current draft

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
    & cv-qualifier-seqopt
    && cv-qualifier-seqopt

8.3.5 - Functions [dcl.fct]

...

-4- A cv-qualifier-seq shall only be part of the function type for a nonstatic member function, the function type to which a pointer to member refers, or the top-level function type of a function typedef declaration. The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type, i.e., it does not create a cv-qualified function type. In fact, if at any time in the determination of a type a cv-qualified function type is formed, the program is ill-formed. [Example:

typedef void F();
struct S {
	const F f;              //  ill-formed:
				//  not equivalent to:  void   f()   const;
};

--- end example] The return type, the parameter type list and the cv-qualifier-seq, but not the default arguments (dcl.fct.default) , & lvalue-qualifier, && rvalue-qualifier (class.mfct.nonstatic) or the exception specification (except.spec), are part of the function type. [Note: function types are checked during the assignments and initializations of pointer-to-functions, reference-to-functions, and pointer-to-member-functions. ]

9.3.1 - Nonstatic member functions [class.mfct.nonstatic]

...

-3- A nonstatic member function may be declared const, volatile, or const volatile. These cv-qualifiers affect the type of the this pointer (class.this). They also affect the function type (dcl.fct) of the member function; a member function declared const is a const member function, a member function declared volatile is a volatile member function and a member function declared const volatile is a const volatile member function. [Example:

struct X {
	void g() const;
	void h() const volatile;
};
X::g is a const member function and X::h is a const volatile member function. ]

-4- A nonstatic member function may be declared virtual (class.virtual) or pure virtual (class.abstract).

-5- A nonstatic member function may be qualified by one of && rvalue-qualifier or & lvalue-qualifier. These qualifiers affect the type of implicit object parameter passed to non-static member functions (over.match.funcs)

13.1 - Overloadable declarations [over.load]

...

-2- Certain function declarations cannot be overloaded:

13.3.1 - Candidate functions and argument lists

...

-4- For non-static member functions, the type of the implicit object parameter is ``reference to cv X'', for non-static functions qualified with && rvalue-qualifier (class.mfct.nonstatic), the type of the implicit object parameter is ``rvalue-reference to cv X'', for non-static functions qualified with & lvalue-qualifier (class.mfct.nonstatic), the type of the implicit object parameter is ``lvalue-reference to cv X'', where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration. [Example: for a const member function of class X, the extra parameter is assumed to have type ``reference to const X''. ] For conversion functions, the function is considered to be a member of the class of the implicit object argument for the purpose of defining the type of the implicit object parameter. For non-conversion functions introduced by a using-declaration into a derived class, the function is considered to be a member of the derived class for the purpose of defining the type of the implicit object parameter. For static member functions, the implicit object parameter is considered to match any object (since if the function is selected, the object is discarded). [Note: no actual type is established for the implicit object parameter of a static member function, and no attempt will be made to determine a conversion sequence for that parameter (over.match.best).]

-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:

For non-static member functions which are not qualified with an rvalue-qualifier or lvalue-qualifier (class.mfct.nonstatic) additional rule applies:

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:

...

...

 
 

Acknowledgements

Many thanks to Howard Hinnant for help in all stages of this proposal and to Peter Dimov for motivating example