Copy Control Flashcards

1
Q

When does initialzation occur

A

Copy initialization happens not only when we define variables using an =, but also when we
• Pass an object as an argument to a parameter of nonreference type
• Return an object from a function that has a nonreference return type
• Brace initialize the elements in an array or the members of an aggregate class

Some class types also use copy initialization for the objects they allocate. For example, the library containers copy initialize their elements when we initialize the container, or when we call an insert or push member (§ 9.3.1, p. 342). By contrast, elements created by an emplace member are direct initialized

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

How does explicit keyword constraints on copy initialization

A
vector v1(10); // ok: direct initialization
vector v2 = 10; // error: constructor that takes a size is explicit
void f(vector); // f's parameter is copy initialized
f(10); // error: can't use an explicit constructor to copy an argument
f(vector(10)); // ok: directly construct a temporary vector from an int

Directly initializing v1 is fine, but the seemingly equivalent copy initialization of v2 is an error, because the vector constructor that takes a single size parameter is explicit. For the same reasons that we cannot copy initialize v2, we cannot implicitly use an explicit constructor when we pass an argument or return a value from a function.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Can compiler bypass copy ctor?

A

During copy initialization, the compiler is permitted (but not obligated) to skip the copy/move constructor and create the object directly.

string null_book = "9-999-99999-9"; // copy initialization
to
string null_book("9-999-99999-9"); // compiler omits the copy constructor

However, even if the compiler omits the call to the copy/move constructor, the copy/move constructor must exist and must be accessible (e.g., not private) at that point in the program.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

What is the best practise for the return value of a assignment operator

A

It is also worth noting that the library generally requires that types stored in a container have assignment operators that return a reference to the left-hand operand.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

What are synthesized copy-assignment operator

A

The compiler generates a synthesized copyassignment operator for a class if the class does not define its own. Analogously to the copy constructor, for some classes the synthesized copy-assignment operator disallows assignment. Otherwise, it assigns each nonstatic member of the right-hand object to the corresponding member of the left-hand object using the copy-assignment operator for the type of that member. Array members are assigned by assigning each element of the array.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

What is the order of destruction for members in a class inside a destructor

A

In a destructor, the function body is executed first and then the members are destroyed. Members are destroyed in reverse order from the order in which they were initialized.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

When is a Destructor called?

A

The destructor is used automatically whenever an object of its type is destroyed:
• Variables are destroyed when they go out of scope.
• Members of an object are destroyed when the object of which they are a part is destroyed.
• Elements in a container—whether a library container or an array—are destroyed when the container is destroyed.
• Dynamically allocated objects are destroyed when the delete operator is applied to a pointer to the object.
• Temporary objects are destroyed at the end of the full expression in which the temporary was created.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Why does a class with a user defined destructor almost certainly needs a copy ctor and copy-assignment operator

A

Consider what would happen if we gave HasPtr a destructor but used the synthesized versions of the copy constructor and copy-assignment operator:

class HasPtr {
public:
HasPtr(const std::string &s = std::string()):
	ps(new std::string(s)), i(0) { }
	~HasPtr() { delete ps; }
// WRONG: HasPtr needs a copy constructor and copy-assignment operator
// other members as before
};

In this version of the class, the memory allocated in the constructor will be freed when a HasPtr object is destroyed. Unfortunately, we have introduced a serious bug! This version of the class uses the synthesized versions of copy and assignment. Those functions copy the pointer member, meaning that multiple HasPtr objects may be pointing to the same memory:

HasPtr f(HasPtr hp) // HasPtr passed by value, so it is copied
{
	HasPtr ret = hp; // copies the given HasPtr
	// process ret
	return ret; // ret and hp are destroyed
}

When f returns, both hp and ret are destroyed and the HasPtr destructor is run on each of these objects. That destructor will delete the pointer member in ret and in hp. But these objects contain the same pointer value. This code will delete that pointer twice, which is an error

HasPtr p("some values");
f(p); // when f completes, the memory to which p.ps points is freed
HasPtr q(p); // now both p and q point to invalid memory!

The memory to which p (and q) points is no longer valid. It was returned to the system when hp (or ret!) was destroyed.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

What is the syntax to hint a compiler to provide a synthesized member function

