Ref-qualifiers - introduced in C++11
Ref-qualifiers is not C++17 feature (looking at the tag of the question), but was a feature introduced in C++11.
struct Foo
{
void bar() const & { std::cout << "const lvalue Foo
"; }
void bar() & { std::cout << "lvalue Foo
"; }
void bar() const && { std::cout << "const rvalue Foo
"; }
void bar() && { std::cout << "rvalue Foo
"; }
};
const Foo&& getFoo() { return std::move(Foo()); }
int main()
{
const Foo c_foo;
Foo foo;
c_foo.bar(); // const lvalue Foo
foo.bar(); // lvalue Foo
getFoo().bar(); // [prvalue] const rvalue Foo
Foo().bar(); // [prvalue] rvalue Foo
// xvalues bind to rvalue references, and overload resolution
// favours selecting the rvalue ref-qualifier overloads.
std::move(c_foo).bar(); // [xvalue] const rvalue Foo
std::move(foo).bar(); // [xvalue] rvalue Foo
}
Note that an rvalue may be used to initialize a const lvalue reference (and in so expanding the lifetime of the object identified by the rvalue), meaning that if we remove the rvalue ref-qualifier overloads from the example above, then the rvalue value categories in the example will all favour the remaining const &
overload:
struct Foo
{
void bar() const & { std::cout << "const lvalue Foo
"; }
void bar() & { std::cout << "lvalue Foo
"; }
};
const Foo&& getFoo() { return std::move(Foo()); }
int main()
{
const Foo c_foo;
Foo foo;
// For all rvalue value categories overload resolution
// now selects the 'const &' overload, as an rvalue may
// be used to initialize a const lvalue reference.
c_foo.bar(); // const lvalue Foo
foo.bar(); // lvalue Foo
getFoo().bar(); // const lvalue Foo
Foo().bar(); // const lvalue Foo
std::move(c_foo).bar(); // const lvalue Foo
std::move(foo).bar(); // const lvalue Foo
}
See e.g. the following blog post for for a brief introduction:
rvalues cannot invoke non-const
&
overloads
To possibly explain the intent of your recollected quote from the CppCon talk,
"... that the only true way of overloading operator=
..."
we visit [over.match.funcs]/1, /4 & /5 [emphasis mine]:
/1 The subclauses of [over.match.funcs] describe the set of candidate functions and the argument list submitted to overload
resolution in each context in which overload resolution is used. ...
/4 For non-static member functions, the type of the implicit object parameter is
where X
is the class of which the function is a member and cv is the
cv-qualification on the member function declaration. ...
/5 ... For non-static member functions declared without a ref-qualifier, an additional rule applies:
- (5.1) — even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as
in all other respects the argument can be converted to the type of the
implicit object parameter. [?Note: The fact that such an argument is
an rvalue does not affect the ranking of implicit conversion
sequences. —?end note?]
From /5 above, the following overload (where the explicit &
ref-qualifier has been omitted)
struct test
{
test& operator=(const test&) { return *this }
}
allows assigning values to r-values, e.g.
int main()
{
test t1;
t1 = test(); // assign to l-value
test() = t1; // assign to r-value
}
However, if we explicitly declare the overload with the &
ref-qualifier, [over.match.funcs]/5.1 does not apply, and as long we do not supply an overload declared with the &&
ref-qualifier, r-value assignment will not be allowed.
struct test
{
test& operator=(const test&) & { return *this; }
};
int main()
{
test t1;
t1 = test(); // assign to l-value
test() = t1; // error [clang]: error: no viable overloaded '='
}
I won't place any opinion as to whether explicitly including the &
ref-qualifier when declaring custom assignment operator overloads is "the only true way of overload operator=
", but would I dare to speculate, then I would guess that the intent behind such a statement is the exclusion of to-r-value assignment.
As a properly designed assignment operator should arguably never be const
(const T& operator=(const T&) const &
would not make much sense), and as an rvalue may not be used to initialize a non-const lvalue reference, a set of overloads for operator=
for a given type T
that contain only T& operator=(const T&) &
will never proviade a viable overload that can be invoked from a T
object identified to be of an rvalue value category.