What Does the Single Ampersand After the Parameter List of a Member Function Declaration Mean

What does the single ampersand after the parameter list of a member function declaration mean?

It means the member will be invoked when the object is an lvalue reference.

[C++11: 9.3.1/5]: A non-static member function may be declared with a ref-qualifier (8.3.5); see 13.3.1.

[C++11: 13.3.1/4]: For non-static member functions, the type of the implicit object parameter is

  • “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
  • “rvalue reference to cv X” for functions declared with the && ref-qualifier

where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration. [..]

(and some more rules that I can't find)

Without a ref-qualifier, the function can always be invoked, regardless of the value category of the expression through which you're invoking it:

struct foo
{
void bar() {}
void bar1() & {}
void bar2() && {}
};

int main()
{
foo().bar(); // (always fine)
foo().bar1(); // doesn't compile because bar1() requires an lvalue
foo().bar2();

foo f;
f.bar(); // (always fine)
f.bar1();
f.bar2(); // doesn't compile because bar2() requires an rvalue
}

Live demo (thanks Praetorian)

What does the & (ampersand) at the end of member function signature mean?

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\n"; }
void bar() & { std::cout << "lvalue Foo\n"; }
void bar() const && { std::cout << "const rvalue Foo\n"; }
void bar() && { std::cout << "rvalue Foo\n"; }
};

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\n"; }
void bar() & { std::cout << "lvalue Foo\n"; }
};

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:

  • Andrzej's C++ blog - Ref-qualifiers

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

  • (4.1) — “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier

  • (4.2) — “rvalue reference to cv X” for functions declared with the && ref-qualifier


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.

How does ampersand in the return type of a function declaration work? [duplicate]

When the & operator is used in a declaration form, preceded by a type it doesn't mean "the address of" but a "reference to" which is essentially an automatically dereferenced pointer with disabled pointer arithmetic.

There are no references in C, so if you want to pass or return by reference, you had to pass a const pointer and dereference it to access the pointed to value. C++ added references to make this concept easier, to prevent accidental walking off the address with pointer arithmetic, and to save the need to dereference the pointer. This makes working with it much easier and resulting in cleaner and more readable syntax.

Meaning of ampersand in method declaration [duplicate]

  1. So what does the ampersand mean in here?

It means ref-qualified member functions:

A non-static member function can be declared with either an lvalue ref-qualifier (the token & after the function name) or rvalue ref-qualifier (the token && after the function name). During overload resolution, non-static cv-qualified member function of class X is treated as a function that takes an implicit parameter of type lvalue reference to cv-qualified X if it has no ref-qualifiers or if it has the lvalue ref-qualifier. Otherwise (if it has rvalue ref-qualifier), it is treated as a function taking an implicit parameter of type rvalue reference to cv-qualified X.

You can define both (lvalue/rvalue ref-qualifier), and the appropriate one will be picked up by overload resolution. Such as:

bool operator ==(const MyClass &right) const &;
bool operator ==(const MyClass &right) const &&;

  1. Isn't decltype(*this) always MyClass& / const MyClass&?

Note the type of *this won't change even in the rvalue ref-qualified function.

Note: unlike cv-qualification, ref-qualification does not change the properties of the this pointer: within a rvalue ref-qualified function, *this remains an lvalue expression.

Interpretation of access decoration of member functions [duplicate]

The qualifiers have the exact same meaning as if they were the qualifiers on the hypothetical implicit object parameter which is passed the object expression of the member access expression.

So, #4 can not be called on a prvalue, because a non-const lvalue reference can not bind to a prvalue, explaining why A{}.f(); doesn't work. (A{} is a prvalue)

The old style without reference qualifier is the odd one. It behaves in overload resolution as if the implicit object parameter was an lvalue reference (const or not depending on that qualifier), but in contrast to normal function parameters it is allowed to bind to rvalues anyway for the purpose of overload resolution.

So to replicate the old style unqualified behavior, you need to specify both the &-qualified overload and the &&-qualified overload (at least if the function is not also const-qualified). (There are likely some corner cases where the two qualified member functions are not 100% equivalent to one unqualified one though. I guess a simple example would be trying to take the address &B::f.)

const& , & and && specifiers for member functions in C++

const& means, that this overload will be used only for const, non-const and lvalue object.

const A a = A();
*a;

& means, that this overload will be used only for non-const object.

A a;
*a;

&& means, that this overload will be used only for rvalue object.

*A();

for more information about this feature of C++11 standard you can read this post What is "rvalue reference for *this"?

What's the ampersand for when used after class name like ostream& operator (...)?

In that case you are returning a reference to an ostream object. Strictly thinking of ampersand as "address of" will not always work for you. Here's some info from C++ FAQ Lite on references.

As far as const goes, const correctness is very important in C++ type safety and something you'll want to do as much as you can. Another page from the FAQ helps in that regard. const helps you from side effect-related changes mucking up your data in situations where you might not expect it.



Related Topics



Leave a reply



Submit