A

We can explicitly ask the compiler to generate the synthesized versions of the copycontrol members by defining them as = default:

// copy control; use defaults
Sales_data() = default;

We can use = default only on member functions that have a synthesized version (i.e., the default constructor or a copy-control member).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

How to mark a member function as delete-ed

A

Under the new standard, we can prevent copies by defining the copy constructor and copy-assignment operator as deleted functions.

NoCopy() = default; // use the synthesized default constructor
NoCopy(const NoCopy&) = delete; // no copy

Unlike = default, = delete must appear on the first declaration of a deleted function.

Also unlike = default, we can specify = delete on any function (we can use = default only on the default constructor or a copy-control member that the compiler can synthesize). Although the primary use of deleted functions is to suppress the copy-control members, deleted functions are sometimes also useful when we want to guide the function-matching process.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What is the harm in delete-ing a destructor?

A

It is not possible to define an object or delete a pointer to a dynamically allocated object of a type with a deleted destructor.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

When are synthesized copy ctor or copy assignment operator as deleted?

A
For some classes, the compiler defines these synthesized members as deleted functions:
• The synthesized destructor is defined as deleted if the class has a member whose own destructor is deleted or is inaccessible (e.g., private).
• The synthesized copy constructor is defined as deleted if the class has a member whose own copy constructor is deleted or inaccessible. It is also deleted if the class has a member with a deleted or inaccessible destructor.
• The synthesized copy-assignment operator is defined as deleted if a member has a deleted or inaccessible copy-assignment operator, or if the class has a const or reference member.
• The synthesized default constructor is defined as deleted if the class has a member with a deleted or inaccessible destructor; or has a reference member that does not have an in-class initializer or has a const member whose type does not explicitly define a default constructor and that member does not have an in-class initializer.

In essence, these rules mean that if a class has a data member that cannot be default constructed, copied, assigned, or destroyed, then the corresponding member will be a deleted function. It may be surprising that a member that has a deleted or inaccessible destructor causes the synthesized default and copy constructors to be defined as deleted. The reason for this rule is that without it, we could create objects that we could not destroy.

It should not be surprising that the compiler will not synthesize a default constructor for a class with a reference member or a const member that cannot be default constructed. Nor should it be surprising that a class with a const member cannot use the synthesized copy-assignment operator

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

How to “copy-control” pre-c++11

A

Prior to the new standard, classes prevented copies by declaring their copy constructor
and copy-assignment operator as private:

class PrivateCopy {
// no access specifier; following members are private by default
// copy control is private and so is inaccessible to ordinary user code
	PrivateCopy(const PrivateCopy&);
	PrivateCopy &operator=(const PrivateCopy&);
	// other members
	public:
	PrivateCopy() = default; // use the synthesized default constructor
	~PrivateCopy(); // users can define objects of this type but not copy them
};

Because the destructor is public, users will be able to define PrivateCopy objects. However, because the copy constructor and copy-assignment operator are private, user code will not be able to copy such objects. However, friends and members of the class can still make copies. To prevent copies by friends and members, we declare these members as private but do not define them.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Give example for a value-like copy-control for class which dynamically allocates a data member

A

Assignment operators typically combine the actions of the destructor and the copy constructor.

It is crucially important that these actions be done in a sequence that is correct even if an object is assigned to itself. Moreover, when possible, we should also write our assignment operators so that they will leave the left-hand operand in a sensible state should an exception occur

Make our code safe should an exception happen—by first copying the right-hand side. After the copy is made, we’ll free the left-hand side and update the pointer to point to the newly allocated string:

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
	auto newp = new string(*rhs.ps); // copy the underlying string
	delete ps; // free the old memory
	ps = newp; // copy data from rhs into this object
	i = rhs.i;
	return *this; // return this object
}
  • Assignment operators must work correctly if an object is assigned to itself.
  • Most assignment operators share work with the destructor and copy constructor.

A good pattern to use when you write an assignment operator is to first copy the right-hand operand into a local temporary. After the copy is done, it is safe to destroy the existing members of the left-hand operand. Once the lefthand operand is destroyed, copy the data from the temporary into the members of the left-hand operand.

