What Does the & (Ampersand) at the End of Member Function Signature Mean

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.

What does && mean at the end of a function signature (after the closing parenthesis)?

This is a ref-value qualifier. Here is a basic example:

// t.cpp
#include <iostream>

struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}

Output:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object

Taken from here.

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 is rvalue reference for *this?

First, "ref-qualifiers for *this" is a just a "marketing statement". The type of *this never changes, see the bottom of this post. It's way easier to understand it with this wording though.

Next, the following code chooses the function to be called based on the ref-qualifier of the "implicit object parameter" of the function:

// t.cpp
#include <iostream>

struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}

Output:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object

The whole thing is done to allow you to take advantage of the fact when the object the function is called on is an rvalue (unnamed temporary, for example). Take the following code as a further example:

struct test2{
std::unique_ptr<int[]> heavy_resource;

test2()
: heavy_resource(new int[500]) {}

operator std::unique_ptr<int[]>() const&{
// lvalue object, deep copy
std::unique_ptr<int[]> p(new int[500]);
for(int i=0; i < 500; ++i)
p[i] = heavy_resource[i];

return p;
}

operator std::unique_ptr<int[]>() &&{
// rvalue object
// we are garbage anyways, just move resource
return std::move(heavy_resource);
}
};

This may be a bit contrived, but you should get the idea.

Note that you can combine the cv-qualifiers (const and volatile) and ref-qualifiers (& and &&).


Note: Many standard quotes and overload resolution explanation after here!

† To understand how this works, and why @Nicol Bolas' answer is at least partly wrong, we have to dig in the C++ standard for a bit (the part explaining why @Nicol's answer is wrong is at the bottom, if you're only interested in that).

Which function is going to be called is determined by a process called overload resolution. This process is fairly complicated, so we'll only touch the bit that is important to us.

First, it's important to see how overload resolution for member functions works:

§13.3.1 [over.match.funcs]

p2 The set of candidate functions can contain both member and non-member functions to be resolved against the same argument list. So that argument and parameter lists are comparable within this heterogeneous set, a member function is considered to have an extra parameter, called the implicit object parameter, which represents the object for which the member function has been called. [...]

p3 Similarly, when appropriate, the context can construct an argument list that contains an implied object argument to denote the object to be operated on.

Why do we even need to compare member and non-member functions? Operator overloading, that's why. Consider this:

struct foo{
foo& operator<<(void*); // implementation unimportant
};

foo& operator<<(foo&, char const*); // implementation unimportant

You'd certainly want the following to call the free function, don't you?

char const* s = "free foo!\n";
foo f;
f << s;

That's why member and non-member functions are included in the so-called overload-set. To make the resolution less complicated, the bold part of the standard quote exists. Additionally, this is the important bit for us (same clause):

p4 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. [...]

p5 During overload resolution [...] [t]he implicit object parameter [...] retains its identity since conversions on the corresponding argument shall obey these additional rules:

  • no temporary object can be introduced to hold the argument for the implicit object parameter; and

  • no user-defined conversions can be applied to achieve a type match with it

[...]

(The last bit just means that you can't cheat overload resolution based on implicit conversions of the object a member function (or operator) is called on.)

Let's take the first example at the top of this post. After the aforementioned transformation, the overload-set looks something like this:

void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'

Then the argument list, containing an implied object argument, is matched against the parameter-list of every function contained in the overload-set. In our case, the argument list will only contain that object argument. Let's see how that looks like:

// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
// kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
// taken out of overload-set

If, after all overloads in the set are tested, only one remains, the overload resolution succeeded and the function linked to that transformed overload is called. The same goes for the second call to 'f':

// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
// taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
// kept in overload-set

Note however that, had we not provided any ref-qualifier (and as such not overloaded the function), that f1 would match an rvalue (still §13.3.1):

p5 [...] For non-static member functions declared without a ref-qualifier, an additional rule applies:

  • 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.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
test t;
t.f(); // OK
test().f(); // OK too
}

Now, onto why @Nicol's answer is atleast partly wrong. He says:

Note that this declaration changes the type of *this.

That is wrong, *this is always an lvalue:

§5.3.1 [expr.unary.op] p1

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

§9.3.2 [class.this] p1

In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*. [...]

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.

C++ member function overloading with & (ampersand)

Do I need to cast somehow?

Yes, you can use static_cast.

static_cast may also be used to disambiguate function overloads by
performing a function-to-pointer conversion to specific type, as in

std::for_each(files.begin(), files.end(),
static_cast<std::ostream&(*)(std::ostream&)>(std::flush));

So you can:

auto f1 = static_cast<int(test::*)()>(&test::error);
auto f2 = static_cast<void(test::*)(int)>(&test::error);

C++: difference between ampersand & and asterisk * in function/method declaration?

Both do the same, but one uses references and one uses pointers.

See my answer here for a comprehensive list of all the differences.

What is the ampersand character at the end of an object type?

The comments right before that line of code are telling you exactly what's going on. The & after a type name indicates that it's a reference type, and the @ before a variable name generates a reference to that variable.

(The @ sign can also be used in C# code to escape keywords for use as variable names but that's not what is happening here. pageBounds is not a C# keyword.)

Note that this is not valid C# syntax -- you cannot take a reference to a local variable in C#, although the CLR supports it. (NOTE: As of C# 7.0, this is no longer true; the syntax is described here, but it does not use the & so this decompiled code is still invalid C#).

Creating a reference to a local variable happens implicitly when you use ref and out parameters, for example, but the keywords are used instead of explicitly typing the parameters as reference. (e.g. if you had an out int x, internally that variable is of type Int32&.) The intent of the code, if it were legal C#, would be that pageBounds and local were the same instance with two different names; anything you do to one happens to the other. So, for example, this illegal code:

Rectangle pageBounds;
Rectangle& local = @pageBounds;
local = new Rectangle();

would be the same as this legal code:

Rectangle pageBounds = new Rectangle();

If you tried to compile the code as-decompiled, you would get an error because the compiler treats & as the bitwise and operator, and will complain that you used a type as if it were a variable. But that's ok because you didn't get it from a C# source file. You decompiled an IL method to get it, and there are a lot of things you can do in IL that are illegal in C#. This happens all the time when you decompile code; you see illegal class and method names for example. It just means that the compiler generated IL based on the original code that does not translate directly back into C#, but behaves the way you wanted. The code you are getting back is simply the decompiler's best attempt to produce C# code from the IL it has.

You can see examples of the sort of code that produces these references in the numerous Jetbrains bug reports about them:

  • http://youtrack.jetbrains.com/issue/DOTP-521
  • http://youtrack.jetbrains.com/issue/DOTP-1077
  • http://youtrack.jetbrains.com/issue/DOTP-524


Related Topics



Leave a reply



Submit