Do you want to get a C++ question every day to get prepared for your next interview? Sign up for DailyCppInterview for free!
In one of my previous articles, I wrote about Scott Meyer's Effective Modern C++ and that with its focus on C++11/14 it's like discovering a completely new language. I already wrote about trailing return type declaration. Now it's time to review what usages you might have in C++ for ampersands (&
).
Let's start with the good old, better-known usages:
-
&
to declare a reference to a type -
&
to get the address of a variable -
&
as a bit-wise operator -
&&
in a conditional expression
These are not new, but "repetition is the mother of learning".
Use &
to declare a reference to a type
If you use &
in the left-hand side of a variable declaration, it means that you expect to have a reference to the declared type. It can be used in any type of declarations (local variables, class members, method parameters).
std::string mrSamberg("Andy");
std::string& theBoss = mrSamberg;
This doesn't just mean that both mrSamberg
and theBoss
will have the same value, but they will actually point to the same place in the memory. You can read more about references here.
Use &
to get the address of a variable
The meaning of &
changes if you use it in the right-hand side of an expression. In fact, if you use it on the left-hand side, it must be used in a variable declaration, on the right-hand side, it can be used in assignments too.
When using it on the right-hand side of a variable, it's also known as the "address-of operator". Not surprisingly if you put it in front of a variable, it'll return its address in the memory instead of the variable's value itself. It is useful for pointer declarations.
std::string mrSamberg("Andy");
std::string* theBoss;
theBoss = &mrSamberg;
The end result of the previous snippet is the same as previously. Although the type of theBoss
is different. Previously it was a reference, now it's a pointer. The main difference is that a pointer can be null, while a reference must point to a valid value. (Well... There are shortcuts... But that's beyond our scope in this article.). More on this topic here.
Use &
as a bitwise operator
It is the bitwise AND. Its an infix operator taking two numbers as inputs and doing an AND
on each of the bit pairs of the inputs. Here is an example. 14 is represented as 1110 as a binary number and 42 can be written as 101010. So 1110 (14) will be zero filed from the left and then the operation goes like this.
32 | 16 | 8 | 4 | 2 | 1 | |
---|---|---|---|---|---|---|
14 | 0 | 0 | 1 | 1 | 1 | 0 |
42 | 1 | 0 | 1 | 0 | 1 | 0 |
14&42=10 | 0 | 0 | 1 | 0 | 1 | 0 |
Use &&
in a logical expression
&&
in a (logical) expression is just the C-style way to say and
. That's it.
Use &&
for declaring rvalue references
Declaring a what? - you might ask. Okay, so let's clarify first what are lvalues and rvalues and what are the differences.
According to Eli Bendersky:
An lvalue (locator value) represents an object that occupies some identifiable location in memory (i.e. has an address).
rvalues are defined by exclusion, by saying that every expression is either an lvalue or an rvalue. Therefore, from the above definition of lvalue, an rvalue is an expression that does not represent an object occupying some identifiable location in memory.
Let's take one example to show both an lvalue and an rvalue.
auto mrSamberg = std::string{"Andy"};
mrSamberg
represents an lvalue. It points to a specific place in the memory which identifies an object. On the other hand, what you can find on the right side std::string{"Andy"}
is actually an rvalue. It's an expression that can't have a value assigned to, that's already the value itself. It can be only on the right-hand side of an assignment operator.
For a better and deeper explanation, please read Eli's article.
Although rvalues can only appear on the right-hand side, still one can capture references to them. Those "captures" are called rvalue references and such variables have to be declared with double ampersands (&&
). Binding such temporaries are needed to implement move semantics and perfect forwarding. (I'll explain perfect forwarding and move semantics in a later article.)
Use &&
for declaring universal references
The bad news is that &&
after a type might or might not mean that you are declaring an rvalue reference. In certain circumstances, it only means something that [Scott Meyers] calls a universal reference in his Effective Modern C++.
What are those circumstances? Briefly, if type deduction takes place, you declare a universal reference, if not an rvalue reference.
Vehicle car;
auto&& car2 = car; // type deduction! this is a universal reference!
Vehicle&& car3 = car; // no type deduction, so it's an rvalue reference
There is another possibility, it's in case of templates. Taking the example from Effective Modern C++:
template<typename T>
void f(std::vector<T>&& param); // rvalue reference
template<typename T>
void f(T&& param); // type deduction occurs, so this is a universal reference!
There are more subtilities in the case of templates, but again, it's beyond the scope. Read Item 24 from Effective Modern C++ in case you want to learn more about how to distinguish universal references from rvalue references.
Use &
or &&
for function overloading
We are not finished yet.
Since C++11 you can use both the single and double ampersands as part of the function signature, but not part of the parameter list. If I'm not clear enough, let me give the examples:
void doSomething() &;
void doSomething() &&;
auto doSomethingElse() & -> int;
auto doSomethingElse() && -> int;
What this means is that you can limit the use of a member function based on whether *this
is a lvalue or an rvalue. So you can only use this feature within classes, of course. Let's expand our example.
class Tool {
public:
// ...
void doSomething() &; // used when *this is a lvalue
void doSomething() &&; // used when *this is a rvalue
};
Tool makeTool(); //a factory function returning an rvalue
Tool t; // t is an lvalue
t.doSomething(); // Tool::doSomething & is called
makeTool().doSomething(); // Tool::doSomething && is called
When would you use this kind of differentiation? Mostly when you'd like to optimize your memory footprint by taking advantage of move semantics. In a later post, I'll go more deeply on that.
Conclusions
In this post, you saw 7 different type of usages the ampersands in C++. They can be used in single or double form, in variable declarations, function declarations and conditional expressions.
I didn't intend to give you a full explanation of each. Move semantics and perfect forwarding can fill multiple chapters of good books, like in Effective Modern C++. On the other hand, I'll try to give a deeper explanation on those topics in a later post.
This article has been originally published on my blog.
Top comments (3)
Thanks for helping me get a basic hold of the usages of & in C++
Not related: I wonder what the second line in this note alludes to, though:
"Please leave your appreciation by commenting on this post!
It takes just one minute and is worth it for your career."
:)
Some really nice clarifications in this article. Thanks! I gotta read that "Effective modern C++", it has been recommended to me many times.
PS. You have a typo in "Conclusions" :)
Thanks a lot, Nicolas. I fixed the typo.