To illustrate the importance of guarding against self-assignment, consider what would happen if we wrote the assignment operator as

// WRONG way to write an assignment operator!
HasPtr&
HasPtr::operator=(const HasPtr &rhs)
{
	delete ps; // frees the string to which this object points
	// if rhs and *this are the same object, we're copying from deleted memory!
	ps = new string(*(rhs.ps));
	i = rhs.i;
	return *this;
}

It is crucially important for assignment operators to work correctly, even when an object is assigned to itself. A good way to do so is to copy the right-hand operand before destroying the left-hand operand.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

How does reference counting work

A

Reference counting works as follows:
• In addition to initializing the object, each constructor (other than the copy constructor) creates a counter. This counter will keep track of how many objects share state with the object we are creating. When we create an object, there is only one such object, so we initialize the counter to 1.
• The copy constructor does not allocate a new counter; instead, it copies the data members of its given object, including the counter. The copy constructor increments this shared counter, indicating that there is another user of that object’s state.
• The destructor decrements the counter, indicating that there is one less user of the shared state. If the count goes to zero, the destructor deletes that state.
• The copy-assignment operator increments the right-hand operand’s counter and decrements the counter of the left-hand operand. If the counter for the left-hand operand goes to zero, there are no more users. In this case, the copyassignment operator must destroy the state of the left-hand operand.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Give example for a pointer-like copy-control for class which dynamically allocates a data member

A

Using a reference count, we can write the pointerlike version of HasPtr as follows:

class HasPtr {
public:
// constructor allocates a new string and a new counter, which it sets to 1
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0), use(new std::size_t(1))
{}
// copy constructor copies all three data members and increments the counter
HasPtr(const HasPtr &p):
ps(p.ps), i(p.i), use(p.use) { ++*use; }
HasPtr& operator=(const HasPtr&);
~HasPtr();
private:
std::string *ps;
int i;
std::size_t *use; // member to keep track of how many objects share *ps
};

The copy constructor (which we defined inside the class) copies all three members from its given HasPtr. This constructor also increments the use member, indicating that there is another user for the string to which ps and p.ps point.

The destructor cannot unconditionally delete ps—there might be other objects pointing to that memory.

HasPtr::~HasPtr()
{
	if (--*use == 0) { // if the reference count goes to 0
		delete ps; // delete the string
		delete use; // and the counter
	}
}

Also, as usual, the operator must handle self-assignment. We do so by incrementing the count in rhs before decrementing the count in the left-hand object.

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.use; // increment the use count of the right-hand operand
if (–
use == 0) { // then decrement this object’s counter
delete ps; // if no other users
delete use; // free this object’s allocated members
}
ps = rhs.ps; // copy data from rhs into this object
i = rhs.i;
use = rhs.use;
return *this; // return this object
}

17
Q

Define a swap function

A

Defining swap is particularly important for classes that we plan to use with algorithms that reorder elements

If a class defines its own swap, then the algorithm uses that class-specific version. Otherwise, it uses the swap function defined by the library.

class HasPtr {
	friend void swap(HasPtr&, HasPtr&);
	// other members as in § 13.2.1 (p. 511)
};
inline
void swap(HasPtr &lhs, HasPtr &rhs)
{
	using std::swap;
	swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
	swap(lhs.i, rhs.i); // swap the int members
}

Unlike the copy-control members, swap is never necessary. However, defining swap can be an important optimization for classes that allocate resources.

if a class has a member that has its own type-specific swap function, calling std::swap would be a mistake. For example, assume we had another class named Foo that has a member named h, which has type HasPtr. If we did not write a Foo version of swap, then the library version of swap would be used. As we’ve already seen, the library swap makes unnecessary copies of the strings managed by
HasPtr.
void swap(Foo &lhs, Foo &rhs)
{
	// WRONG: this function uses the library version of swap, not the HasPtr version
	std::swap(lhs.h, rhs.h);
	// swap other members of type Foo
}

this code would compile and execute. However, there would be no performance difference between this code and simply using the default version of swap. The problem is that we’ve explicitly requested the library version of swap. However, we don’t want the version in std; we want the one defined for HasPtr objects.

The right way to write this swap function is:

void swap(Foo &lhs, Foo &rhs)
{
	using std::swap;
	swap(lhs.h, rhs.h); // uses the HasPtr version of swap
	// swap other members of type Foo
}

Each call to swap must be unqualified. That is, each call should be to swap, not std::swap.

18
Q

How to use swap in copy assignment operator

A

Copy And Swap technique

// note rhs is passed by value, which means the HasPtr copy constructor
// copies the string in the right-hand operand into rhs
HasPtr& HasPtr::operator=(HasPtr rhs)
{
	// swap the contents of the left-hand operand with the local variable rhs
	swap(*this, rhs); // rhs now points to the memory this object had used
	return *this; // rhs is destroyed, which deletes the pointer in rhs
}

In this version of the assignment operator, the parameter is not a reference. Instead, we pass the right-hand operand by value. Thus, rhs is a copy of the right-hand operand. Copying a HasPtr allocates a new copy of that object’s string.

In the body of the assignment operator, we call swap, which swaps the data members of rhs with those in *this. This call puts the pointer that had been in the left-hand operand into rhs, and puts the pointer that was in rhs into *this. Thus, after the swap, the pointer member in *this points to the newly allocated string that is a copy of the right-hand operand.

When the assignment operator finishes, rhs is destroyed and the HasPtr destructor is run. That destructor deletes the memory to which rhs now points, thus freeing the memory to which the left-hand operand had pointed.

The interesting thing about this technique is that it automatically handles self assignment and is automatically exception safe. By copying the right-hand operand before changing the left-hand operand, it handles self assignment in the same was as we did in our original assignment operator. It manages exception safety in the same way as the original definition as well. The only code that might throw is the new expression inside the copy constructor. If an exception occurs, it will happen before we have changed the left-hand operand.

19
Q

What is a rvalue reference

A

An rvalue reference is a reference that must be bound to an rvalue. An rvalue reference is obtained by using && rather than &.

Generally speaking, an lvalue expression refers to an object’s identity whereas an rvalue expression refers to an object’s value.

rvalue references—to expressions that require a conversion, to literals, or to expressions that return an rvalue

We can bind an rvalue reference to these kinds of expressions, but we cannot directly bind an rvalue reference to an lvalue:

int i = 42;
int &r = i; // ok: r refers to i
int &&rr = i; // error: cannot bind an rvalue reference to an lvalue
int &r2 = i * 42; // error: i * 42 is an rvalue
const int &r3 = i * 42; // ok: we can bind a reference to const to an rvalue
int &&rr2 = i * 42; // ok: bind rr2 to the result of the multiplication

Functions that return a nonreference type, along with the arithmetic, relational, bitwise, and postfix increment/decrement operators, all yield rvalues. We cannot bind an lvalue reference to these expressions, but we can bind either an lvalue reference to const or an rvalue reference to such expressions.

Lvalues have persistent state, whereas rvalues are either literals or temporary objects created in the course of evaluating expressions.

Because rvalue references can only be bound to temporaries, we know that
• The referred-to object is about to be destroyed
• There can be no other users of that object

Rvalue references refer to objects that are about to be destroyed. Hence, we can “steal” state from an object bound to an rvalue reference.

Variable expressions are lvalues. It may be surprising, but as a consequence, we cannot bind an rvalue reference to a variable defined as an rvalue reference type:

int &&rr1 = 42; // ok: literals are rvalues
int &&rr2 = rr1; // error: the expression rr1 is an lvalue!

A variable is an lvalue; we cannot directly bind an rvalue reference to a variable even if that variable was defined as an rvalue reference type.

20
Q

Explain std::move

A

Calling move tells the compiler that we have an lvalue that we want to treat as if it were an rvalue. It is essential to realize that the call to move promises that we do not intend to use rr1 again except to assign to it or to destroy it. After a call to move, we cannot make any assumptions about the value of the moved-from object.

We can destroy a moved-from object and can assign a new value to it, but we cannot use the value of a moved-from object.

21
Q

Describe move ctor and move assignment operator

A
Like the copy constructor, the move constructor has an initial parameter that is a
reference to the class type. Differently from the copy constructor, the reference
parameter in the move constructor is an rvalue reference.

We must specify noexcept on both the declaration in the class header and on the definition if that definition appears outside the class.

We need to indicate that a move operation doesn’t throw because of two interrelated facts: First, although move operations usually don’t throw exceptions, they are permitted to do so. Second, the library containers provide guarantees as to what they do if an exception happens. As one example, vector guarantees that if an exception happens when we call push_back, the vector itself will be left unchanged.

push_back on a vector might require that the vector be reallocated.

If reallocation uses a move constructor and that constructor throws an exception after moving some but not all of the elements, there would be a problem. The moved-from elements in the old space would have been changed, and the unconstructed elements in the new space would not yet exist. In this case, vector would be unable to meet its requirement that the vector is left unchanged.

On the other hand, if vector uses the copy constructor and an exception happens, it can easily meet this requirement. In this case, while the elements are being constructed in the new memory, the old elements remain unchanged. If an exception happens, vector can free the space it allocated (but could not successfully construct) and return. The original vector elements still exist. To avoid this potential problem, vector must use a copy constructor instead of a move constructor during reallocation unless it knows that the element type’s move constructor cannot throw an exception. If we want objects of our type to be moved rather than copied in circumstances such as vector reallocation, we must explicity tell the library that our move constructor is safe to use. We do so by marking the move constructor (and move-assignment operator) noexcept.

The move-assignment operator does the same work as the destructor and the move constructor. As with the move constructor, if our move-assignment operator won’t throw any exceptions, we should make it noexcept. Like a copy-assignment operator, a move-assignment operator must guard against self-assignment:

StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
	// direct test for self-assignment
	if (this != &rhs) {
		free(); // free existing elements
		elements = rhs.elements; // take over resources from rhs
		first_free = rhs.first_free;
		cap = rhs.cap;
		// leave rhs in a destructible state
		rhs.elements = rhs.first_free = rhs.cap = nullptr;
	}
	return *this;
}
22
Q

Does move ctor destroy rhs object?

A

Moving from an object does not destroy that object: Sometime after the move operation completes, the moved-from object will be destroyed. Therefore, when we write a move operation, we must ensure that the moved-from object is in a state in which the destructor can be run.

In addition to leaving the moved-from object in a state that is safe to destroy, move operations must guarantee that the object remains valid.

After a move operation, the “moved-from” object must remain a valid, destructible object but users may make no assumptions about its value.

23
Q

When is move ctor synthesized

A

The compiler synthesizes the move constructor and move assignment only if a class does not define any of its own copy-control members and only if all the data members can be moved constructed and move assigned, respectively

  • Unlike the copy constructor, the move constructor is defined as deleted if the class has a member that defines its own copy constructor but does not also define a move constructor, or if the class has a member that doesn’t define its own copy operations and for which the compiler is unable to synthesize a move constructor. Similarly for move-assignment.
  • The move constructor or move-assignment operator is defined as deleted if the class has a member whose own move constructor or move-assignment operator is deleted or inaccessible.
  • Like the copy constructor, the move constructor is defined as deleted if the destructor is deleted or inaccessible.
  • Like the copy-assignment operator, the move-assignment operator is defined as deleted if the class has a const or reference member.

Classes that define a move constructor or move-assignment operator must also define their own copy operations. Otherwise, those members are deleted by default.

24
Q

What happens to rvalue rhs in initialization or assignment if there is no move ctor ( or assignment )?

A

If a class has a usable copy constructor and no move constructor, objects will be “moved” by the copy constructor. Similarly for the copy-assignment operator and move-assignment.

class Foo {
public:
	Foo() = default;
	Foo(const Foo&); // copy constructor
	// other members, but Foo does not define a move constructor
};
Foo x;
Foo y(x); // copy constructor; x is an lvalue
Foo z(std::move(x)); // copy constructor, because there is no move constructor
25
Q

Describe move iterator

A

The new library defines a move iterator adaptor. A move iterator adapts its given iterator by changing the behavior of the iterator’s dereference operator. Ordinarily, an iterator dereference operator returns an lvalue reference to the element. Unlike other iterators, the dereference operator of a move iterator yields an rvalue reference.

We transform an ordinary iterator to a move iterator by calling the library make_move_iterator function. This function takes an iterator and returns a move iterator. we can pass a pair of move iterators to an algorithm

// move the elements
auto last = uninitialized_copy(make_move_iterator(begin()), make_move_iterator(end()), first);

Outside of class implementation code such as move constructors or move-assignment operators, use std::move only when you are certain that you need to do a move and that the move is guaranteed to be safe.

26
Q

Can we overload a move enabled function?

A

move-enabled members typically use the same parameter pattern as the copy/move constructor and the assignment operators—one version takes an lvalue reference to const, and the second takes an rvalue reference to nonconst.

void push_back(const X&); // copy: binds to any kind of X
void push_back(X&&); // move: binds only to modifiable rvalues of type X

Overloaded functions that distinguish between moving and copying a parameter typically have one version that takes a const T& and one that takes a T&&.

vec. push_back(s); // calls push_back(const string&)
vec. push_back(“done”); // calls push_back(string&&)

27
Q

How to prevent member function call on rvalue reference?

A

Ordinarily, we can call a member function on an object, regardless of whether that object is an lvalue or an rvalue

string s1 = “a value”, s2 = “another”;
auto n = (s1 + s2).find(‘a’);

Here, we called the find member on the string rvalue that results from adding two strings. Sometimes such usage can be surprising:

s1 + s2 = “wow!”;

We indicate the lvalue/rvalue property of this in the same way that we define const member , we place a reference qualifier after the parameter list:

class Foo {
public:
	Foo &operator=(const Foo&) &; // may assign only to modifiable lvalues
	// other members of Foo
};
Foo &Foo::operator=(const Foo &rhs) &
{
	// do whatever is needed to assign rhs to this object
	return *this;
}
Foo &retFoo(); // returns a reference; a call to retFoo is an lvalue
Foo retVal(); // returns by value; a call to retVal is an rvalue
Foo i, j; // i and j are lvalues
i = j; // ok: i is an lvalue
retFoo() = j; // ok: retFoo() returns an lvalue
retVal() = j; // error: retVal() returns an rvalue
i = retVal(); // ok: we can pass an rvalue as the right-hand operand to assignment

A function can be both const and reference qualified. In such cases, the reference qualifier must follow the const qualifier:

class Foo {
public:
	Foo someMem() & const; // error: const qualifier must come first
	Foo anotherMem() const &; // ok: const qualifier comes first
};
28
Q

What are the constraints on reference qualified member function while overloading

A
class Foo {
public:
	Foo sorted() &&; // may run on modifiable rvalues
	Foo sorted() const &; // may run on any kind of Foo
	// other members of Foo
private:
	vector data;
};
// this object is an rvalue, so we can sort in place
Foo Foo::sorted() &&
{
	sort(data.begin(), data.end());
	return *this;
}
// this object is either const or it is an lvalue; either way we can't sort in place
Foo Foo::sorted() const & {
	Foo ret(*this); // make a copy
	sort(ret.data.begin(), ret.data.end()); // sort the copy
	return ret; // return the copy
}

When we run sorted on an rvalue, it is safe to sort the data member directly. The object is an rvalue, which means it has no other users, so we can change the object itself. When we run sorted on a const rvalue or on an lvalue, we can’t change this object, so we copy data before sorting it.

Overload resolution uses the lvalue/rvalue property of the object that calls sorted to determine which version is used:

retVal().sorted(); // retVal() is an rvalue, calls Foo::sorted() &&
retFoo().sorted(); // retFoo() is an lvalue, calls Foo::sorted() const &

When we define const memeber functions, we can define two versions that differ only in that one is const qualified and the other is not. There is no similar default for reference qualified functions. When we define two or more members that have the same name and the same parameter list, we must provide a reference qualifier on all or none of those functions:

class Foo {
public:
	Foo sorted() &&;
	Foo sorted() const; // error: must have reference qualifier
	// Comp is type alias for the function type (see § 6.7 (p. 249))
	// that can be used to compare int values
	using Comp = bool(const int&, const int&);
	Foo sorted(Comp*); // ok: different parameter list
	Foo sorted(Comp*) const; // ok: neither version is reference qualified
};

If a member function has a reference qualifier, all the versions of that member with the same parameter list must have reference qualifiers.