Copy Control

A class controls what happens when objects of class type are copied, moved, assigned, and destroyed by defining five special member functions: copy constructor, move constructor, copy-assignment operator, move-assignment operator, destructor. The copy and move constructors define what happens when an object is initialized from another object of the same type. The copy and move assignment operators define what happens when we assign an object of a class type to another object of that same class type. The destructor defines what happens when an object ceases to exist. If a class does not define all of the copy-control members, the compiler automatically defines the missing operations.

The Copy Constructor

A constructor is the copy constructor if its first parameter is a reference to the class type and any additional parameters have default values. The first parameter must be a reference type, and it is almost always a reference to const, although we can define a reference to nonconst. Unlike the synthesized default constructor, a copy constructor is synthesized even if we define other constructors.

1
2
3
4
5
class Foo {
public:
    Foo();              // default constructor
    Foo(const Foo&);    // copy constructor
};

The Synthesized Copy Constructor

The type of each member determines how that member is copied: Members of class type are copied by the copy constructor for that class; members of built-in type are copied directly. Although we cannot directly copy an array, the synthesized copy constructor copies members of array type by copying each element. Elements of class type are copied by using the element’s copy constructor. For example, the synthesized copy constructor:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Sales_data {
public:
    Sales_data(const Sales_data &orig);
private:
    string bookNo;
    int units_sold = 0;
    double revenue = 0.0;
};

Sales_data::Sales_data(const Sales_data &orig):
    bookNo(orig.bookNo), // use the string copy constructor
    units_sold(orig.units_sold),
    revenue(orig.revenue)
{
}

Copy Initialization

We are now in a position to fully understand the differences between direct initialization and copy initialization:

1
2
3
4
5
string dots(10, '.');                // direct initialization
string s(dots);                      // direct initialization
string s2 = dots;                    // copy initialization
string null_book = "9-999-99999-9";  // copy initialization
string nines = string(100, '9');     // copy initialization

When using direct initialization, we are asking the compiler to use ordinary function matching to select the constructor that best matches the arguments we provide. When we use copy initialization, we are asking the compiler to copy the right-hand operand into the object being created, converting that operand if necessary.

Copy initialization ordinary uses the copy constructor. However, if a class has a move constructor, then copy initialization sometimes uses the move constructor instead of the copy constructor. 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 initialization 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. By contrast, elements created by an emplace member are direct initialized.

During a function call, parameters that have a nonreference type are copy initialized. This explains why the copy constructor’s own parameters must be a reference. If that parameter were not a reference, then the call would never succeed.

Whether we use copy or direct initialization matters if we use an initializer that requires conversion by an explicit constructors:

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

If we want to use an explicit constructor, we must do so explicitly.

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

1
string null_book = "9-999-99999-9"; // copy initialization

into

1
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 at that point in the program.

The Copy-Assignment Operator

Overloaded operators are functions that have the name operator followed by the symbol for the operator being defined. Hence, the assignment operator is a function named operator=. The parameters in an overloaded operator represent the operands of the operator. When an operator is a member function, the left-hand operand is bound to implicit this parameter. The copy-assignment operator takes an argument of the same type as the class:

1
2
3
4
class Foo {
public:
    Foo& operator=(const Foo&); // assignment operator
};

To be consistent with assignment for built-in types, assignment operators usually return a reference to left-hand operand. As an example, the floowing is equivalent to the synthesized Sales_data copy-assignment operator:

1
2
3
4
5
6
7
8
// equivalent to the synthesized copy-assignment operator
Sales_data& Sales_data::operator=(const Sales_data &rhs)
{
    bookNo = rhs.bookNo;  // calls the string::operator=
    units_sold = rhs.units_sold;  // uses the built-in int assignment
    revenue = rhs.revenue;   // uses the built-in double
    return *this;  // return a reference to this object
}

The Destructor

Constructors initialize the nonstatic data members of an object and may do other work; destructors do whatever work is needed to free the resources used by an object and destroy the nonstatic data members of the object. The destructor is a member function with the name of the class prefixed by a tilde (~). It has no return value and takes no parameters:

1
2
3
4
class Foo {
public:
    ~Foo();  // destructor
};

In a constructor, members are initialized before the function body is executed, and members are initialized in the same order as they appear in the class. 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.

The built-in types do not have destructors, so nothing is done to destroy members of built-in type.

Unlike ordinary pointers, the smart pointers are class types and have destructors. As a result, members that are smart pointers are automatically destroyed during the destruction phase.

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 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

Note: the destructor is not run when a reference or a pointer to an object goes out of scope.

The members are automatically destroyed after the destructor body is run. It is important to realize that the destructor body does not directly destroy the members. Members are destroyed as part of the implicit destruction phase that follows the destructor body.

The Rule of Copy Control Operations

One rule of thumb to use when you decide whether a class needs to define its own versions of the copy-control members is to decide first whether the class needs a destructor. If the class needs a destructor, it almost surely needs a copy constructor and copy-assignment operator as well. The following example just explain this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

class HasPtr {
public:
    HasPtr(const string &s = string()): ps(new string(s)) { }
    ~HasPtr() { delete ps; }
private:
    string *ps;
};

HasPtr f(HasPtr hp) {
	// copies the given HasPtr, multiple HasPtr objects may be pointing the same memory
    HasPtr ret = hp; 
    // ret and hp are destroyed, so HasPtr destructor will be called twice, this is an error
    return ret; 
}

int main() {
    HasPtr p("some values");
    f(p); // when f completes, the memory is freed
    HasPtr q(p); // now both p and q point to invalid memory
    return 0;
}

To avoid using the synthesized copy constructor and copy-assignment operator for this class, we should define our own version:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class HasPtr {
public:
    HasPtr(const string &s = string()): ps(new string(s)) { }
    ~HasPtr() { delete ps; }
    HasPtr(const HasPtr &p): ps(new string(*p.ps)) { }
    HasPtr& operator =(const HasPtr &p) { ps = new string(*p.ps); }
    void print() { cout << *ps << endl; }
private:
    string *ps;
};

A second rule of thumb: If a class needs a copy constructor, it almost surely needs a copy-assignment operator. And vice versa, if the class needs an assignment operator, it almost surely needs a copy constructor as well. Nevertheless, needing either the copy constructor or the copy-assignment operator does not indicate the need for a destructor.

Using = default

Under the new standard, we can explicitly ask the compiler to generate the synthesized versions of the copy-control members by defining them as = default.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Sales_data {
public:
    // copy control, use default
    Sales_data() = default;
    Sales_data(const Sales_data&) = default;
    Sales_data& operator =(const Sales_data&);
    ~Sales_data() = default;
    // other members
};
Sales_data& Sales_data::operator=(const Sales_data &) = default;

When we specify = default on the declaration in the class body, the synthesized function is implicitly inline (just as any other member function defined in the body of the class). If we don’t want it inline, we can specify = default on the definition.

Preventing Copies

Under the new standard C++ 11, we can prevent copies by defining the copy constructor and copy-assignment operator as deleted functions. A deleted function is one that is declared but may not be used in any other way.

1
2
3
4
5
6
struct NoCopy {
    NoCopy() = default;
    ~NoCopy() = default;
    NoCopy(const NoCopy&) = delete; // no copy
    NoCopy& operator =(const NoCopy&) = delete; // no assignment
};

Unlike = default, = delete must appear on the first declaration of a deleted function. Also, we can specify = delete on any function. But the destructor should not be a deleted member, or that member cannot be destroyed.

For some classes, the compiler defines these synthesized members as deleted functions:

  • synthesized destructor is defined as deleted if class has a member whose own destructor is deleted or is inaccessible (private).
  • 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.
  • 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.
  • 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 is that without it, we could create objects that we could not destroy.

Prior to the new standard, classes prevented copies by declaring their copy constructor and copy-assignment operator as private. However, friends and members of the class can still make copies. To prevent these copies, we declare these members as private but do not define them. An attempt to use an undefined member results in a link-time failure. Classes that want to prevent copying should define their copy constructor and copy-assignment operator using = delete rather than making those members private.

Classes That Act Like Values

To decide what copying an object of type mean, we have two choices: We can define the copy operations to make the class behave like a value or like a pointer. Classes that behave like values have their own state. Classes that act like pointers share state. To illustrate these two approaches, we’ll make a class act like a value, then like a pointer.

To provide valuelike behavior, each object has to have its own copy of the resource that the class manages. There are two points to keep in mind when you write an assignment operator:

  • assignment operator 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 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class HasPtr {
public:
    HasPtr(const string &s = string()): ps(new string(s)), i(0) { }
    // To act like value, copy constructor should copy string, not just the pointer
    HasPtr(const HasPtr &p): ps(new string(*p.ps)), i(p.i) { }
    // To act like value, the copy assignment operator should free the object's
    // existing string and copy the string from its right-hand operand.
    HasPtr& operator =(const HasPtr &rhs);
    ~HasPtr() { delete ps; }
private:
    string *ps;
    int i;
};

HasPtr& HasPtr::operator=(const HasPtr &rhs) {
    auto newp = new string(*rhs.ps);
    delete ps;
    ps = newp;
    i = rhs.i;
    return *this;
}

Classes That Act Like Pointers

The easiest way to make a class act like a pointer is to use shared_ptr to manage the resource in the class. Copying (assigning) a shared_ptr copies (assigns) the pointer to which the shared_ptr points. Sometimes we want to manage a resource directly, so we will do our own reference counting. Reference counting works as follows:

  • each constructor (other than copy constructor) creates a counter that keep track of how many objects share state
  • copy constructor does not allocate a new counter, it just copies data members , including the counter. Then the copy constructor increments this shared counter, indicating that there is another user of that object’s state.
  • the destructor decrements the counter. If the count goes to zero, the destructor deletes that state.
  • 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, we must destroy the state of the left-hand operand.

So where to put the reference count ? One way to solve this problem is to store the counter in dynamic memory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class HasPtr {
public:
    HasPtr(const string &s = string()): ps(new string(s)), i(0), use(new size_t(1)) { }
    HasPtr(const HasPtr &p): ps(p.ps), i(p.i), use(p.use) { ++*use; }
    HasPtr& operator =(const HasPtr &rhs);
    ~HasPtr();
private:
    string *ps;
    int i;
    size_t *use;
};

HasPtr::~HasPtr() {
    if (--*use == 0) {
        delete ps;
        delete use;
    }
}

HasPtr& HasPtr::operator=(const HasPtr &rhs) {
    ++*rhs.use;
    if (--*use == 0) {
        delete ps;
        delete use;
    }
    ps = rhs.ps;
    i = rhs.i;
    use = rhs.use;
    return *this;
}

Swap

In addition to defining the copy-control members, classes that manage resources often also define a function named swap. If a class defines its own swap, then the algorithm which need to exchange two elements uses that class-specific version. Otherwise, it uses the swap function defined by the library. The swap function involves a copy and two assignments:

1
2
3
HasPtr temp = v1; // make a temporary copy of the value of v1
v1 = v2; // assign the value of v2 to v1
v2 = temp; // assign the saved value of v1 to v2

In principle, none of the memory allocation is necessary. Rather than allocating new copies of the string, we’d like swap to swap the pointers. We can override the default behavior of swap by defining a version of swap that operate on our class.

1
2
3
4
5
6
7
8
9
class HasPtr {
    friend void swap(HasPtr&, HasPtr&);
    // other members
};

inline void swap(HasPtr &lhs, HasPtr &rhs){
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
}

Classes that define swap often use swap to define their assignment operator. These operators use a technique known as copy and swap. This technique swaps the left-hand operand with a copy of the right-hand operand:

1
2
3
4
5
HasPtr& HasPtr::operator=(HasPtr rhs) {
    // swap the content of the left-hand operand with 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. Copying a HasPtr allocates a new copy of that object’s string. When it finishes, rhs is destroyed and the destructor deletes the memory to which rhs now points. The interesting thing about this technique is that it automatically handles self assignment and is automatically exception safe.

Classes That Manage Dynamic Memory

Some classes need to allocate a varying amount of storage at run time. Such classes often can use a library container to hold their data. However, this strategy does not work for every class; some classes need to do their own allocation. Such classes generally must define their own copy-control members to manage the memory they allocate. As an example, we’ll implement a simplification of the library vector class.

In our StrVec class, We’ll use an allocator to obtain raw memory. Then we can use the allocator’s construct member to create objects in that space when we need to add an element, and use the destroy member to remove an element. Each StrVec will have three pointers into the space it uses for its elements:

  • elements, which points to the first element in the allocated memory
  • first_free, which points just after the last actual element
  • cap, which points just past the end of the allocated memory

In addition to these pointers, StrVec will have a member named alloc that is an allocator. The alloc member will allocate the memory used by a StrVec. Our class will also have four utility functions:

  • alloc_n_copy will allocate space and copy a given range of elements.
  • free will destroy the constructed elements and deallocate the space.
  • chk_n_alloc will ensure that there is room to add at least one more element to the StrVec. If there isn’t room for another element, chk_n_alloc will call reallocate to get more space.
  • reallocate will reallocate the StrVec when it runs out of space

Copying a string copies the data because ordinarily after we copy a string, there are two users of that string. However, when reallocate copies the strings in a StrVec, there will be only one user of these strings after the copy. So copying the data in these strings is unnecessary.

Under the new standard, we can avoid copying the strings by using two facilities. First, the library classes define “move constructors”. The move constructors operate by “moving” resources from the given object to the object being constructed. For example, the string move constructor copies the pointer rather than allocating space for copying the characters themselves. Second, we’ll use a library function named move, defined in utility header. There are two points about move:

  • When reallocate constructs strings in the new memory, it must call move to signal that it wants to use the string move constructor. If it omits this call, the copy constructor will be used.
  • We usually do not provide a using declaration for move. When we use move, we call std::move, not move.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <iostream>
#include <memory>
using namespace std;

class StrVec {
public:
    StrVec(): elements(nullptr), first_free(nullptr), cap(nullptr) { }
    StrVec(const StrVec& s);
    StrVec& operator =(const StrVec& rhs);
    ~StrVec() { free(); }

    void push_back(const string& s);
    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }
    string* begin() const { return elements; }
    string* end() const { return first_free; }

    void reverse(size_t new_cap);
    void resize(size_t count);
    void resize(size_t count, const string& s);
private:
    allocator<string> alloc;
    void chk_n_alloc() { if (size() == capacity()) reallocate(); }
    pair<string*, string*> alloc_n_copy(const string* b, const string* e);
    void alloc_n_move(size_t new_cap);
    void free();
    void reallocate();
    string *elements;
    string *first_free;
    string *cap;
};

void StrVec::push_back(const string& s) {
    // ensure there is room for another element
    chk_n_alloc();
    // construct a copy of s in the element to which first_free points
    // the string copy constructor will be called
    alloc.construct(first_free++, s);
}

// allocate storage and copy elements into the newly allocated space
pair<string*, string*> StrVec::alloc_n_copy(const string* b, const string* e) {
    auto data = alloc.allocate(e - b);
    // the first data points to the start of the allocated memory
    // the second points to one element past the last constructed element
    return {data, uninitialized_copy(b, e, data)};
}

void StrVec::free() {
    if (elements) {
        // destroy old elements in reverse order
        // destroy function runs the string destructor
        for (auto p = first_free; p != elements; )
            alloc.destroy(--p);
        alloc.deallocate(elements, cap - elements);
    }
}

StrVec::StrVec(const StrVec &s) {
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec& StrVec::operator=(const StrVec &rhs) {
    auto data = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

void StrVec::reallocate() {
    auto newcapacity = size() ? 2 * size() : 1;
    alloc_n_move(newcapacity);
}

void StrVec::alloc_n_move(size_t new_cap) {
    // allocate new memory
    auto newdata = alloc.allocate(new_cap);
    // move data from old memory to the new
    auto dest = newdata; // points the next free position in the new array
    auto elem = elements; // points to the next element in the old array
    for (size_t i = 0; i != size(); ++i) {
        // move returns a result that causes construct to use the string move constructor
        // the memory managed by those strings will not be copied, and each string we construct
        // will take over ownership of the memory from the string to which elem points
        alloc.construct(dest++, std::move(*elem++));
    }
    free();
    elements = newdata;
    first_free = dest;
    cap = elements + new_cap;
}

void StrVec::reverse(size_t new_cap) {
    if (new_cap > capacity()) {
        alloc_n_move(new_cap);
    }
}

void StrVec::resize(size_t count) {
    resize(count, string());
}

void StrVec::resize(size_t count, const string &s) {
    if (count < size()) {
        while (first_free != elements + count)
            alloc.destroy(--first_free);
    }
    else if (count > size()) {
        if (count > capacity())
            reverse(count * 2);
        for (size_t i = size(); i != count; ++i)
            alloc.construct(first_free++, s);
    }
}

int main() {
    StrVec words;
    words.reverse(10);
    cout << words.size() << " " << words.capacity() << endl;
    words.push_back("how");
    words.push_back("are");
    words.push_back("you");
    words.resize(2);
    cout << words.size() << " " << words.capacity() << endl;
    for (auto it = words.begin(); it != words.end(); ++it) cout << *it << " ";
    return 0;
}

Note: One of the major features in the new standard is the ability to move rather than copy an object. The library containers, string, and shared_ptr clases support move as well as copy. The IO and unique_ptr classes can be moved but not copied.

Rvalue References

To support move operations, the new standard introduced a new kind of reference, an rvalue reference. An rvalue reference is a reference that must be bound to an rvalue. An rvalue reference is obtained by using && rather than &. As we’ll see, rvalue references have the important property that they may be bound only to an object that is about to be destroyed. Generally speaking, an lvalue expression refers to an object’s identity whereas an rvalue expression refers to an object’s value.

1
2
3
4
5
6
7
8
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

Variables are lvalues. As a result, we cannot bind an rvalue reference to a variable defined as an rvalue reference type:

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

Alghough we cannot directly bind an rvalue reference to an lvalue, we can explicitly cast an lvalue to its corresponding rvalue reference type. We can also obtain an rvalue reference bound to an lvaue by calling a new library function named move, which is defined in the utility header. The move function uses facilities to return an rvalue reference to its given object.

1
int &&rr3 = std::move(rr1); // ok

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.

Code that uses move should use std::move, not move. Doing so avoids potential name collisions.

Move Constructor

To enable move operations for our own types, we define a move constructor and a move-assignment operator. These members are similar to the corresponding copy operations, but they “steal” resources from their given object rather than copy them.

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. As in the copy constructor, any additional parameters must all have default arguments. In addition to moving resources, the move constructor must ensure that the moved-from object is left in a state such that destroying that object will be harmless. In particular, once its resources are moved, the original object must no longer point to those moved resources. Responsibility for those resources has been assumed by the newly created object.

1
2
3
4
StrVec::StrVec(StrVec &&s) noexcept : elements(s.elements), first_free(s.first_free), cap(s.cap) {
    // leave s in a state in which it is safe to run the destructor
    s.elements = s.first_free = s.cap = nullptr;
}

Unlike the copy constructor, the move constructor does not allocate any new memory; it takes over the memory in the given object. As a result, move operations ordinarily will not throw any exceptions. As we’ll see, unless the library knows that our move constructor won’t throw, it will do extra work to cater to the possibliity that moving an object of our class type might throw. One way inform the library is to specify noexcept on our constructor. We specify noexcept on a function after its parameter list. Move constructors and move assignment operators that cannot throw exceptions should be marked as noexcept.

So why noexcept is needed ? The push_back operation in the class StrVec might require that the vector be reallocated. As we’ve just seen, moving an object generally changes the value of the moved-from object. If reallocation uses a move constructor which throw an exception after moving some but not all of elements, 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 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 by using noexcept.

Move-Assignment Operator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
StrVec& StrVec::operator=(StrVec &&rhs) noexcept {
    // direct test for self-assignment
    if (this != &rhs) {
        free();
        elements = rhs.elements;
        first_free = rhs.first_free;
        cap = rhs.cap;
        // leave rhs in a destructible state
        rhs.elements = rhs.first_free = rhs.cap = nullptr;
    }
    return *this;
}

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. After a move operation, the moved-from object must remain a valid, destructible object but users may make no assumptions about its value.

The Synthesized Move Operations

Recall that if we do not declare our own copy constructor or copy-assignment operator the compiler always synthesizes these operations. The compiler will synthesize a move constructor or a move-assignment operator only if the class doesn’t define any of its own copy-control members and if every nonstatic data member of the class can be moved.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// the compiler will synthesize the move operations for X and hasX
struct X {
	int i; // built-in types can be moved
	std::string s; // string defines its own move operations
};

struct hasX {
	X mem; // X has synthesized move operations
};
X x, x2 = std::move(x); // uses the synthesized move constructor
hasX hx, hx2 = std::move(hx); // uses the synthesized move constructor

Move Operation Defined as Deleted

Unlike the copy operations, a move operation is never implicitly defined as a deleted function. However, if we explicitly ask the compiler to generate a move operation by using = default, and the compiler is unable to move all the members, then the move operation will be defined as deleted. The rules for when a synthesized move operation is defined as deleted:

  • 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.

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

Which Constructor to Use

When a class has both a move constructor and a copy constructor, the compiler uses ordinary function matching to determine which constructor to use. Similarly for assignment. If a class has no move constructor, function matching ensures that objects of that type are copied, even if we attempt to move them by calling move.

Copy-and-Swap Assignment Operators and Move

1
2
3
4
5
6
7
8
class HasPtr {
public:
	// added move constructor
	HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) {p.ps = 0;}
	// assignment operator is both the move-assignment and copy-assignment operator
	HasPtr& operator=(HasPtr rhs) { swap(*this, rhs); return *this; }
	// other members
};

If we add a move constructor to this class, it will effectively get a move assignment operator as well. Now let’s look at the assignment operator. That operator has a nonreference parameter, which means the parameter is copy initialized. Copy initialization uses either the copy constructor or the move constructor; lvalues are copied and rvalues are moved. As a result, this single assignment operator acts as both the copy-assignment and move-assignment operator.

The third rule of thumb: All five copy-control members should be thought of as a unit. Ordinarily, if a class defines any of these operations, it usually should define them all.

Move Iterators

The reallocate member of StrVec used a for loop to call construct to copy the elements from the old memory to the new. As an alternative to writing that loop, it would be easier if we could call uninitialized_copy to construct the newly allocated space. However, uninitialized_copy does what it says: It copies the elements. There is no analogous library function to “move” objects into unconstructed memory.

Instead, 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. All of the original iterator’s other operations work as usual.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void StrVec::reallocate()
{
    // allocate space for twice as many elements as the current size
    auto newcapacity = size() ? 2 * size() : 1;
    auto first = alloc.allocate(newcapacity);
    // move the elements
    auto last = uninitialized_copy(make_move_iterator(begin()), make_move_iterator(end()), first);
    free(); // free the old space
    elements = first; // update the pointers
    first_free = last;
    cap = elements + newcapacity;
}

uninitialized_copy calls construct on each element in the input sequence to “copy” that element into the destination. That algorithm uses the iterator dereference operator to fetch elements from the input sequence. Because we passed move iterators, the dereference operator yields an rvalue reference, which means construct will use the move constructor to construct the elements. You should pass move iterators to algorithms only when you are confident that the algorithm does not access an element after it has assigned to that element or passed that element to a user-defined function.

Because a moved-from object has indeterminate state, calling std::move on an object is a dangerous operation. When we call move, we must be absolutely certain that there can be no other users of the moved-from object.

Rvalue References and Member Functions

Member functions other than constructors and assignment can benefit from providing both copy and move versions. For example, the library containers that define push_back provide two versions:

1
2
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&&. Ordinarily, there is no need to define versions of the operation that take a const X&& or a (plain) X&. Usually, we pass an rvalue reference when we want to “steal” from the argument. In order to do so, the argument must not be const. Similarly, copying from an object should not change the object being copied.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void StrVec::push_back(const string& s)
{
	chk_n_alloc(); // ensure that there is room for another element
	// construct a copy of s in the element to which first_free points
	alloc.construct(first_free++, s);
}

void StrVec::push_back(string &&s)
{
	chk_n_alloc(); // reallocates the StrVec if necessary
	alloc.construct(first_free++, std::move(s));
}

The difference is that the rvalue reference version of push_back calls move to pass its parameter to construct. As we’ve seen, the construct function uses the type of its second and subsequent arguments to determine which constructor to use.

1
2
3
4
StrVec vec; // empty StrVec
string s = "some string or another";
vec.push_back(s); // calls push_back(const string&)
vec.push_back("done"); // calls push_back(string&&)

When we call push_back the type of the argument determines whether the new element is copied or moved into the container. These calls differ as to whether the argument is an lvalue (s) or an rvalue (the temporary string created from “done”).

The reference qualifier

The following usage can be surprising when we assign to the rvalue. Prior to the new standard, there was no way to prevent such usage. In order to maintain backward compatability, the library classes continue to allow assignment to rvalues, However, we might want to prevent such usage in our own classes. In this case, we’d like to force the left-hand operand to be an lvalue.

1
2
string s1 = "a value", s2 = "another";
s1 + s2 = "wow!";

C++ 11, We indicate the lvalue/rvalue property of “this” in the same way that we define const member functions; we place a reference qualifier after the parameter list.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Foo {
public:
	Foo &operator=(const Foo&) &; // may assign only to modifiable lvalues
};

Foo &Foo::operator=(const Foo &rhs) &
{
	// do whatever is needed to assign rhs to this object
	return *this;
}

The reference qualifier can be either & or &&, indicating that “this” may point to an lvalue or rvalue, respectively. Like the const qualifier, a reference qualifier may appear only on a (nonstatic) member function and must appear in both the declaration and definition of the function.

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

1
2
3
4
5
class Foo {
public:
	Foo someMem() & const; // error: const qualifier must come first
	Foo anotherMem() const &; // ok: const qualifier comes first
};

Overloading and Reference Functions

Just as we can overload a member function based on whether it is const, we can also overload a function based on its reference qualifier.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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<int> 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
}

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:

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

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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
};

Overloaded Operations and Conversions

C++ lets us define what the operators mean when applied to objects of class type. It also lets us define conversions for class types. Class-type conversions are used like the built-in conversions to implicitly convert an object of one type to another type when needed. Operator overloading lets us define the meaning of an operator when applied to operand(s) of a class type. Judicious use of operator overloading can make our programs easier to write and easier to read.

Basic Concepts

Overloaded operators are functions with special names: the keyword operator followed by the symbol for the operator being defined. An overloaded operator function has the same number of parameters as the operator has operands. When an overloaded operator is a member function, this is bound to the left-hand operand. Member operator functions have one less (explicit) parameter than the number of operands.

We cannot change the meaning of an operator when applied to operands of built-in type. We can overload only existing operators and cannot invent new operator symbols. For example, we cannot define operator** to provide exponentiation. Four symbols (+, -, *, and &) serve as both unary and binary operators. Either or both of these operators can be overloaded. An overloaded operator has the same precedence and associativity as the corresponding built-in operator.

Calling Overloaded Operator Directly

1
2
3
// equivalent calls to a nonmember operator function
data1 + data2;            // normal expression
operator+(data1, data2);  // equivalent function call

These calls are equivalent: Both call the nonmember function operator+, passing data1 as the first argument and data2 as the second. And we call a member operator function explicitly in the same way that we call any other member function.

1
2
data1 += data2;          // expression-based ''call''
data1.operator+=(data2); // equivalent call to a member operator function

Some Operators Shouldn’t Be Overloaded

Because the overloaded versions of these operators do not preserve order of evaluation and/or short-circuit evaluation, it is usually a bad idea to overload them. Ordinarily, the comma, address-of, logical AND , and logical OR operators should not be overloaded.

Use Definitions That Are Consistent with the Built-in Meaning

  • If the class does IO, define the shift operators to be consistent with how IO is done for the built-in types.
  • If the class has an operation to test for equality, define operator==. If the class has operator==, it should usually have operator!= as well.
  • If the class has a single, natural ordering operation, define operator<. If the class has operator<, it should probably have all of the relational operators.
  • The return type of an overloaded operator usually should be compatible with the return from the built-in version of the operator.

Operator overloading is most useful when there is a logical mapping of a built-in operator to an operation on our type.

Choosing Member or Nonmember

When we define an overloaded operator, we must decide whether to make the operator a class member or an ordinary nonmember function.

  • The assignment (=), subscript ([]), call (()), and member access arrow (->) operators must be defined as members.
  • The compound-assignment operators ordinarily ought to be members. However, unlike assignment, they are not required to be members.
  • Operators that change the state of their object or that are closely tied to their given type—such as increment, decrement, and dereference—usually should be members.
  • Symmetric operators—those that might convert either operand, such as the arithmetic, equality, relational, and bitwise operators—usually should be defined as ordinary nonmember functions.

Programmers expect to be able to use symmetric operators in expressions with mixed types. For example, we can add an int and a double. The addition is symmetric because we can use either type as the left-hand or the right-hand operand. If we want to provide similar mixed-type expressions involving class objects, then the operator must be defined as a nonmember function. When we define an operator as a member function, then the left-hand operand must be an object of the class of which that operator is a member. For example:

1
2
3
string s = "world";
string t = s + "!";  // ok: we can add a const char* to a string
string u = "hi" + s; // would be an error if + were a member of string

If operator+ were a member of the string class, the first addition would be equivalent to s.operator+("!"). Likewise, “hi” + s would be equivalent to “hi”.operator+(s). However, the type of “hi” is const char*, and that is a built-in type; it does not even have member functions.

Overloading the Output Operator

Ordinarily, the first parameter of an output operator is a reference to a nonconst ostream object. The ostream is nonconst because writing to the stream changes its state. The parameter is a reference because we cannot copy an ostream object. The second parameter ordinarily should be a reference to const of the class type we want to print. The parameter is a reference to avoid copying the argument. It can be const because (ordinarily) printing an object does not change that object. To be consistent with other output operators, operator« normally returns its ostream parameter.

1
2
3
4
5
6
ostream &operator<<(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " "
    << item.revenue << " " << item.avg_price();
    return os;
}

Generally, output operators should print the contents of the object, with minimal formatting. They should not print a newline.

Input and output operators that conform to the conventions of the iostream library must be ordinary nonmember functions.

Overloading the Input Operator

Ordinarily the first parameter of an input operator is a reference to the stream from which it is to read, and the second parameter is a reference to the (nonconst) object into which to read. The operator usually returns a reference to its given stream.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
istream &operator>>(istream &is, Sales_data &item)
{
    double price; // no need to initialize; we'll read into price before we use it
    is >> item.bookNo >> item.units_sold >> price;
    if (is) // check that the inputs succeeded
        item.revenue = item.units_sold * price;
    else
        item = Sales_data(); // input failed: give the object the default state
    return is;
}

Input operators must deal with the possibility that the input might fail; output operators generally don’t bother. Some input operators need to do additional data verification. In such cases, the input operator might need to set the stream’s condition state to indicate failure, even though technically speaking the actual IO was successful. Usually an input operator should set only the failbit. Setting eofbit would imply that the file was exhausted, and setting badbit would indicate that the stream was corrupted. These errors are best left to the IO library itself to indicate.

Arithmetic and Relational Operators

Ordinarily, we define the arithmetic and relational operators as nonmember functions in order to allow conversions for either the left- or right-hand operand.

1
2
3
4
5
6
7
8
// assumes that both objects refer to the same book
Sales_data
operator+(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs; // copy data members from lhs into sum
    sum += rhs; // add rhs into sum
    return sum;
}

Classes that define both an arithmetic operator and the related compound assignment ordinarily ought to implement the arithmetic operator by using the compound assignment.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
    return lhs.isbn() == rhs.isbn() &&
        lhs.units_sold == rhs.units_sold &&
        lhs.revenue == rhs.revenue;
}
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
    return !(lhs == rhs);
}

Classes for which there is a logical meaning for equality normally should define operator==. Classes that define == make it easier for users to use the class with the library algorithms.

For Sales_data, there is no single logical definition of <. Thus, it is better for this class not to define < at all. If a single logical definition for < exists, classes usually should define the < operator. However, if the class also has ==, define < only if the definitions of < and == yield consistent results.

Assignment Operators

In addition to the copy- and move-assignment operators that assign one object of the class type to another object of the same type, a class can define additional assignment operators that allow other types as the right-hand operand. As one example, the library vector class defines a third assignment operator that takes a braced list of elements. We can add this operator to our StrVec class:

1
2
3
4
5
class StrVec {
public:
    StrVec &operator=(std::initializer_list<std::string>);
    // other members as in § 13.5 (p. 526)
};

Assignment operators can be overloaded. Assignment operators, regardless of parameter type, must be defined as member functions.

Compound-Assignment Operators

Compound assignment operators are not required to be members. However, we prefer to define all assignments, including compound assignments, in the class. For example:

1
2
3
4
5
6
7
8
// member binary operator: left-hand operand is bound to the implicit this pointer
// assumes that both objects refer to the same book
Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

Assignment operators must, and ordinarily compound-assignment operators should, be defined as members. These operators should return a reference to the left-hand operand.

Subscript Operator

Classes that represent containers from which elements can be retrieved by position often define the subscript operator, operator[]. The subscript operator must be a member function. If a class has a subscript operator, it usually should define two versions: one that returns a plain reference and the other that is a const member and returns a reference to const.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class StrVec {
public:
    std::string& operator[](std::size_t n)
        { return elements[n]; }
    const std::string& operator[](std::size_t n) const
        { return elements[n]; }
    // other members as in § 13.5 (p. 526)
private:
    std::string *elements; // pointer to the first element in the array
};

Increment and Decrement Operators

The increment (++) and decrement (–) operators are most often implemented for iterator classes. These operators let the class move between the elements of a sequence. Classes that define increment or decrement operators should define both the prefix and postfix versions. These operators usually should be defined as members.

1
2
3
4
5
6
7
class StrBlobPtr {
public:
    // increment and decrement
    StrBlobPtr& operator++(); // prefix operators
    StrBlobPtr& operator--();
    // other members as before
};

To be consistent with the built-in operators, the prefix operators should return a reference to the incremented or decremented object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// prefix: return a reference to the incremented/decremented object
StrBlobPtr& StrBlobPtr::operator++()
{
    // if curr already points past the end of the container, can't increment it
    check(curr, "increment past end of StrBlobPtr");
    ++curr; // advance the current state
    return *this;
}
StrBlobPtr& StrBlobPtr::operator--()
{
    // if curr is zero, decrementing it will yield an invalid subscript
    --curr; // move the current state back one element
    check(-1, "decrement past begin of StrBlobPtr");
    return *this;
}

There is one problem with defining both the prefix and postfix operators: Normal overloading cannot distinguish between these operators. To solve this problem, the postfix versions take an extra (unused) parameter of type int.

1
2
3
4
5
6
7
class StrBlobPtr {
public:
    // increment and decrement
    StrBlobPtr operator++(int); // postfix operators
    StrBlobPtr operator--(int);
    // other members as before
};

To be consistent with the built-in operators, the postfix operators should return the old (unincremented or undecremented) value. That value is returned as a value, not a reference. The postfix versions have to remember the current state of the object before incrementing the object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// postfix: increment/decrement the object but return the unchanged value
StrBlobPtr StrBlobPtr::operator++(int)
{
    // no check needed here; the call to prefix increment will do the check
    StrBlobPtr ret = *this; // save the current value
    ++*this; // advance one element; prefix ++ checks the increment
    return ret; // return the saved state
}
StrBlobPtr StrBlobPtr::operator--(int)
{
    // no check needed here; the call to prefix decrement will do the check
    StrBlobPtr ret = *this; // save the current value
    --*this; // move backward one element; prefix -- checks the decrement
    return ret; // return the saved state
}

The int parameter is not used, so we do not give it a name. If we want to call the postfix version using a function call, then we must pass a value for the integer argument:

1
2
3
StrBlobPtr p(a1); // p points to the vector inside a1
p.operator++(0); // call postfix operator++
p.operator++(); // call prefix operator++

Member Access Operators

The dereference (*) and arrow (->) operators are often used in classes that represent iterators and in smart pointer classes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class StrBlobPtr {
public:
    std::string& operator*() const
    { 
        auto p = check(curr, "dereference past end");
        return (*p)[curr]; // (*p) is the vector to which this object points
    }
    std::string* operator->() const
    {
        // delegate the real work to the dereference operator
        return & this->operator*();
    }
    // other members as before
};

Operator arrow must be a member. The dereference operator is not required to be a member but usually should be a member as well. The overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow.

Function-Call Operator

Classes that overload the call operator allow objects of its type to be used as if they were a function. Because such classes can also store state, they can be more flexible than ordinary functions.

1
2
3
4
5
6
struct absInt {
    int operator()(int val) const 
    {
        return val < 0 ? -val : val;
    }
};

We use the call operator by applying an argument list to an absInt object in a way that looks like a function call. Even though absObj is an object, not a function, we can “call” this object. Calling an object runs its overloaded call operator.

1
2
3
int i = -42;
absInt absObj; // object that has a function-call operator
int ui = absObj(i); // passes i to absObj.operator()

The function-call operator must be a member function. A class may define multiple versions of the call operator, each of which must differ as to the number or types of their parameters. Objects of classes that define the call operator are referred to as function objects. Such objects “act like functions” because we can call them.

As an example, we’ll define a class that prints a string argument.

1
2
3
4
5
6
7
8
class PrintString {
public:
    PrintString(ostream &o = cout, char c = ' '): os(o), sep(c) { }
    void operator()(const string &s) const { os << s << sep; }
private:
    ostream &os; // stream on which to write
    char sep; // character to print after each output
};

When we define PrintString objects, we can use the defaults or supply our own values for the separator or output stream:

1
2
3
4
PrintString printer; // uses the defaults; prints to cout
printer(s); // prints s followed by a space on cout
PrintString errors(cerr, '\n');
errors(s); // prints s followed by a newline on cerr

Function objects are most often used as arguments to the generic algorithms. For example, we can use the library for_each algorithm and our PrintString class to print the contents of a container:

1
for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));

Lambdas Are Function Objects

In the previous section, we used a PrintString object as an argument in a call to for_each. This usage is similar to the programs that used lambda expressions. When we write a lambda, the compiler translates that expression into an unnamed object of an unnamed class. The classes generated from a lambda contain an overloaded function-call operator. For example, the lambda that we passed as the last argument to stable_sort:

1
2
3
4
// sort words by size, but maintain alphabetical order for words of the same size
stable_sort(words.begin(), words.end(),
            [](const string &a, const string &b)
            { return a.size() < b.size();});

acts like an unnamed object of a class that would look something like

1
2
3
4
5
class ShorterString {
public:
    bool operator()(const string &s1, const string &s2) const
    { return s1.size() < s2.size(); }
};

By default, lambdas may not change their captured variables. As a result, by default, the function-call operator in a class generated from a lambda is a const member function. If the lambda is declared as mutable, then the call operator is not const. We can rewrite the call to stable_sort to use this class instead of the lambda expression:

1
stable_sort(words.begin(), words.end(), ShorterString());

As we’ve seen, when a lambda captures a variable by reference, the compiler is permitted to use the reference directly without storing that reference as a data member in the generated class. In contrast, variables that are captured by value are copied into the lambda. As a result, classes generated from lambdas that capture variables by value have data members corresponding to each such variable. As an example, the lambda that we used to find the first string whose length was greater than or equal to a given bound:

1
2
3
// get an iterator to the first element whose size() is >= sz
auto wc = find_if(words.begin(), words.end(),
                [sz](const string &a)

would generate a class that looks something like

1
2
3
4
5
6
7
class SizeComp {
    SizeComp(size_t n): sz(n) { } // parameter for each captured variable
    // call operator with the same return type, parameters, and body as the lambda
    bool operator()(const string &s) const { return s.size() >= sz; }
private:
    size_t sz; // a data member for each variable captured by value
};

Unlike our ShorterString class, this class has a data member and a constructor to initialize that member. This synthesized class does not have a default constructor; to use this class, we must pass an argument:

1
2
// get an iterator to the first element whose size() is >= sz
auto wc = find_if(words.begin(), words.end(), SizeComp(sz));

Classes generated from a lambda expression have a deleted default constructor, deleted assignment operators, and a default destructor. Whether the class has a defaulted or deleted copy/move constructor depends in the usual ways on the types of the captured data members.

Library-Defined Function Objects

The standard library defines a set of classes that represent the arithmetic, relational, and logical operators. Each class defines a call operator that applies the named operation. These classes are templates to which we supply a single type. That type specifies the parameter type for the call operator. For example, plus applies the string addition operator to string objects; for plus the operands are ints; and so on.

1
2
3
4
5
6
7
8
plus<int> intAdd; // function object that can add two int values
negate<int> intNegate; // function object that can negate an int value
// uses intAdd::operator(int, int) to add 10 and 20
int sum = intAdd(10, 20); // equivalent to sum = 30
sum = intNegate(intAdd(10, 20)); // equivalent to sum = 30
// uses intNegate::operator(int) to generate -10 as the second parameter
// to intAdd::operator(int, int)
sum = intAdd(10, intNegate(10)); // sum = 0

These types are defined in the functional header.

1
2
3
4
5
6
7
8
Arithmetic      Relational          Logical

plus<T>         equal_to<T>         logical_and<T>
minus<T>        not_equal_to<T>     logical_or<T>
multiplies<T>   greater<T>          logical_not<T>
divides<T>      greater_equal<T>
modulus<T>      less<T>
negate<T>       less_equal<T>

The function-object classes that represent operators are often used to override the default operator used by an algorithm. As we’ve seen, by default, the sorting algorithms use operator<, which ordinarily sorts the sequence into ascending order. To sort into descending order, we can pass an object of type greater.

1
2
// passes a temporary function object that applies the < operator to two strings
sort(svec.begin(), svec.end(), greater<string>());

One important aspect of these library function objects is that the library guarantees that they will work for pointers. Recall that comparing two unrelated pointers is undefined. However, we might want to sort a vector of pointers based on their addresses in memory.

1
2
3
4
5
6
vector<string *> nameTable; // vector of pointers
// error: the pointers in nameTable are unrelated, so < is undefined
sort(nameTable.begin(), nameTable.end(),
    [](string *a, string *b) { return a < b; });
// ok: library guarantees that less on pointer types is well defined
sort(nameTable.begin(), nameTable.end(), less<string*>());

Callable Objects and function

C++ has several kinds of callable objects: functions and pointers to functions, lambdas, objects created by bind, and classes that overload the function-call operator. Sometimes we want to treat several callable objects that share a call signature as if they had the same type. For example, consider the following different types of callable objects:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ordinary function
int add(int i, int j) { return i + j; }
// lambda, which generates an unnamed function-object class
auto mod = [](int i, int j) { return i % j; };
// function-object class
struct div 
{
    int operator()(int denominator, int divisor) 
    {
        return denominator / divisor;
    }
};

Even though each has a distinct type, they all share the same call signature:

1
int(int, int)

We might want to use these callables to build a simple desk calculator. To do so, we’d want to define a function table to store “pointers” to these callables. In C++, function tables are easy to implement using a map.

1
2
// maps an operator to a pointer to a function taking two ints and returning an int
map<string, int(*)(int,int)> binops;

We could put a pointer to add into binops as follows:

1
2
// ok: add is a pointer to function of the appropriate type
binops.insert({"+", add});

However, we can’t store mod or div in binops:

1
binops.insert({"%", mod}); // error: mod is not a pointer to function

The problem is that mod is a lambda, and each lambda has its own class type. That type does not match the type of the values stored in binops. We can solve this problem using a new library type named function that is defined in the functional header. function is a template. We can declare a function type that can represent callable objects that return an int result and have two int parameters.

1
2
3
4
5
6
7
function<int(int, int)> f1 = add; // function pointer
function<int(int, int)> f2 = div(); // object of a function-object class
function<int(int, int)> f3 = [](int i, int j) // lambda
{ return i * j; };
cout << f1(4,2) << endl; // prints 6
cout << f2(4,2) << endl; // prints 2
cout << f3(4,2) << endl; // prints 8

We can now redefine our map using this function type:

1
map<string, function<int(int, int)>> binops;

We can add each of our callable objects, be they function pointers, lambdas, or function objects, to this map:

1
2
3
4
5
6
map<string, function<int(int, int)>> binops = {
{"+", add}, // function pointer
{"-", std::minus<int>()}, // library function object
{"/", div()}, // user-defined function object
{"*", [](int i, int j) { return i * j; }}, // unnamed lambda
{"%", mod} }; // named lambda object

When we index binops, we get a reference to an object of type function. The function type overloads the call operator. That call operator takes its own arguments and passes them along to its stored callable object:

1
2
3
4
5
binops["+"](10, 5); // calls add(10, 5)
binops["-"](10, 5); // uses the call operator of the minus<int> object
binops["/"](10, 5); // uses the call operator of the div object
binops["*"](10, 5); // calls the lambda function object
binops["%"](10, 5); // calls the lambda function object

The function class in the new library is not related to classes named unary_function and binary_function that were part of earlier versions of the library. These classes have been deprecated by the more general bind function.

Conversion Operators

Converting constructors and conversion operators define class-type conversions. Such conversions are also referred to as user-defined conversions. A conversion operator is a special kind of member function that converts a value of a class type to a value of some other type. A conversion function typically has the general form

1
operator type() const;

where type represents a type. Conversion operators can be defined for any type (other than void) that can be a function return type. Conversions to an array or a function type are not permitted. Conversions to pointer types—both data and function pointers—and to reference types are allowed.

Conversion operators have no explicitly stated return type and no parameters, and they must be defined as member functions. Conversion operations ordinarily should not change the object they are converting. As a result, conversion operators usually should be defined as const members.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class SmallInt {
public:
    SmallInt(int i = 0): val(i)
    {
        if (i < 0 || i > 255)
            throw std::out_of_range("Bad SmallInt value");
    }
    operator int() const { return val; }
private:
    std::size_t val;
};

The conversion operator converts SmallInt objects to int:

1
2
3
SmallInt si;
si = 4; // implicitly converts 4 to SmallInt then calls SmallInt::operator=
si + 3; // implicitly converts si to int followed by integer addition

Although the compiler will apply only one user-defined conversion at a time, an implicit user-defined conversion can be preceded or followed by a standard (built-in) conversion. As a result, we can pass any arithmetic type to the SmallInt constructor.

1
2
3
4
// the double argument is converted to int using the built-in conversion
SmallInt si = 3.14; // calls the SmallInt(int) constructor
// the SmallInt conversion operator converts si to int;
si + 3.14; // that int is converted to double using the built-in conversion

Because conversion operators are implicitly applied, there is no way to pass arguments to these functions. Hence, conversion operators may not be defined to take parameters. Although a conversion function does not specify a return type, each conversion function must return a value of its corresponding type:

1
2
3
4
5
6
7
8
9
class SmallInt;
operator int(SmallInt&); // error: nonmember

class SmallInt {
public:
    int operator int() const; // error: return type
    operator int(int = 0) const; // error: parameter list
    operator int*() const { return 42; } // error: 42 is not a pointer
};

Conversion operators are misleading when there is no obvious single mapping between the class type and the conversion type. For example, consider a class that represents a Date. We might think it would be a good idea to provide a conversion from Date to int. Alternatively, the conversion operator might return an int representing the number of days that have elapsed since some epoch point, such as January 1, 1970. The problem is that there is no single one-to-one mapping between an object of type Date and a value of type int. In such cases, it is better not to define the conversion operator. Instead, the class ought to define one or more ordinary members to extract the information in these various forms.

explicit Conversion Operators

In practice, classes rarely provide conversion operators. It is not uncommon for classes to define conversions to bool.

1
2
int i = 42;
cin << i; // this code would be legal if the conversion to bool were not explicit!

This program attempts to use the output operator on an input stream. There is no « defined for istream, so the code is almost surely in error. However, this code could use the bool conversion operator to convert cin to bool. The resulting bool value would then be promoted to int and used as the left-hand operand to the built-in version of the left-shift operator. To prevent such problems, the new standard introduced explicit conversion operators:

1
2
3
4
5
6
class SmallInt {
public:
    // the compiler won't automatically apply this conversion
    explicit operator int() const { return val; }
    // other members as before
};

If the conversion operator is explicit, we can still do the conversion. However, with one exception, we must do so explicitly through a cast.

1
2
3
SmallInt si = 3; // ok: the SmallInt constructor is not explicit
si + 3; // error: implicit is conversion required, but operator int is explicit
static_cast<int>(si) + 3; // ok: explicitly request the conversion

The exception is that the compiler will apply an explicit conversion to an expression used as a condition. That is, an explicit conversion will be used implicitly to convert an expression used as

  • The condition of an if, while, or do statement
  • The condition expression in a for statement header
  • An operand to the logical NOT (!), OR (||), or AND (&&) operators
  • The condition expression in a conditional (?:) operator

Conversion to bool

Under the new standard, the IO library defines an explicit conversion to bool. Whenever we use a stream object in a condition, we use the operator bool that is defined for the IO types.

1
while (std::cin >> value)

The condition in the while executes the input operator, which reads into value and returns cin. To evaluate the condition, cin is implicitly converted by the istream operator bool conversion function. That function returns true if the condition state of cin is good, and false otherwise. Conversion to bool is usually intended for use in conditions. As a result, operator bool ordinarily should be defined as explicit.

Avoiding Ambiguous Conversions

If a class has one or more conversions, it is important to ensure that there is only one way to convert from the class type to the target type. If there is more than one way to perform a conversion, it will be hard to write unambiguous code. There are two ways that multiple conversion paths can occur. The first happens when two classes provide mutual conversions. For example, mutual conversions exist when a class A defines a converting constructor that takes an object of class B and B itself defines a conversion operator to type A. The second way to generate multiple conversion paths is to define multiple conversions from or to types that are themselves related by conversions. The most obvious instance is the built-in arithmetic types. A given class ordinarily ought to define at most one conversion to or from an arithmetic type. So, ordinarily it is a bad idea to define classes with mutual conversions or to define conversions to or from two arithmetic types.

In the following example, we’ve defined two ways to obtain an A from a B: either by using B’s conversion operator or by using the A constructor that takes a B:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// usually a bad idea to have mutual conversions between two class types
struct B;
struct A {
    A() = default;
    A(const B&); // converts a B to an A
    // other members
};
struct B {
    operator A() const; // also converts a B to an A
    // other members
};
A f(const A&);
B b;
A a = f(b); // error ambiguous: f(B::operator A())
            // or f(A::A(const B&))

Because there are two ways to obtain an A from a B, the compiler doesn’t know which conversion to run; the call to f is ambiguous. If we want to make this call, we have to explicitly call the conversion operator or the constructor:

1
2
A a1 = f(b.operator A()); // ok: use B's conversion operator
A a2 = f(A(b)); // ok: use A's constructor

The following class has converting constructors from two different arithmetic types, and conversion operators to two different arithmetic types:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct A {
    A(int = 0); // usually a bad idea to have two
    A(double); // conversions from arithmetic types
    operator int() const; // usually a bad idea to have two
    operator double() const; // conversions to arithmetic types
    // other members
};
void f2(long double);
A a;
f2(a);  // error ambiguous: f(A::operator int())
        // or f(A::operator double())
long lg;
A a2(lg); // error ambiguous: A::A(int) or A::A(double)

In the call to f2, neither conversion is an exact match to long double. However, the call is ambiguous. We encounter the same problem when we try to initialize a2 from a long. Neither constructor is an exact match for long. The call to f2, and the initialization of a2, are ambiguous because the standard conversions that were needed had the same rank.

1
2
3
short s = 42;
// promoting short to int is better than converting short to double
A a3(s); // uses A::A(int)

When two user-defined conversions are used, the rank of the standard conversion, if any, preceding or following the conversion function is used to select the best match.

Overloaded Functions and Converting Constructors

Choosing among multiple conversions is further complicated when we call an overloaded function. If two or more conversions provide a viable match, then the conversions are considered equally good.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct C {
    C(int);
    // other members
};
struct D {
    D(int);
    // other members
};
void manip(const C&);
void manip(const D&);
manip(10); // error ambiguous: manip(C(10)) or manip(D(10))

Here both C and D have constructors that take an int. Hence, the call is ambiguous. The caller can disambiguate by explicitly constructing the correct type:

1
manip(C(10)); // ok: calls manip(const C&)

Needing to use a constructor or a cast to convert an argument in a call to an overloaded function frequently is a sign of bad design.

Overloaded Functions and User-Defined Conversion

In a call to an overloaded function, if two (or more) user-defined conversions provide a viable match, the conversions are considered equally good. The rank of any standard conversions that might or might not be required is not considered.

1
2
3
4
5
6
7
8
struct E {
    E(double);
    // other members
};
void manip2(const C&);
void manip2(const E&);
// error ambiguous: two different user-defined conversions could be used
manip2(10); // manip2(C(10) or manip2(E(double(10)))

In this case, C has a conversion from int and E has a conversion from double. For the call manip2(10), both manip2 functions are viable. Because calls to the overloaded functions require different user-defined conversions from one another, this call is ambiguous. In particular, even though one of the calls requires a standard conversion and the other is an exact match, the compiler will still flag this call as an error.

Function Matching and Overloaded Operators

Overloaded operators are overloaded functions. Normal function matching is used to determine which operator—built-in or overloaded—to apply to a given expression. However, when an operator function is used in an expression, the set of candidate functions is broader than when we call a function using the call operator. If a has a class type, the expression a sym b might be

1
2
a.operatorsym (b); // a has operatorsym as a member function
operatorsym(a, b); // operatorsym is an ordinary function

When a call is through an object of a class type (or through a reference or pointer to such an object), then only the member functions of that class are considered. When we use an overloaded operator in an expression, there is nothing to indicate whether we’re using a member or nonmember function. Therefore, the set of candidate functions for an operator used in an expression can contain both nonmember and member functions. As an example, we’ll define an addition operator for our SmallInt class:

1
2
3
4
5
6
7
8
9
class SmallInt {
    friend
    SmallInt operator+(const SmallInt&, const SmallInt&);
public:
    SmallInt(int = 0); // conversion from int
    operator int() const { return val; } // conversion to int
private:
    std::size_t val;
};

We can use this class to add two SmallInts, but we will run into ambiguity problems if we attempt to perform mixed-mode arithmetic:

1
2
3
SmallInt s1, s2;
SmallInt s3 = s1 + s2; // uses overloaded operator+
int i = s3 + 0; // error: ambiguous

The first addition uses the overloaded version of + that takes two SmallInt values. The second addition is ambiguous, because we can convert 0 to a SmallInt and use the SmallInt version of +, or convert s3 to int and use the built-in addition operator on ints. Providing both conversion functions to an arithmetic type and overloaded operators for the same class type may lead to ambiguities between the overloaded operators and the built-in operators.

Object-Oriented Programming

The key ideas in object-oriented programming are data abstraction, inheritance, and dynamic binding. Using data abstraction, we can define classes that separate interface from implementation. Through inheritance, we can define classes that model the relationships among similar types. Through dynamic binding, we can use objects of these types while ignoring the details of how they differ.

Inheritance

Classes related by inheritance form a hierarchy. Typically there is a base class at the root of the hierarchy, from which the other classes inherit, directly or indirectly. These inheriting classes are known as derived classes. The base class defines those members that are common to the types in the hierarchy. Each derived class defines those members that are specific to the derived class itself.

To model our different kinds of pricing strategies, we’ll define a class named Quote, which will be the base class of our hierarchy. A Quote object will represent undiscounted books. From Quote we will inherit a second class, named Bulk_quote, to represent books that can be sold with a quantity discount. In C++, a base class distinguishes functions that are type dependent from those that it expects its derived classes to inherit without change. The base class defines as virtual those functions it expects its derived classes to define for themselves.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Quote {
public:
    std::string isbn() const;
    virtual double net_price(std::size_t n) const;
};

// the derivation list, Bulk_quote inherits from Quote
class Bulk_quote : public Quote {
public:
    double net_price(std::size_t) const override;
};

Because Bulk_quote uses public in its derivation list, we can use objects of type Bulk_quote as if they were Quote objects. A derived class must include in its own class body a declaration of all the virtual functions it intends to define for itself. A derived class may include the virtual keyword on these functions but is not required to do so. The new standard lets a derived class explicitly note that it intends a member function to override a virtual that it inherits. It does so by specifying override after its parameter list.

Dynamic Binding

Through dynamic binding, we can use the same code to process objects of either type Quote or Bulk_quote interchangeably.

1
2
3
4
5
6
7
8
9
double print_total(ostream &os, const Quote &item, size_t n)
{
    // depending on the type of the object bound to the item parameter
    // calls either Quote::net_price or Bulk_quote::net_price
    double ret = item.net_price(n);
    os << "ISBN: " << item.isbn() // calls Quote::isbn
    << " # sold: " << n << " total due: " << ret << endl;
    return ret;
}

Because the parameter is a reference to Quote, we can call this function on either a Quote object or a Bulk_quote object. Because net_price is a virtual function, and because print_total calls net_price through a reference, the version of net_price that is run will depend on the type of the object that we pass to print_total:

1
2
3
// basic has type Quote; bulk has type Bulk_quote
print_total(cout, basic, 20); // calls Quote version of net_price
print_total(cout, bulk, 20); // calls Bulk_quote version of net_price

Because the decision as to which version to run depends on the type of the argument, that decision can’t be made until run time. Therefore, dynamic binding is sometimes known as run-time binding. In C++, dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class.

Defining a Base Class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Quote {
public:
    Quote() = default;
    Quote(const std::string &book, double sales_price):
        bookNo(book), price(sales_price) { }
    std::string isbn() const { return bookNo; }
    // returns the total sales price for the specified number of items
    // derived classes will override and apply different discount algorithms
    virtual double net_price(std::size_t n) const
        { return n * price; }
    virtual ~Quote() = default; // dynamic binding for the destructor
private:
    std::string bookNo; // ISBN number of this item
protected:
    double price = 0.0; // normal, undiscounted price
};

Base classes ordinarily should define a virtual destructor. Virtual destructors are needed even if they do no work.

In C++, a base class must distinguish the functions it expects its derived classes to override from those that it expects its derived classes to inherit without change. The base class defines as virtual those functions it expects its derived classes to override. When we call a virtual function through a pointer or reference , the call will be dynamically bound. Any nonstatic member function, other than a constructor, may be virtual. The virtual keyword appears only on the declaration inside the class and may not be used on a function definition that appears outside the class body.

A derived class inherits the members defined in its base class. Like any other code that uses the base class, a derived class may access the public members of its base class but may not access the private members. However, sometimes a base class has members that it wants to let its derived classes use while still prohibiting access to those same members by other users. We specify such members after a protected access specifier.

Defining a Derived Class

1
2
3
4
5
6
7
8
9
class Bulk_quote : public Quote { // Bulk_quote inherits from Quote
    Bulk_quote() = default;
    Bulk_quote(const std::string&, double, std::size_t, double);
    //overrides the base version in order to implement the bulk purchase discount policy
    double net_price(std::size_t) const override;
private:
    std::size_t min_qty = 0; // minimum purchase for the discount to apply
    double discount = 0.0; // fractional discount to apply
};

When the derivation is public, the public members of the base class become part of the interface of the derived class as well.

If a derived class does not override a virtual from its base, then, like any other member, the derived class inherits the version defined in its base class. A derived class may include the virtual keyword on the functions it overrides, but it is not required to do so. The new standard lets a derived class explicitly note that it intends a member function to override a virtual that it inherits. It does so by specifying override after the parameter list, or after the const or reference qualifier(s) if the member is a const or reference function.

A derived object contains multiple parts: a subobject containing the (nonstatic) members defined in the derived class itself, plus subobjects corresponding to each base class from which the derived class inherits. Thus, a Bulk_quote object will contain four data elements: the bookNo and price data members that it inherits from Quote, and the min_qty and discount members, which are defined by Bulk_quote. The base and derived parts of an object are not guaranteed to be stored contiguously. Because a derived object contains subparts corresponding to its base class(es), we can use an object of a derived type as if it were an object of its base type.

1
2
3
4
5
Quote item; // object of base type
Bulk_quote bulk; // object of derived type
Quote *p = &item; // p points to a Quote object
p = &bulk; // p points to the Quote part of bulk
Quote &r = bulk; // r bound to the Quote part of bulk

This conversion is often referred to as the derived-to-base conversion. The fact that the derived-to-base conversion is implicit means that we can use an object of derived type or a reference to a derived type when a reference to the base type is required. Similarly, we can use a pointer to a derived type where a pointer to the base type is required.

Derived-Class Constructors

Although a derived object contains members that it inherits from its base, it cannot directly initialize those members. Like any other code that creates an object of the base-class type, a derived class must use a base-class constructor to initialize its base-class part. The base-class part of an object is initialized, along with the data members of the derived class, during the initialization phase of the constructor. Analogously to how we initialize a member, a derived-class constructor uses its constructor initializer list to pass arguments to a base-class constructor. For example, the Bulk_quote constructor with four parameters:

1
2
3
4
5
Bulk_quote(const std::string& book, double p,
    std::size_t qty, double disc) :
    Quote(book, p), min_qty(qty), discount(disc) { }
    // as before
};

As with a data member, unless we say otherwise, the base part of a derived object is default initialized. To use a different base-class constructor, we provide a constructor initializer using the name of the base class. The base class is initialized first, and then the members of the derived class are initialized in the order in which they are declared in the class.

A derived class may access the public and protected members of its base class:

1
2
3
4
5
6
7
8
// if the specified number of items are purchased, use the discounted price
double Bulk_quote::net_price(size_t cnt) const
{
    if (cnt >= min_qty)
        return cnt * (1 - discount) * price;
    else
        return cnt * price;
}

It is essential to understand that each class defines its own interface. Interactions with an object of a class-type should use the interface of that class, even if that object is the base-class part of a derived object. As a result, derived-class constructors may not directly initialize the members of its base class. The constructor body of a derived constructor can assign values to its public or protected base-class members. Although it can assign to those members, it generally should not do so. Like any other user of the base class, a derived class should respect the interface of its base class by using a constructor to initialize its inherited members.

Inheritance and static Members

1
2
3
4
5
6
7
class Base {
public:
    static void statmem();
};
class Derived : public Base {
    void f(const Derived&);
};

static members obey normal access control. If the member is private in the base class, then derived classes have no access to it.

1
2
3
4
5
6
7
8
void Derived::f(const Derived &derived_obj)
{
    Base::statmem(); // ok: Base defines statmem
    Derived::statmem(); // ok: Derived inherits statmem
    // ok: derived objects can be used to access static from base
    derived_obj.statmem(); // accessed through a Derived object
    statmem(); // accessed through this object
}

Classes Used as a Base Class

A class must be defined, not just declared, before we can use it as a base class:

1
2
3
class Quote; // declared but not defined
// error: Quote must be defined
class Bulk_quote : public Quote { ... };

A base class can itself be a derived class:

1
2
3
class Base { /* ... */ } ;
class D1: public Base { /* ... */ };
class D2: public D1 { /* ... */ };

In this hierarchy, Base is a direct base to D1 and an indirect base to D2. A direct base class is named in the derivation list. Effectively, the most derived object contains a subobject for its direct base and for each of its indirect bases.

Preventing Inheritance

Under the new standard, we can prevent a class from being used as a base by following the class name with final:

1
2
3
4
5
6
class NoDerived final { /* */ }; // NoDerived can't be a base class
class Base { /* */ };
// Last is final; we cannot inherit from Last
class Last final : Base { /* */ }; // Last can't be a base class
class Bad : NoDerived { /* */ }; // error: NoDerived is final
class Bad2 : Last { /* */ }; // error: Last is final

Conversions and Inheritance

Ordinarily, we can bind a reference or a pointer only to an object that has the same type as the corresponding reference or pointer or to a type that involves an acceptable const conversion. Classes related by inheritance are an important exception: We can bind a pointer or reference to a base-class type to an object of a type derived from that base class. For example, we can use a Quote& to refer to a Bulk_quote object, and we can assign the address of a Bulk_quote object to a Quote*.

The fact that we can bind a reference (or pointer) to a base-class type to a derived object has a crucially important implication: When we use a reference (or pointer) to a base-class type, we don’t know the actual type of the object to which the pointer or reference is bound. That object can be an object of the base class or it can be an object of a derived class.

There Is No Implicit Conversion from Base to Derived. We cannot convert from base to derived even when a base pointer or reference is bound to a derived object:

1
2
3
Bulk_quote bulk;
Quote *itemP = &bulk; // ok: dynamic type is Bulk_quote
Bulk_quote *bulkP = itemP; // error: can't convert base to derived

If the base class has one or more virtual functions, we can use a dynamic_cast to request a conversion that is checked at run time. Alternatively, in those caseswhen we know that the conversion from base to derived is safe, we can use a static_cast to override the compiler.

The automatic derived-to-base conversion applies only for conversions to a reference or pointer type. There is no such conversion from a derived-class type to the base-class type.

1
2
3
Bulk_quote bulk; // object of derived type
Quote item(bulk); // uses the Quote::Quote(const Quote&) constructor
item = bulk; // calls Quote::operator=(const Quote&)

When item is constructed, the Quote copy constructor is run. That constructor knows only about the bookNo and price members. It copies those members from the Quote part of bulk and ignores the members that are part of the Bulk_quote portion of bulk. Because the Bulk_quote part is ignored, we say that the Bulk_quote portion of bulk is sliced down. When we initialize or assign an object of a base type from an object of a derived type, only the base-class part of the derived object is copied, moved, or assigned. The derived part of the object is ignored.

Virtual Functions

1
2
3
4
Quote base("0-201-82470-1", 50);
print_total(cout, base, 10); // calls Quote::net_price
Bulk_quote derived("0-201-82470-1", 50, 5, .19);
print_total(cout, derived, 10); // calls Bulk_quote::net_price

It is crucial to understand that dynamic binding happens only when a virtual function is called through a pointer or a reference.

1
2
base = derived; // copies the Quote part of derived into base
base.net_price(20); // calls Quote::net_price

When we call a virtual function on an expression that has a plain—nonreference and nonpointer—type, that call is bound at compile time. For example, We can change the value of the object that base represents, but there is no way to change the type of that object. Hence, this call is resolved, at compile time, to the Quote version of net_price.

The key idea behind OOP is polymorphism. Polymorphism is derived from a Greek word meaning “many forms.” We speak of types related by inheritance as polymorphic types, because we can use the “many forms” of these types while ignoring the differences among them.

A function that is virtual in a base class is implicitly virtual in its derived classes. When a derived class overrides a virtual, the parameters in the base and derived classes must match exactly.

It is legal for a derived class to define a function with the same name as a virtual in its base class but with a different parameter list. In practice, such declarations often are a mistake—the class author intended to override a virtual from the base class but made a mistake in specifying the parameter list. Under the new standard we can specify override on a virtual function in a derived class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct B {
    virtual void f1(int) const;
    virtual void f2();
    void f3();
};
struct D1 : B {
    void f1(int) const override; // ok: f1 matches f1 in the base
    void f2(int) override; // error: B has no f2(int) function
    void f3() override; // error: f3 not virtual
    void f4() override; // error: B doesn't have a function named f4
};

We can also designate a function as final. Any attempt to override a function that has been defined as final will be flagged as an error:

1
2
3
4
5
6
7
8
struct D2 : B {
    // inherits f2() and f3() from B and overrides f1(int)
    void f1(int) const final; // subsequent classes can't override f1(int)
};
struct D3 : D2 {
    void f2(); // ok: overrides f2 inherited from the indirect base, B
    void f1(int) const; // error: D2 declared f2 as final
};

Like any other function, a virtual function can have default arguments. If a call uses a default argument, the value that is used is the one defined by the static type through which the function is called. That is, when a call is made through a reference or pointer to base, the default argument(s) will be those defined in the base class. The base-class arguments will be used even when the derived version of the function is run.

In some cases, we want to prevent dynamic binding of a call to a virtual function; we want to force the call to use a particular version of that virtual. We can use the scope operator to do so. For example, this code:

1
2
// calls the version from the base class regardless of the dynamic type of baseP
double undiscounted = baseP->Quote::net_price(42);

Ordinarily, only code inside member functions (or friends) should need to use the scope operator to circumvent the virtual mechanism. Why might we wish to circumvent the virtual mechanism? The most common reason is when a derived-class virtual function calls the version from the base class. In such cases, the base-class version might do work common to all types in the hierarchy. The versions defined in the derived classes would do whatever additional work is particular to their own type. If a derived virtual function that intended to call its base-class version omits the scope operator, the call will be resolved at run time as a call to the derived version itself, resulting in an infinite recursion.

Abstract Base Classes

In practice, we’d like to prevent users from creating Disc_quote objects at all. This class represents the general concept of a discounted book, not a concrete discount strategy. We can enforce this design intent, by defining net_price as a pure virtual function. Unlike ordinary virtuals, a pure virtual function does not have to be defined. It is worth noting that we can provide a definition for a pure virtual. However, the function body must be defined outside the class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// class to hold the discount rate and quantity
// derived classes will implement pricing strategies using these data
class Disc_quote : public Quote {
public:
    Disc_quote() = default;
    Disc_quote(const std::string& book, double price,
        std::size_t qty, double disc):
    Quote(book, price),
    quantity(qty), discount(disc) { }
    double net_price(std::size_t) const = 0;
protected:
    std::size_t quantity = 0; // purchase size for the discount to apply
    double discount = 0.0; // fractional discount to apply
};

A class containing (or inheriting without overridding) a pure virtual function is an abstract base class. An abstract base class defines an interface for subsequent classes to override. We cannot (directly) create objects of a type that is an abstract base class. Classes that inherit from Disc_quote must define net_price or those classes will be abstract as well.

Now we can reimplement Bulk_quote to inherit from Disc_quote rather than inheriting directly from Quote:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// the discount kicks in when a specified number of copies of the same book are sold
// the discount is expressed as a fraction to use to reduce the normal price
class Bulk_quote : public Disc_quote {
public:
    Bulk_quote() = default;
    Bulk_quote(const std::string& book, double price,
        std::size_t qty, double disc):
        Disc_quote(book, price, qty, disc) { }
    // overrides the base version to implement the bulk purchase discount policy
    double net_price(std::size_t) const override;
};

This version of Bulk_quote has a direct base class, Disc_quote, and an indirect base class, Quote. Each Bulk_quote object has three subobjects: an (empty) Bulk_quote part, a Disc_quote subobject, and a Quote subobject. As we’ve seen, each class controls the initialization of objects of its type. Therefore, even though Bulk_quote has no data members of its own, it provides the same four-argument constructor as in our original class.

Access Control and Inheritance

As we’ve seen, a class uses protected for those members that it is willing to share with its derived classes but wants to protect from general access. The protected specifier can be thought of as a blend of private and public:

  • Like private, protected members are inaccessible to users of the class.
  • Like public, protected members are accessible to members and friends of classes derived from this class.

In addition, protected has another important property:

  • A derived class member or friend may access the protected members of the base class only through a derived object. The derived class has no special access to the protected members of base-class objects.

To understand this last rule, consider the following example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Base {
protected:
    int prot_mem; // protected member
};
class Sneaky : public Base {
    friend void clobber(Sneaky&); // can access Sneaky::prot_mem
    friend void clobber(Base&); // can't access Base::prot_mem
    int j; // j is private by default
};
// ok: clobber can access the private and protected members in Sneaky objects
void clobber(Sneaky &s) { s.j = s.prot_mem = 0; }
// error: clobber can't access the protected members in Base
void clobber(Base &b) { b.prot_mem = 0; }

That function is just a friend of Sneaky.

Access to a member that a class inherits is controlled by a combination of the access specifier for that member in the base class, and the access specifier in the derivation list of the derived class. As an example, consider the following hierarchy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Base {
public:
    void pub_mem(); // public member
protected:
    int prot_mem; // protected member
private:
    char priv_mem; // private member
};
struct Pub_Derv : public Base {
    // ok: derived classes can access protected members
    int f() { return prot_mem; }
    // error: private members are inaccessible to derived classes
    char g() { return priv_mem; }
};
struct Priv_Derv : private Base {
    // private derivation doesn't affect access in the derived class
    int f1() const { return prot_mem; }
};

The derivation access specifier has no effect on whether members (and friends) of a derived class may access the members of its own direct base class. Access to the members of a base class is controlled by the access specifiers in the base class itself. The purpose of the derivation access specifier is to control the access that users of the derived class—including other classes derived from the derived class—have to the members inherited from Base:

1
2
3
4
Pub_Derv d1; // members inherited from Base are public
Priv_Derv d2; // members inherited from Base are private
d1.pub_mem(); // ok: pub_mem is public in the derived class
d2.pub_mem(); // error: pub_mem is private in the derived class

The derivation access specifier used by a derived class also controls access from classes that inherit from that derived class:

1
2
3
4
5
6
7
8
struct Derived_from_Public : public Pub_Derv {
    // ok: Base::prot_mem remains protected in Pub_Derv
    int use_base() { return prot_mem; }
};
struct Derived_from_Private : public Priv_Derv {
    // error: Base::prot_mem is private in Priv_Derv
    int use_base() { return prot_mem; }
};

Had we defined another class, say, Prot_Derv, that used protected inheritance, the public members of Base would be protected members in that class. Users of Prot_Derv would have no access to pub_mem, but the members and friends of Prot_Derv could access that inherited member.

Accessibility of Derived-to-Base Conversion

Whether the derived-to-base conversion is accessible depends on which code is trying to use the conversion and may depend on the access specifier used in the derived class’ derivation. Assuming D inherits from B:

  • User code may use the derived-to-base conversion only if D inherits publicly from B. User code may not use the conversion if D inherits from B using either protected or private.
  • Member functions and friends of D can use the conversion to B regardless of how D inherits from B. The derived-to-base conversion to a direct base class is always accessible to members and friends of a derived class.
  • Member functions and friends of classes derived from D may use the derived-to-base conversion if D inherits from B using either public or protected. Such code may not use the conversion if D inherits privately from B.

In the absence of inheritance, we can think of a class as having two different kinds of users: ordinary users and implementors. Ordinary users write code that uses objects of the class type; such code can access only the public (interface) members of the class. Implementors write the code contained in the members and friends of the class. The members and friends of the class can access both the public and private (implementation) sections.

Under inheritance, there is a third kind of user, namely, derived classes. A base class makes protected those parts of its implementation that it is willing to let its derived classes use. The protected members remain inaccessible to ordinary user code; private members remain inaccessible to derived classes and their friends.

Like any other class, a class that is used as a base class makes its interface members public . A class that is used as a base class may divide its implementation into those members that are accessible to derived classes and those that remain accessible only to the base class and its friends. An implementation member should be protected if it provides an operation or data that a derived class will need to use in its own implementation. Otherwise, implementation members should be private.

Friendship and Inheritance

Just as friendship is not transitive, friendship is also not inherited.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Base {
    // added friend declaration; other members as before
    friend class Pal; // Pal has no access to classes derived from Base
};
class Pal {
public:
    int f(Base b) { return b.prot_mem; } // ok: Pal is a friend of Base
    int f2(Sneaky s) { return s.j; } // error: Pal not friend of Sneaky
    // access to a base class is controlled by the base class, even inside a derived object
    int f3(Sneaky s) { return s.prot_mem; } // ok: Pal is a friend
};

The fact that f3 is legal may seem surprising, but it follows directly from the notion that each class controls access to its own members. Pal is a friend of Base, so Pal can access the members of Base objects. That access includes access to Base objects that are embedded in an object of a type derived from Base.

Exempting Individual Members

Sometimes we need to change the access level of a name that a derived class inherits. We can do so by providing a using declaration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Base {
public:
    std::size_t size() const { return n; }
protected:
    std::size_t n;
};
class Derived : private Base { // note: private inheritance
public:
    // maintain access levels for members related to the size of the object
    using Base::size;
protected:
    using Base::n;
};

Because Derived uses private inheritance, the inherited members, size and n, are (by default) private members of Derived. The using declarations adjust the accessibility of these members. Users of Derived can access the size member, and classes subsequently derived from Derived can access n.

A using declaration inside a class can name any accessible member of a direct or indirect base class. Access to a name specified in a using declaration depends on the access specifier preceding the using declaration. That is, if a using declaration appears in a private part of the class, that name is accessible to members and friends only. If the declaration is in a public section, the name is available to all users of the class. If the declaration is in a protected section, the name is accessible to the members, friends, and derived classes.

Default Inheritance Protection Levels

By default, a derived class defined with the class keyword has private inheritance; a derived class defined with struct has public inheritance:

1
2
3
class Base { /* ... */ };
struct D1 : Base { /* ... */ }; // public inheritance by default
class D2 : Base { /* ... */ }; // private inheritance by default

The only differences are the default access specifier for members and the default derivation access specifier. There are no other distinctions.

Class Scope under Inheritance

Each class defines its own scope within which its members are defined. Under inheritance, the scope of a derived class is nested inside the scope of its base classes. If a name is unresolved within the scope of the derived class, the enclosing base-class scopes are searched for a definition of that name.

Name Lookup Happens at Compile Time. The static type of an object, reference, or pointer determines which members of that object are visible.

As usual, names defined in an inner scope (e.g., a derived class) hide uses of that name in the outer scope. Aside from overriding inherited virtual functions, a derived class usually should not reuse names defined in its base class.

As Usual, Name Lookup Happens before Type Checking. As we’ve seen, functions declared in an inner scope do not overload functions declared in an outer scope. As a result, functions defined in a derived class do not overload members defined in its base class(es). As in any other scope, if a member in a derived class has the same name as a base-class member, then the derived member hides the base-class member within the scope of the derived class. The base member is hidden even if the functions have different parameter lists:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Base {
    int memfcn();
};
struct Derived : Base {
    int memfcn(int); // hides memfcn in the base
};
Derived d; Base b;
b.memfcn(); // calls Base::memfcn
d.memfcn(10); // calls Derived::memfcn
d.memfcn(); // error: memfcn with no arguments is hidden
d.Base::memfcn(); // ok: calls Base::memfcn

Overriding Overloaded Functions

A derived class can override zero or more instances of the overloaded functions it inherits. If a derived class wants to make all the overloaded versions available through its type, then it must override all of them or none of them. Instead of overriding every base-class version that it inherits, a derived class can provide a using declaration for the overloaded member. A using declaration specifies only a name; it may not specify a parameter list. Thus, a using declaration for a base-class member function adds all the overloaded instances of that function to the scope of the derived class.

Virtual Destructors

The primary direct impact that inheritance has on copy control for a base class is that a base class generally should define a virtual destructor. As with any other function, we arrange to run the proper destructor by defining the destructor as virtual in the base class:

1
2
3
4
5
class Quote {
public:
    // virtual destructor needed if a base pointer pointing to a derived object is deleted
    virtual ~Quote() = default; // dynamic binding for the destructor
};

Like any other virtual, the virtual nature of the destructor is inherited. Thus, classes derived from Quote have virtual destructors, whether they use the synthesized destructor or define their own version. So long as the base class destructor is virtual, when we delete a pointer to base, the correct destructor will be run:

1
2
3
4
Quote *itemP = new Quote; // same static and dynamic type
delete itemP; // destructor for Quote called
itemP = new Bulk_quote; // static and dynamic types differ
delete itemP; // destructor for Bulk_quote called

Executing delete on a pointer to base that points to a derived object has undefined behavior if the base’s destructor is not virtual.

Destructors for base classes are an important exception to the rule of thumb that if a class needs a destructor, it also needs copy and assignment.

The fact that a base class needs a virtual destructor has an important indirect impact on the definition of base and derived classes: If a class defines a destructor—even if it uses = default to use the synthesized version—the compiler will not synthesize a move operation for that class.

Synthesized Copy Control and Inheritance

The synthesized copy-control members in a base or a derived class execute like any other synthesized constructor, assignment operator, or destructor: They memberwise initialize, assign, or destroy the members of the class itself. In addition, these synthesized members initialize, assign, or destroy the direct base part of an object by using the corresponding operation from the base class.

It is worth noting that it doesn’t matter whether the base-class member is itself synthesized or has a an user-provided definition. All that matters is that the corresponding member is accessible and that it is not a deleted function.

The synthesized default constructor, or any of the copy-control members of either a base or a derived class, may be defined as deleted for the same reasons as in any other class. In addition, the way in which a base class is defined can cause a derived-class member to be defined as deleted:

  • If the default constructor, copy constructor, copy-assignment operator, or destructor in the base class is deleted or inaccessible, then the corresponding member in the derived class is defined as deleted, because the compiler can’t use the base-class member to construct, assign, or destroy the base-class part of the object.

  • If the base class has an inaccessible or deleted destructor, then the synthesized default and copy constructors in the derived classes are defined as deleted, because there is no way to destroy the base part of the derived object.

  • As usual, the compiler will not synthesize a deleted move operation. If we use = default to request a move operation, it will be a deleted function in the derived if the corresponding operation in the base is deleted or inaccessible, because the base class part cannot be moved. The move constructor will also be deleted if the base class destructor is deleted or inaccessible.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class B {
public:
    B();
    B(const B&) = delete;
    // other members, not including a move constructor
};
class D : public B {
    // no constructors
};
D d; // ok: D's synthesized default constructor uses B's default constructor
D d2(d); // error: D's synthesized copy constructor is deleted
D d3(std::move(d)); // error: implicitly uses D's deleted copy constructor

Because the copy constructor is defined, the compiler will not synthesize a move constructor for class B. If a class derived from B wanted to allow its objects to be copied or moved, that derived class would have to define its own versions of these constructors.

Move Operations and Inheritance

As we’ve seen, most base classes define a virtual destructor. As a result, by default, base classes generally do not get synthesized move operations. Moreover, by default, classes derived from a base class that doesn’t have move operations don’t get synthesized move operations either.

Because lack of a move operation in a base class suppresses synthesized move for its derived classes, base classes ordinarily should define the move operations if it is sensible to do so. Our Quote class can use the synthesized versions. However, Quote must define these members explicitly. Once it defines its move operations, it must also explicitly define the copy versions as well:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Quote {
public:
    Quote() = default; // memberwise default initialize
    Quote(const Quote&) = default; // memberwise copy
    Quote(Quote&&) = default; // memberwise copy
    Quote& operator=(const Quote&) = default; // copy assign
    Quote& operator=(Quote&&) = default; // move assign
    virtual ~Quote() = default;
    // other members as before
};

Now, Quote objects will be memberwise copied, moved, assigned, and destroyed. Moreover, classes derived from Quote will automatically obtain synthesized move operations as well, unless they have members that otherwise preclude move.

Derived-Class Copy-Control Members

When a derived class defines a copy or move operation, that operation is responsible for copying or moving the entire object, including base-class members. We ordinarily use the corresponding base-class constructor to initialize the base part of the object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Base { /* ... */ } ;
class D: public Base {
public:
    // by default, the base class default constructor initializes the base part of an object
    // to use the copy or move constructor, we must explicitly call that
    // constructor in the constructor initializer list
    D(const D& d): Base(d) // copy the base members
    /* initializers for members of D */ { /* ... */ }
    D(D&& d): Base(std::move(d)) // move the base members
    /* initializers for members of D */ { /* ... */ }
};

By default, the base-class default constructor initializes the base-class part of a derived object. If we want copy (or move) the base-class part, we must explicitly use the copy (or move) constructor for the base class in the derived’s constructor initializer list.

Derived-Class Assignment Operator

Like the copy and move constructors, a derived-class assignment operator, must assign its base part explicitly:

1
2
3
4
5
6
7
8
// Base::operator=(const Base&) is not invoked automatically
D &D::operator=(const D &rhs)
{
    Base::operator=(rhs); // assigns the base part
    // assign the members in the derived class, as usual,
    // handling self-assignment and freeing existing resources as appropriate
    return *this;
}

Derived-Class Destructor

Recall that the data members of an object are implicitly destroyed after the destructor body completes. Similarly, the base-class parts of an object are also implicitly destroyed. As a result, unlike the constructors and assignment operators, a derived destructor is responsible only for destroying the resources allocated by the derived class:

1
2
3
4
5
class D: public Base {
public:
    // Base::~Base invoked automatically
    ~D() { /* do what it takes to clean up derived members */ }
};

Objects are destroyed in the opposite order from which they are constructed: The derived destructor is run first, and then the base-class destructors are invoked, back up through the inheritance hierarchy.

Calls to Virtuals in Constructors and Destructors

As we’ve seen, the base-class part of a derived object is constructed first. While the base-class constructor is executing, the derived part of the object is uninitialized. Similarly, derived objects are destroyed in reverse order, so that when a base class destructor runs, the derived part has already been destroyed. As a result, while these base-class members are executing, the object is incomplete.

To accommodate this incompleteness, the compiler treats the object as if its type changes during construction or destruction. That is, while an object is being constructed it is treated as if it has the same class as the constructor; calls to virtual functions will be bound as if the object has the same type as the constructor itself.

To understand this behavior, consider what would happen if the derived-class version of a virtual was called from a base-class constructor. However, those members are uninitialized while a base constructor is running. If such access were allowed, the program would probably crash.

If a constructor or destructor calls a virtual, the version that is run is the one corresponding to the type of the constructor or destructor itself.

Inherited Constructors

Under the new standard, a derived class can reuse the constructors defined by its direct base class. If the derived class does not directly define these constructors, the compiler synthesizes them as usual. A derived class inherits its base-class constructors by providing a using declaration that names its (direct) base class. As an example, we can redefine our Bulk_quote class to inherit its constructors from Disc_quote:

1
2
3
4
5
class Bulk_quote : public Disc_quote {
public:
    using Disc_quote::Disc_quote; // inherit Disc_quote's constructors
    double net_price(std::size_t) const;
};

Ordinarily, a using declaration only makes a name visible in the current scope. When applied to a constructor, a using declaration causes the compiler to generate code. The compiler generates a derived constructor corresponding to each constructor in the base. That is, for each constructor in the base class, the compiler generates a constructor in the derived class that has the same parameter list. These compiler-generated constructors have the form:

1
derived(parms) : base(args) { }

In our Bulk_quote class, the inherited constructor would be equivalent to:

1
2
3
Bulk_quote(const std::string& book, double price,
            std::size_t qty, double disc):
    Disc_quote(book, price, qty, disc) { }

Unlike using declarations for ordinary members, a constructor using declaration does not change the access level of the inherited constructor(s). Moreover, a using declaration can’t specify explicit or constexpr. If a constructor in the base is explicit or constexpr, the inherited constructor has the same property.

If a base-class constructor has default arguments, those arguments are not inherited. Instead, the derived class gets multiple inherited constructors in which each parameter with a default argument is successively omitted.

If a base class has several constructors, then with two exceptions, the derived class inherits each of the constructors from its base class. The first exception is that a derived class can inherit some constructors and define its own versions of other constructors. The second exception is that the default, copy, and move constructors are not inherited.

Containers and Inheritance

We cannot put objects of types related by inheritance directly into a container, because there is no way to define a container that holds elements of differing types.

1
2
3
4
5
6
vector<Quote> basket;
basket.push_back(Quote("0-201-82470-1", 50));
// ok, but copies only the Quote part of the object into basket
basket.push_back(Bulk_quote("0-201-54848-8", 50, 10, .25));
// calls version defined by Quote, prints 750, i.e., 15 * $50
cout << basket.back().net_price(15) << endl;

The elements in basket are Quote objects. When we add a Bulk_quote object to the vector its derived part is ignored. Because derived objects are “sliced down” when assigned to a base-type object, containers and types related by inheritance do not mix well.

When we need a container that holds objects related by inheritance, we typically define the container to hold pointers (preferably smart pointers) to the base class.

1
2
3
4
5
6
vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("0-201-82470-1", 50));
basket.push_back(
make_shared<Bulk_quote>("0-201-54848-8", 50, 10, .25));
// calls the version defined by Quote; prints 562.5, i.e., 15 * $50 less the discount
cout << basket.back()->net_price(15) << endl;

Writing a Basket Class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Basket {
public:
    // Basket uses synthesized default constructor and copy-control members
    void add_item(const std::shared_ptr<Quote> &sale)
        { items.insert(sale); }
    // prints the total price for each book and the overall total for all items in the basket
    double total_receipt(std::ostream&) const;
private:
    // function to compare shared_ptrs needed by the multiset member
    static bool compare(const std::shared_ptr<Quote> &lhs,
                        const std::shared_ptr<Quote> &rhs)
        { return lhs->isbn() < rhs->isbn(); }
    // multiset to hold multiple quotes, ordered by the compare member
    std::multiset<std::shared_ptr<Quote>, decltype(compare)*>
        items{compare};
};

The elements in our multiset are shared_ptrs and there is no less-than operator for shared_ptr. As a result, we must provide our own comparison operation to order the elements. Here, we define a private static member, named compare, that compares the isbns of the objects to which the shared_ptrs point. We initialize our multiset to use this comparison function through an in-class initializer:

1
2
3
// multiset to hold multiple quotes, ordered by the compare member
std::multiset<std::shared_ptr<Quote>, decltype(compare)*>
    items{compare};

The multiset member is named items, and we’re initializing items to use our compare function. decltype(compare)* in the template parameter specifies the type of the comparator. It doesn’t tell which function is to be used. So we need specify a compare function. A better approach might be to use a function class type; then the function call can be resolved at compile time, and a default-constructed object will do the right thing:

1
2
3
4
5
6
7
8
struct compare {
    bool operator()(std::shared_ptr<Quote> &lhs, 
                    std::shared_ptr<Quote> &rhs) const {
        return lhs->isbn() < rhs->isbn();
    }
};

std::multiset<std::shared_ptr<Quote>, compare> items;

The member function total_receipt prints an itemized bill for the contents of the basket and returns the price for all the items in the basket:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
double Basket::total_receipt(ostream &os) const
{
    double sum = 0.0; // holds the running total
    // iter refers to the first element in a batch of elements with the same ISBN
    // upper_bound returns an iterator to the element just past the end of that batch
    for (auto iter = items.cbegin();
        iter != items.cend();
        iter = items.upper_bound(*iter)) {
    // we know there's at least one element with this key in the Basket
    // print the line item for this book
        sum += print_total(os, **iter, items.count(*iter));
    }
    os << "Total Sale: " << sum << endl; // print the final overall total
    return sum;
}

We skip over all the elements that match the current key by calling upper_bound. Inside the for loop, we call print_total to print the details for each book in the basket. When we dereference iter, we get a shared_ptr that points to the object we want to print. To get that object, we must dereference that shared_ptr. Thus, **iter is a Quote object.

Users of Basket still have to deal with dynamic memory, because add_item takes a shared_ptr. As a result, users have to write code such as

1
2
3
Basket bsk;
bsk.add_item(make_shared<Quote>("123", 45));
bsk.add_item(make_shared<Bulk_quote>("345", 45, 3, .15));

Our next step will be to redefine add_item so that it takes a Quote object instead of a shared_ptr. This new version of add_item will handle the memory allocation so that our users no longer need to do so. We’ll define two versions, one that will copy its given object and the other that will move from it.

1
2
void add_item(const Quote& sale); // copy the given object
void add_item(Quote&& sale); // move the given object

The only problem is that add_item doesn’t know what type to allocate. Somewhere there will be a new expression such as: new Quote(sale). Unfortunately, this expression allocates an object of type Quote and copies the Quote portion of sale. However, sale might refer to a Bulk_quote object, in which case, that object will be sliced down. We’ll solve this problem by giving our Quote classes a virtual member that allocates a copy of itself.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Quote {
public:
    // virtual function to return a dynamically allocated copy of itself
    // these members use reference qualifiers; see §13.6.3 (p. 546)
    virtual  Quote*  clone()  const  &  {return  new Quote(*this);}
    virtual Quote* clone() && {return new Quote(std::move(*this));}
    // other members as before
};
class Bulk_quote : public Quote {
    Bulk_quote*  clone()  const  &  {return  new Bulk_quote(*this);}
    Bulk_quote* clone() && {return new Bulk_quote(std::move(*this));}
    // other members as before
};

Because we have a copy and a move version of add_item, we defined lvalue and rvalue versions of clone. Each clone function allocates a new object of its own type. The const lvalue reference member copies itself into that newly allocated object; the rvalue reference member moves its own data. Using clone, it is easy to write our new versions of add_item:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Basket {
public:
    void add_item(const Quote& sale) // copy the given object
    { 
        items.insert(std::shared_ptr<Quote>(sale.clone())); 
    }
    void add_item(Quote&& sale) // move the given object
    { 
        items.insert(std::shared_ptr<Quote>(std::move(sale).clone()));
    }
    // other members as before
};

Our clone function is also virtual. Whether the Quote or Bulk_quote function is run, depends on the dynamic type of sale. We bind a shared_ptr to that object and call insert to add this newly allocated object to items. Note that because shared_ptr supports the derived-to-base conversion, we can bind a shared_ptr<Quote> to a Bulk_quote*.

Templates and Generic Programming

When we write a generic program, we write the code in a way that is independent of any particular type. For example, the library provides a single, generic definition of each container, such as vector. Templates are the foundation of generic programming. A template is a blueprint or formula for creating classes or functions. When we use a generic type, such as vector, or a generic function, such as find, we supply the information needed to transform that blueprint into a specific class or function.

Function Templates

Rather than defining a new function for each type, we can define a function template. A function template is a formula from which we can generate type-specific versions of that function. The template version of compare looks like:

1
2
3
4
5
6
7
template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

A template definition starts with the keyword template followed by a template parameter list, which is a comma-separated list of one or more template parameters bracketed by the less-than (<) and greater-than (>) tokens. Template parameters represent types or values used in the definition of a class or function. When we use a template, we specify—either implicitly or explicitly template argument(s) to bind to the template parameter(s). Our compare function declares one type parameter named T.

When we call a function template, the compiler (ordinarily) uses the arguments of the call to deduce the template argument(s) for us. That is, when we call compare, the compiler uses the type of the arguments to determine what type to bind to the template parameter T.

The compiler uses the deduced template parameter(s) to instantiate a specific version of the function for us. When the compiler instantiates a template, it creates a new “instance” of the template using the actual template argument(s) in place of the corresponding template parameter(s).

1
2
3
4
5
// instantiates int compare(const int&, const int&)
cout << compare(1, 0) << endl; // T is int
// instantiates int compare(const vector<int>&, const vector<int>&)
vector<int> vec1{1, 2, 3}, vec2{4, 5, 6};
cout << compare(vec1, vec2) << endl; // T is vector<int>

Our compare function has one template type parameter. In particular, a type parameter can be used to name the return type or a function parameter type, and for variable declarations or casts inside the function body:

1
2
3
4
5
6
7
// ok: same type used for the return type and parameter
template <typename T> T foo(T* p)
{
    T tmp = *p; // tmp will have the type to which p points
    // ...
    return tmp;
}

Each type parameter must be preceded by the keyword class or typename:

1
2
// error: must precede U with either typename or class
template <typename T, U> T calc(const T&, const U&);

These keywords have the same meaning and can be used interchangeably inside a template parameter list. A template parameter list can use both keywords:

1
2
// ok: no distinction between typename and class in a template parameter list
template <typename T, class U> calc (const T&, const U&);

Nontype Template Parameters

In addition to defining type parameters, we can define templates that take nontype parameters. A nontype parameter represents a value rather than a type. Nontype parameters are specified by using a specific type name instead of the class or typename keyword. When the template is instantiated, nontype parameters are replaced with a value supplied by the user or deduced by the compiler. These values must be constant expressions.

As an example, we can write a version of compare that will handle string literals. Because we’d like to be able to compare literals of different lengths, we’ll give our template two nontype parameters. The first template parameter will represent the size of the first array, and the second parameter will represent the size of the second array:

1
2
3
4
5
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
    return strcmp(p1, p2);
}

When we call this version of compare:

1
compare("hi", "mom")

the compiler will use the size of the literals to instantiate a version of the template with the sizes substituted for N and M. Remembering that the compiler inserts a null terminator at the end of a string literal, the compiler will instantiate

1
int compare(const char (&p1)[3], const char (&p2)[4])

A nontype parameter may be an integral type, or a pointer or (lvalue) reference to an object or to a function type. An argument bound to a nontype integral parameter must be a constant expression. Arguments bound to a pointer or reference nontype parameter must have static lifetime. We may not use an ordinary (nonstatic) local object or a dynamic object as a template argument for reference or pointer nontype template parameters. A pointer parameter can also be instantiated by nullptr or a zero-valued constant expression.

A template nontype parameter is a constant value inside the template definition. A nontype parameter can be used when constant expressions are required, for example, to specify the size of an array.

inline and constexpr Function Templates

The inline or constexpr specifier follows the template parameter list and precedes the return type:

1
2
3
4
// ok: inline specifier follows the template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of the inline specifier
inline template <typename T> T min(const T&, const T&);

Type-Independent Code

Simple though it is, our initial compare function illustrates two important principles for writing generic code:

  • The function parameters in the template are references to const.
  • The tests in the body use only < comparisons.

By making the function parameters references to const, we ensure that our function can be used on types that cannot be copied. Most types—including the built-in types and, except for unique_ptr and the IO types, all the library types we’ve used do allow copying. By writing the code using only the < operator, we reduce the requirements on types that can be used with our compare function. In fact, if we were truly concerned about type independence and portability, we probably should have defined our function using the less:

1
2
3
4
5
6
7
// version of compare that will be correct even if used on pointers;
template <typename T> int compare(const T &v1, const T &v2)
{
    if (less<T>()(v1, v2)) return -1;
    if (less<T>()(v2, v1)) return 1;
    return 0;
}

The problem with our original version is that if a user calls it with two pointers and those pointers do not point to the same array, then our code is undefined.

Template Compilation

The fact that code is generated only when we use a template (and not when we define it) affects how we organize our source code and when errors are detected. Ordinarily, when we call a function, the compiler needs to see only a declaration for the function. Similarly, when we use objects of class type, the class definition must be available, but the definitions of the member functions need not be present. As a result, we put class definitions and function declarations in header files and definitions of ordinary and class-member functions in source files.

Templates are different: To generate an instantiation, the compiler needs to have the code that defines a function template or class template member function. As a result, unlike nontemplate code, headers for templates typically include definitions as well as declarations. Definitions of function templates and member functions of class templates are ordinarily put into header files.

When we write a template, the code may not be overtly type specific, but template code usually makes some assumptions about the types that will be used. For example, the code inside our original compare function assumes that the argument type has a < operator. If the arguments passed to compare have a < operation, then the code is fine, but not otherwise. For example:

1
2
Sales_data data1, data2;
cout << compare(data1, data2) << endl; // error: no < on Sales_data

This instantiation generates a version of the function that will not compile. However, errors such as this one cannot be detected until the compiler instantiates the definition of compare on type Sales_data.

Class Templates

As an example, we’ll implement a template version of StrBlob. We’ll name our template Blob to indicate that it is no longer specific to strings.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <typename T> class Blob {
public:
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;
    // constructors
    Blob();
    Blob(std::initializer_list<T> il);
    // number of elements in the Blob
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    // add and remove elements
    void push_back(const T &t) {data->push_back(t);}
    // move version;
    void push_back(T &&t) { data->push_back(std::move(t)); }
    void pop_back();
    // element access
    T& back();
    T& operator[](size_type i);
private:
    std::shared_ptr<std::vector<T>> data;
    // throws msg if data[i] isn't valid
    void check(size_type i, const std::string &msg) const;
};

As we’ve seen many times, when we use a class template, we must supply extra information. We can now see that that extra information is a list of explicit template arguments that are bound to the template’s parameters. For example, to define a type from our Blob template, we must provide the element type:

1
2
Blob<int> ia; // empty Blob<int>
Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> with five elements

The compiler generates a different class for each element type we specify. Each instantiation of a class template constitutes an independent class. The type Blob has no relationship to, or any special access to, the members of any other Blob type.

1
2
3
// these definitions instantiate two distinct Blob types
Blob<string> names; // Blob that holds strings
Blob<double> prices;// different element type

Member Functions of Class Templates

As with any class, we can define the member functions of a class template either inside or outside of the class body. As with any other class, members defined inside the class body are implicitly inline. As usual, when we define a member outside its class, we must say to which class the member belongs. We’ll start by defining the check member, which verifies a given index:

1
2
3
4
5
6
template <typename T>
void Blob<T>::check(size_type i, const std::string &msg) const
{
    if (i >= data->size())
        throw std::out_of_range(msg);
}

The subscript operator and back function use the template parameter to specify the return type but are otherwise unchanged:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
template <typename T>
T& Blob<T>::back()
{
    check(0, "back on empty Blob");
    return data->back();
}
template <typename T>
T& Blob<T>::operator[](size_type i)
{
    // if i is too big, check will throw, preventing access to a nonexistent element
    check(i, "subscript out of range");
    return (*data)[i];
}

In our original StrBlob class these operators returned string&. The template versions will return a reference to whatever type is used to instantiate Blob.

As with any other member defined outside a class template, a constructor starts by declaring the template parameters for the class template of which it is a member:

1
2
template <typename T>
Blob<T>::Blob(): data(std::make_shared<std::vector<T>>()) { }

Similarly, the constructor that takes an initializer_list uses its type parameter T as the element type for its initializer_list parameter:

1
2
3
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il):
    data(std::make_shared<std::vector<T>>(il)) { }

To use this constructor, we must pass an initializer_list in which the elements are compatible with the element type of the Blob:

1
Blob<string> articles = {"a", "an", "the"};

By default, a member of an instantiated class template is instantiated only if the member is used. For example, this code:

1
2
3
4
5
// instantiates Blob<int> and the initializer_list<int> constructor
Blob<int> squares = {0,1,2,3,4,5,6,7,8,9};
// instantiates Blob<int>::size() const
for (size_t i = 0; i != squares.size(); ++i)
squares[i] = i*i; // instantiates Blob<int>::operator[](size_t)

There is one exception to the rule that we must supply template arguments when we use a class template type. Inside the scope of the class template itself, we may use the name of the template without arguments:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// BlobPtr throws an exception on attempts to access a nonexistent element
template <typename T> class BlobPtr
public:
    BlobPtr(): curr(0) { }
    BlobPtr(Blob<T> &a, size_t sz = 0):
        wptr(a.data), curr(sz) { }
    T& operator*() const
    { 
        auto p = check(curr, "dereference past end");
        return (*p)[curr]; // (*p) is the vector to which this object points
    }
    // increment and decrement
    BlobPtr& operator++(); // prefix operators
    BlobPtr& operator--();
private:
    // check returns a shared_ptr to the vector if the check succeeds
    std::shared_ptr<std::vector<T>>
        check(std::size_t, const std::string&) const;
    // store a weak_ptr, which means the underlying vector might be destroyed
    std::weak_ptr<std::vector<T>> wptr;
    std::size_t curr; // current position within the array
};

Careful readers will have noted that the prefix increment and decrement members of BlobPtr return BlobPtr&, not BlobPtr&. When we are inside the scope of a class template, the compiler treats references to the template itself as if we had supplied template arguments matching the template’s own parameters. That is, it is as if we had written:

1
2
BlobPtr<T>& operator++();
BlobPtr<T>& operator--();

When we define members outside the body of a class template, we must remember that we are not in the scope of the class until the class name is seen:

1
2
3
4
5
6
7
8
9
// postfix: increment/decrement the object but return the unchanged value
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int)
{
    // no check needed here; the call to prefix increment will do the check
    BlobPtr ret = *this; // save the current value
    ++*this; // advance one element; prefix ++ checks the increment
    return ret; // return the saved state
}

Because the return type appears outside the scope of the class, we must specify that the return type returns a BlobPtr instantiated with the same type as the class. Inside the function body, we are in the scope of the class so do not need to repeat the template argument when we define ret. Hence, the definition of ret is as if we had written:

1
BlobPtr<T> ret = *this;

Inside the scope of a class template, we may refer to the template without specifying template argument(s).

Class Templates and Friends

When a class contains a friend declaration, the class and the friend can independently be templates or not. A class template that has a nontemplate friend grants that friend access to all the instantiations of the template. When the friend is itself a template, the class granting friendship controls whether friendship includes all instantiations of the template or only specific instantiation(s).

In order to refer to a specific instantiation of a template (class or function) we must first declare the template itself.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// forward declarations needed for friend declarations in Blob
template <typename> class BlobPtr;
template <typename> class Blob; // needed for parameters in operator==
template <typename T>
    bool operator==(const Blob<T>&, const Blob<T>&);
template <typename T> class Blob {
    // each instantiation of Blob grants access to the version of
    // BlobPtr and the equality operator instantiated with the same type
    friend class BlobPtr<T>;
    friend bool operator==<T>
        (const Blob<T>&, const Blob<T>&);
    // other members
};

A class can also make every instantiation of another template its friend, or it may limit friendship to a specific instantiation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// forward declaration necessary to befriend a specific instantiation of a template
template <typename T> class Pal;
class C 
{ 
    // C is an ordinary, nontemplate class
    friend class Pal<C>; // Pal instantiated with class C is a friend to C
    // all instances of Pal2 are friends to C;
    // no forward declaration required when we befriend all instantiations
    template <typename T> friend class Pal2;
};
template <typename T> class C2 
{ 
    // C2 is itself a class template
    // each instantiation of C2 has the same instance of Pal as a friend
    friend class Pal<T>; // a template declaration for Pal must be in scope
    // all instances of Pal2 are friends of each instance of C2, prior declaration needed
    template <typename X> friend class Pal2;
    // Pal3 is a nontemplate class that is a friend of every instance of C2
    friend class Pal3; // prior declaration for Pal3 not needed
};

To allow all instantiations as friends, the friend declaration must use template parameter(s) that differ from those used by the class itself.

Under the new standard, we can make a template type parameter a friend:

1
2
3
4
template <typename Type> class Bar {
friend Type; // grants access to the type used to instantiate Bar
    // ...
};

Here we say that whatever type is used to instantiate Bar is a friend.

Template Type Aliases

we can define a typedef that refers to that instantiated class:

1
typedef Blob<string> StrBlob;

Because a template is not a type, we cannot define a typedef that refers to a template. However, the new standard lets us define a type alias for a class template:

1
2
template<typename T> using twin = pair<T, T>;
twin<string> authors; // authors is a pair<string, string>

A template type alias is a synonym for a family of classes:

1
2
twin<int> win_loss; // win_loss is a pair<int, int>
twin<double> area; // area is a pair<double, double>

static Members of Class Templates

1
2
3
4
5
6
7
8
template <typename T> class Foo {
public:
    static std::size_t count() { return ctr; }
    // other interface members
private:
    static std::size_t ctr;
    // other implementation members
};

Each instantiation of Foo has its own instance of the static members.

1
2
3
4
// instantiates static members Foo<string>::ctr and Foo<string>::count
Foo<string> fs;
// all three objects share the same Foo<int>::ctr and Foo<int>::count members
Foo<int> fi, fi2, fi3;

As with any other static data member, there must be exactly one definition of each static data member of a template class. We define a static data member as a template similarly to how we define the member functions of that template:

1
2
template <typename T>
size_t Foo<T>::ctr = 0; // define and initialize ctr

When Foo is instantiated for a particular template argument type, a separate ctr will be instantiated for that class type and initialized to 0.

As with static members of nontemplate classes, we can access a static member of a class template through an object of the class type or by using the scope operator to access the member directly. To use a static member through the class, we must refer to a specific instantiation:

1
2
3
4
5
Foo<int> fi; // instantiates Foo<int> class
// and the static data member ctr
auto ct = Foo<int>::count(); // instantiates Foo<int>::count
ct = fi.count(); // uses Foo<int>::count
ct = Foo::count(); // error: which template instantiation?

Like any other member function, a static member function is instantiated only if it is used in a program.

Template Parameters

Like the names of function parameters, a template parameter name has no intrinsic meaning. We ordinarily name type parameters T, but we can use any name:

1
2
3
4
5
6
template <typename Foo> Foo calc(const Foo& a, const Foo& b)
{
    Foo tmp = a; // tmp has the same type as the parameters and return type
    // ...
    return tmp; // return type and parameters have the same type
}

Because a parameter name cannot be reused, the name of a template parameter can appear only once with in a given template parameter list:

1
2
// error: illegal reuse of template parameter name V
template <typename V, typename V> // ...

A template declaration must include the template parameters :

1
2
3
// declares but does not define compare and Blob
template <typename T> int compare(const T&, const T&);
template <typename T> class Blob;

The names of a template parameter need not be the same across the declaration(s) and the definition of the same template:

1
2
3
4
5
6
// all three uses of calc refer to the same function template
template <typename T> T calc(const T&, const T&); // declaration
template <typename U> U calc(const U&, const U&); // declaration
// definition of the template
template <typename Type>
Type calc(const Type& a, const Type& b) { /* . . . */ }

Declarations for all the templates needed by a given file usually should appear together at the beginning of a file before any code that uses those names.

Using Class Members That Are Types

Recall that we use the scope operator (::) to access both static members and type members. In ordinary code, the compiler knows whether a name accessed through the scope operator is a type or a static member. Assuming T is a template type parameter, When the compiler sees code such as T::mem it won’t know until instantiation time whether mem is a type or a static data member. However, in order to process the template, the compiler must know whether a name represents a type. For example:

1
T::size_type * p;

it needs to know whether we’re defining a variable named p or are multiplying a static data member named size_type by a variable named p.

By default, the language assumes that a name accessed through the scope operator is not a type. As a result, if we want to use a type member of a template type parameter, we must explicitly tell the compiler that the name is a type. We do so by using the keyword typename:

1
2
3
4
5
6
7
8
template <typename T>
typename T::value_type top(const T& c)
{
    if (!c.empty())
        return c.back();
    else
        return typename T::value_type();
}

When we want to inform the compiler that a name represents a type, we must use the keyword typename, not class.

Default Template Arguments

Under the new standard, we can supply default arguments for both function and class templates. Earlier versions of the language, allowed default arguments only with class templates.

1
2
3
4
5
6
7
8
9
// compare has a default template argument, less<T>
// and a default function argument, F()
template <typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F())
{
    if (f(v1, v2)) return -1;
    if (f(v2, v1)) return 1;
    return 0;
}

When users call this version of compare, they may supply their own comparison operation but are not required to do so:

1
2
3
4
bool i = compare(0, 42); // uses less; i is -1
// result depends on the isbns in item1 and item2
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);

The first call uses the default function argument, which is a default-initialized object of type less. In the second call, we pass compareIsbn and two objects of type Sales_data. When compare is called with three arguments, the type of the third argument must be a callable object that returns a type that is convertible to bool and takes arguments of a type compatible with the types of the first two arguments.

As with function default arguments, a template parameter may have a default argument only if all of the parameters to its right also have default arguments.

Whenever we use a class template, we must always follow the template’s name with brackets. In particular, if a class template provides default arguments for all of its template parameters, and we want to use those defaults, we must put an empty bracket pair following the template’s name:

1
2
3
4
5
6
7
8
9
template <class T = int> class Numbers { // by default T is int
public:
    Numbers(T v = 0): val(v) { }
    // various operations on numbers
private:
    T val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; // empty <> says we want the default type

Member Templates

A class—either an ordinary class or a class template—may have a member function that is itself a template. Such members are referred to as member templates. Member templates may not be virtual. As an example of an ordinary class that has a member template, we’ll define a class that is similar to the default deleter type used by unique_ptr. Like the default deleter, our class will have an overloaded function-call operator that will take a pointer and execute delete on the given pointer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// function-object class that calls delete on a given pointer
class DebugDelete {
public:
    DebugDelete(std::ostream &s = std::cerr): os(s) { }
    // as with any function template, the type of T is deduced by the compiler
    template <typename T> void operator()(T *p) const
    { 
        os << "deleting unique_ptr" << std::endl; delete p;
    }
private:
    std::ostream &os;
};

We can use this class as a replacement for delete:

1
2
3
4
5
6
double* p = new double;
DebugDelete d; // an object that can act like a delete expression
d(p); // calls DebugDelete::operator()(double*), which deletes p
int* ip = new int;
// calls operator()(int*) on a temporary DebugDelete object
DebugDelete()(ip);

Because calling a DebugDelete object deletes its given pointer, we can also use DebugDelete as the deleter of a unique_ptr.

1
2
3
4
5
6
// destroying the the object to which p points
// instantiates DebugDelete::operator()<int>(int *)
unique_ptr<int, DebugDelete> p(new int, DebugDelete());
// destroying the the object to which sp points
// instantiates DebugDelete::operator()<string>(string*)
unique_ptr<string,  DebugDelete>  sp(new  string, DebugDelete());

The unique_ptr destructor calls the DebugDelete’s call operator. Thus, whenever unique_ptr’s destructor is instantiated, DebugDelete’s call operator will also be instantiated: Thus, the definitions above will instantiate:

1
2
3
// sample instantiations for member templates of DebugDelete
void DebugDelete::operator()(int *p) const { delete p; }
void DebugDelete::operator()(string *p) const { delete p; }

We can also define a member template of a class template. In this case, both the class and the member have their own, independent, template parameters. As an example, we’ll give our Blob class a constructor that will take two iterators denoting a range of elements to copy.

1
2
3
4
template <typename T> class Blob {
    template <typename It> Blob(It b, It e);
// ...
};

Unlike ordinary function members of class templates, member templates are function templates. When we define a member template outside the body of a class template, we must provide the template parameter list for the class template and for the function template. The parameter list for the class template comes first, followed by the member’s own template parameter list:

1
2
3
4
5
template <typename T> // type parameter for the class
template <typename It> // type parameter for the constructor
    Blob<T>::Blob(It b, It e):
        data(std::make_shared<std::vector<T>>(b, e)) {
}

To instantiate a member template of a class template, we must supply arguments for the template parameters for both the class and the function templates.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int ia[] = {0,1,2,3,4,5,6,7,8,9};
vector<long> vi = {0,1,2,3,4,5,6,7,8,9};
list<const char*> w = {"now", "is", "the", "time"};
// instantiates the Blob<int> class
// and the Blob<int> constructor that has two int* parameters
Blob<int> a1(begin(ia), end(ia));
// instantiates the Blob<int> constructor that has
// two vector<long>::iterator parameters
Blob<int> a2(vi.begin(), vi.end());
// instantiates the Blob<string> class and the Blob<string>
// constructor that has two (list<const char*>::iterator parameters
Blob<string> a3(w.begin(), w.end());

Controlling Instantiations

The fact that instantiations are generated when a template is used means that the same instantiation may appear in multiple object files. In large systems, the overhead of instantiating the same template in multiple files can become significant. Under the new standard, we can avoid this overhead through an explicit instantiation. An explicit instantiation has the form

1
2
extern template declaration; // instantiation declaration
template declaration; // instantiation definition

where declaration is a class or function declaration in which all the template parameters are replaced by the template arguments. For example,

1
2
3
// instantion declaration and definition
extern template class Blob<string>; // declaration
template int compare(const int&, const int&); // definition

When the compiler sees an extern template declaration, it will not generate code for that instantiation in that file. Declaring an instantiation as extern is a promise that there will be a nonextern use of that instantiation elsewhere in the program. There may be several extern declarations for a given instantiation but there must be exactly one definition for that instantiation.

Because the compiler automatically instantiates a template when we use it, the extern declaration must appear before any code that uses that instantiation:

1
2
3
4
5
6
7
8
9
// Application.cc
// these template types must be instantiated elsewhere in the program
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2; // instantiation will appear elsewhere
// Blob<int> and its initializer_list constructor instantiated in this file
Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9};
Blob<int> a2(a1); // copy constructor instantiated in this file
int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere

The file Application.o will contain instantiations for Blob, along with the initializer_list and copy constructors for that class. The compare function and Blob class will not be instantiated in that file. There must be definitions of these templates in some other file in the program:

1
2
3
4
5
// templateBuild.cc
// instantiation file must provide a (nonextern) definition for every
// type and function that other files declare as extern
template int compare(const int&, const int&);
template class Blob<string>; // instantiates all members of the class template

When the compiler sees an instantiation definition (as opposed to a declaration), it generates code. Thus, the file templateBuild.o will contain the definitions for compare instantiated with int and for the Blob class. When we build the application, we must link templateBuild.o with the Application.o files.

There must be an explicit instantiation definition somewhere in the program for every instantiation declaration.

An instantiation definition for a class template instantiates all the members of that template including inline member functions. When the compiler sees an instantiation definition it cannot know which member functions the program uses. Hence, unlike the way it handles ordinary class template instantiations, the compiler instantiates all the members of that class. Even if we do not use a member, that member will be instantiated. Consequently, we can use explicit instantiation only for types that can be used with all the members of that template.

Efficiency and Flexibility

The obvious difference between shared_ptr and unique_ptr is the strategy they use in managing the pointer they hold—one class gives us shared ownership; the other owns the pointer that it holds. This difference is essential to what these classes do. These classes also differ in how they let users override their default deleter.

We can easily override the deleter of a shared_ptr by passing a callable object when we create or reset the pointer. In contrast, the type of the deleter is part of the type of a unique_ptr object. Users must supply that type as an explicit template argument when they define a unique_ptr.

Although we don’t know how the library types are implemented, we can infer that shared_ptr must access its deleter indirectly. That is the deleter must be stored as a pointer or as a class that encapsulates a pointer. We can be certain that shared_ptr does not hold the deleter as a direct member, because the type of the deleter isn’t known until run time. Indeed, we can change the type of the deleter in a given shared_ptr during that shared_ptr’s lifetime. We can construct a shared_ptr using a deleter of one type, and subsequently use reset to give that same shared_ptr a different type of deleter.

Now, let’s think about how unique_ptr might work. In this class, the type of the deleter is part of the type of the unique_ptr. That is, unique_ptr has two template parameters, one that represents the pointer that the unique_ptr manages and the other that represents the type of the deleter. Because the type of the deleter is part of the type of a unique_ptr, the type of the deleter member is known at compile time. The deleter can be stored directly in each unique_ptr object.

By binding the deleter at compile time, unique_ptr avoids the run-time cost of an indirect call to its deleter. By binding the deleter at run time, shared_ptr makes it easier for users to override the deleter.

Conversions and Template Type Parameters

The process of determining the template arguments from the function arguments is known as template argument deduction. During template argument deduction, the compiler uses types of the arguments in the call to find the template arguments that generate a version of the function that best matches the given call.

As with a nontemplate function, the arguments we pass in a call to a function template are used to initialize that function’s parameters. Only a very limited number of conversions are automatically applied to such arguments. Rather than converting the arguments, the compiler generates a new instantiation.

As usual, top-level consts in either the parameter or the argument are ignored. The only other conversions performed in a call to a function template are

  • const conversions: A function parameter that is a reference (or pointer) to a const can be passed a reference (or pointer) to a nonconst object.
  • Array- or function-to-pointer conversions: If the function parameter is not a reference type, then the normal pointer conversion will be applied to arguments of array or function type. An array argument will be converted to a pointer to its first element. Similarly, a function argument will be converted to a pointer to the function’s type.

Other conversions, such as the arithmetic conversions, derived-to-base, and user-defined conversions, are not performed. As examples, consider calls to the functions fobj and fref. The fobj function copies its parameters, whereas fref’s parameters are references:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <typename T> T fobj(T, T); // arguments are copied
template <typename T> T fref(const T&, const T&); // references
string s1("a value");
const string s2("another value");
fobj(s1, s2); // calls fobj(string, string); const is ignored
fref(s1, s2); // calls fref(const string&, const string&)

// uses premissible conversion to const on s1
int a[10], b[42];
fobj(a, b); // calls f(int*, int*)
fref(a, b); // error: array types don't match

In the first pair of calls, we pass a string and a const string. Even though these types do not match exactly, both calls are legal. In the call to fobj, the arguments are copied, so whether the original object is const doesn’t matter. In the call to fref, the parameter type is a reference to const. Conversion to const for a reference parameter is a permitted conversion, so this call is legal.

In the next pair of calls, we pass array arguments in which the arrays are different sizes and hence have different types. In the call to fobj, the fact that the array types differ doesn’t matter. Both arrays are converted to pointers. The template parameter type in fobj is int*. The call to fref, however, is illegal. When the parameter is a reference, the arrays are not converted to pointers. The types of a and b don’t match, so the call is in error.

const conversions and array or function to pointer are the only automatic conversions for arguments to parameters with template types.

Function Parameters That Use the Same Template Parameter Type

A template type parameter can be used as the type of more than one function parameter. Because there are limited conversions, the arguments to such parameters must have essentially the same type. If the deduced types do not match, then the call is an error. For example, our compare function takes two const T& parameters.

1
2
long lng;
compare(lng, 1024); // error: cannot instantiate compare(long, int)

These types don’t match, so template argument deduction fails. If we want to allow normal conversions on the arguments, we can define the function with two type parameters:

1
2
3
4
5
6
7
8
// argument types can differ but must be compatible
template <typename A, typename B>
int flexibleCompare(const A& v1, const B& v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

Normal Conversions Apply for Ordinary Arguments

A function template can have parameters that are defined using ordinary types—that is, types that do not involve a template type parameter. Such arguments have no special processing; they are converted as usual to the corresponding type of the parameter.

1
2
3
4
template <typename T> ostream &print(ostream &os, const T &obj)
{
    return os << obj;
}

Because the type of os is fixed, normal conversions are applied to arguments passed to os when print is called:

1
2
3
print(cout, 42); // instantiates print(ostream&, int)
ofstream f("output");
print(f, 10); // uses print(ostream&, int); converts f to ostream&

Normal conversions are applied to arguments whose type is not a template parameter.

Function-Template Explicit Arguments

We can let the user control the type of the return by defining a third template parameter to represent the return type:

1
2
3
// T1 cannot be deduced: it doesn't appear in the function parameter list
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);

In this case, there is no argument whose type can be used to deduce the type of T1. The caller must provide an explicit template argument for this parameter on each call to sum. Explicit template arguments are specified inside angle brackets after the function name and before the argument list:

1
2
// T1 is explicitly specified; T2 and T3 are inferred from the argument types
auto val3 = sum<long long>(i, lng); // long long sum(int, long)

Explicit template argument(s) are matched to corresponding template parameter(s) from left to right; the first template argument is matched to the first template parameter, the second argument to the second parameter, and so on. An explicit template argument may be omitted only for the trailing (right-most) parameters, and then only if these can be deduced from the function parameters. If our sum function had been written as

1
2
3
// poor design: users must explicitly specify all three template parameters
template <typename T1, typename T2, typename T3>
T3 alternative_sum(T2, T1);

then we would always have to specify arguments for all three parameters:

1
2
3
4
// error: can't infer initial template parameters
auto val3 = alternative_sum<long long>(i, lng);
// ok: all three parameters are explicitly specified
auto val2 = alternative_sum<long long, int, long>(i, lng);

Normal conversions also apply for arguments whose template type parameter is explicitly specified:

1
2
3
4
long lng;
compare(lng, 1024); // error: template parameters don't match
compare<long>(lng, 1024); // ok: instantiates compare(long, long)
compare<int>(lng, 1024); // ok: instantiates compare(int, int)

As we’ve seen, the first call is in error because the arguments to compare must have the same type. If we explicitly specify the template parameter type, normal conversions apply.

Trailing Return Type

Using an explicit template argument to represent a template function’s return type works well when we want to let the user determine the return type. In other cases, requiring an explicit template argument imposes a burden on the user with no compensating advantage.

1
2
3
4
5
6
template <typename It>
??? &fcn(It beg, It end)
{
    // process the range
    return *beg; // return a reference to an element from the range
}

We don’t know the exact type we want to return, but we do know that we want that type to be a reference to the element type of the sequence we’re processing:

1
2
3
4
vector<int> vi = {1,2,3,4,5};
Blob<string> ca = { "hi", "bye" };
auto &i = fcn(vi.begin(), vi.end()); // fcn should return int&
auto &s = fcn(ca.begin(), ca.end()); // fcn should return string&

Here, we know that our function will return *beg, and we know that we can use decltype(*beg) to obtain the type of that expression. However, beg doesn’t exist until the parameter list has been seen. To define this function, we must use a trailing return type. Because a trailing return appears after the parameter list, it can use the function’s parameters:

1
2
3
4
5
6
7
// a trailing return lets us declare the return type after the parameter list is seen
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
    // process the range
    return *beg; // return a reference to an element from the range
}

Type Transformation

Sometimes we do not have direct access to the type that we need. For example, we might want to write a function similar to fcn that returns an element by value, rather than a reference to an element. The problem we face in writing this function is that we know almost nothing about the types we’re passed. In this function, the only operations we know we can use are iterator operations, and there are no iterator operations that yield elements.

To obtain the element type, we can use a library type transformation template. These templates are defined in the type_traits header. In general the classes in type_traits are used for so-called template metaprogramming, a topic that is beyond the scope of this Primer. However, the type transformation templates are useful in ordinary programming as well.

Standard Type Transformation Templates:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
For Mod<T>,where Mod is     If T is                     Then Mod<T>::type is

remove_reference            X& or X&&                   X
                            otherwise                   T

add_const                   X&, const X, or function    T
                            otherwise                   const T

add_lvalue_reference        X&                          T
                            X&&                         X&
                            otherwise                   T&

add_rvalue_reference        X& or X&&                   T
                            otherwise                   T&&

remove_pointer              X*                          X
                            otherwise                   T

add_pointer                 X& or X&&                   X*
                            otherwise                   T*

make_signed                 unsigned X                  X
                            otherwise                   T

make_unsigned               signed type                 unsigned T
                            otherwise                   T

remove_extent               X[n]                        X
                            otherwise                   T

remove_all_extents          X[n1][n2]...                X
                            otherwise                   T

The remove_reference template has one template type parameter and a (public) type member named type. If we instantiate remove_reference with a reference type, then type will be the referred-to type. For example, if we instantiate remove_reference<int&>, the type member will be int.

1
remove_reference<decltype(*beg)>::type

will be the type of the element to which beg refers: decltype(*beg) returns the reference type of the element type. remove_reference::type strips off the reference, leaving the element type itself. Using remove_reference and a trailing return with decltype, we can write our function to return a copy of an element’s value:

1
2
3
4
5
6
7
8
// must use typename to use a type member of a template parameter;
template <typename It>
auto fcn2(It beg, It end) ->
    typename remove_reference<decltype(*beg)>::type
{
    // process the range
    return *beg; // return a copy of an element from the range
}

Note that type is member of a class that depends on a template parameter. As a result, we must use typename in the declaration of the return type to tell the compiler that type represents a type.

If it is not possible (or not necessary) to transform the template’s parameter, the type member is the template parameter type itself. For example, if T is a pointer type, then remove_pointer::type is the type to which T points. If T isn’t a pointer, then no transformation is needed. In this case, type is the same type as T.

Function Pointers and Argument Deduction

When we initialize or assign a function pointer from a function template, the compiler uses the type of the pointer to deduce the template argument(s).

1
2
3
template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare(const int&, const int&)
int (*pf1)(const int&, const int&) = compare;

The type of the parameters in pf1 determines the type of the template argument for T. The template argument for T is int. It is an error if the template arguments cannot be determined from the function pointer type:

1
2
3
4
// overloaded versions of func; each takes a different function pointer type
void func(int(*)(const string&, const string&));
void func(int(*)(const int&, const int&));
func(compare); // error: which instantiation of compare?

We can disambiguate the call to func by using explicit template arguments:

1
2
// ok: explicitly specify which version of compare to instantiate
func(compare<int>); // passing compare(const int&, const int&)

Template Argument Deduction and References

When a function parameter is an ordinary (lvalue) reference to a template type parameter (i.e., that has the form T&), the binding rules say that we can pass only an lvalue (e.g., a variable or an expression that returns a reference type). That argument might or might not have a const type. If the argument is const, then T will be deduced as a const type:

1
2
3
4
5
template <typename T> void f1(T&); // argument must be an lvalue
// calls to f1 use the referred-to type of the argument as the template parameter type
f1(i); // i is an int; template parameter T is int
f1(ci); // ci is a const int; template parameter T is const int
f1(5); // error: argument to a & parameter must be an lvalue

If a function parameter has type const T&, normal binding rules say that we can pass any kind of argument—an object (const or otherwise), a temporary, or a literal value. When the function parameter is itself const, the type deduced for T will not be a const type. The const is already part of the function parameter type; therefore, it does not also become part of the template parameter type:

1
2
3
4
5
6
template <typename T> void f2(const T&); // can take an rvalue
// parameter in f2 is const &; const in the argument is irrelevant
// in each of these three calls, f2's function parameter is inferred as const int&
f2(i); // i is an int; template parameter T is int
f2(ci); // ci is a const int, but template parameter T is int
f2(5); // a const & parameter can be bound to an rvalue; T is int

When a function parameter is an rvalue reference (i.e., has the form T&&), normal binding rules say that we can pass an rvalue to this parameter. When we do so, type deduction behaves similarly to deduction for an ordinary lvalue reference function parameter. The deduced type for T is the type of the rvalue:

1
2
template <typename T> void f3(T&&);
f3(42); // argument is an rvalue of type int; template parameter T is int

Reference Collapsing and Rvalue Reference Parameters

Assuming i is an int object, we might think that a call such as f3(i) would be illegal. After all, i is an lvalue, and normally we cannot bind an rvalue reference to an lvalue. However, the language defines two exceptions to normal binding rules that allow this kind of usage. These exceptions are the foundation for how library facilities such as move operate.

The first exception affects how type deduction is done for rvalue reference parameters. When we pass an lvalue (e.g., i) to a function parameter that is an rvalue reference to a template type parameter (e.g, T&&), the compiler deduces the template type parameter as the argument’s lvalue reference type. So, when we call f3(i), the compiler deduces the type of T as int&, not int. Deducing T as int& would seem to mean that f3’s function parameter would be an rvalue reference to the type int&. Ordinarily, we cannot (directly) define a reference to a reference. However, it is possible to do so indirectly through a type alias or through a template type parameter.

In such contexts, we see the second exception to the normal binding rules: If we indirectly create a reference to a reference, then those references “collapse.” In all but one case, the references collapse to form an ordinary lvalue reference type. The new standard, expanded the collapsing rules to include rvalue references. References collapse to form an rvalue reference only in the specific case of an rvalue reference to an rvalue reference. That is, for a given type X:

  • X& &, X& &&, and X&& & all collapse to type X&
  • The type X&& && collapses to X&&

Reference collapsing applies only when a reference to a reference is created indirectly, such as in a type alias or a template parameter.

The combination of the reference collapsing rule and the special rule for type deduction for rvalue reference parameters means that we can call f3 on an lvalue. When we pass an lvalue to f3’s (rvalue reference) function parameter, the compiler will deduce T as an lvalue reference type:

1
2
f3(i); // argument is an lvalue; template parameter T is int&
f3(ci); // argument is an lvalue; template parameter T is const int&

When a template parameter T is deduced as a reference type, the collapsing rule says that the function parameter T&& collapses to an lvalue reference type. For example, the resulting instantiation for f3(i) would be something like

1
2
// invalid code, for illustration purposes only
void f3<int&>(int& &&); // when T is int&, function parameter is int& &&

The function parameter in f3 is T&& and T is int&, so T&& is int& &&, which collapses to int&. Thus, even though the form of the function parameter in f3 is an rvalue reference (i.e., T&&), this call instantiates f3 with an lvalue reference type (i.e., int&):

1
void f3<int&>(int&); // when T is int&, function parameter collapses to int&

There are two important consequences from these rules:

  • A function parameter that is an rvalue reference to a template type parameter (e.g., T&&) can be bound to an lvalue; and
  • If the argument is an lvalue, then the deduced template argument type will be an lvalue reference type and the function parameter will be instantiated as an (ordinary) lvalue reference parameter (T&)

It is also worth noting that by implication, we can pass any type of argument to a T&& function parameter. A parameter of such a type can (obviously) be used with rvalues, and as we’ve just seen, can be used by lvalues as well. An argument of any type can be passed to a function parameter that is an rvalue reference to a template parameter type (i.e., T&&). When an lvalue is passed to such a parameter, the function parameter is instantiated as an ordinary, lvalue reference (T&).

The fact that the template parameter can be deduced to a reference type can have surprising impacts on the code inside the template:

1
2
3
4
5
6
template <typename T> void f3(T&& val)
{
    T t = val; // copy or binding a reference?
    t = fcn(t); // does the assignment change only t or val and t?
    if (val == t) { /* ... */ } // always true if T is a reference type
}

In practice, rvalue reference parameters are used in one of two contexts: Either the template is forwarding its arguments, or the template is overloaded. For now, it’s worth noting that function templates that use rvalue references often use overloading in the same way as we saw in:

1
2
template <typename T> void f(T&&); // binds to nonconst rvalues
template <typename T> void f(const T&); // lvalues and const rvalues

Understanding std::move

Although we cannot directly bind an rvalue reference to an lvalue, we can use move to obtain an rvalue reference bound to an lvalue. Because move can take arguments of essentially any type, it should not be surprising that move is a function template. The standard defines move as follows:

1
2
3
4
5
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return  static_cast<typename remove_reference<T>::type&&>(t);
}

This code is short but subtle. First, move’s function parameter, T&&, is an rvalue reference to a template parameter type. Through reference collapsing, this parameter can match arguments of any type. In particular, we can pass either an lvalue or an rvalue to move:

1
2
3
string s1("hi!"), s2;
s2 = std::move(string("bye!")); // ok: moving from an rvalue
s2 = std::move(s1); // ok: but after the assigment s1 has indeterminate value

In the first assignment, the argument to move is the rvalue result of the string constructor, string(“bye”). As we’ve seen, when we pass an rvalue to an rvalue reference function parameter, the type deduced from that argument is the referred-to type. Thus, in std::move(string(“bye!”)):

  • The deduced type of T is string.
  • Therefore, remove_reference is instantiated with string.
  • The type member of remove_reference is string.
  • The return type of move is string&&.
  • move’s function parameter, t, has type string&&.

Accordingly, this call instantiates move, which is the function

1
string&& move(string &&t)

The body of this function returns static_cast<string&&>(t). The type of t is already string&&, so the cast does nothing. Therefore, the result of this call is the rvalue reference it was given.

Now consider the second assignment, which calls std::move(s1). In this call, the argument to move is an lvalue. This time:

  • The deduced type of T is string& (reference to string, not plain string).
  • Therefore, remove_reference is instantiated with string&.
  • The type member of remove_reference<string&> is string.
  • The return type of move is still string&&.
  • move’s function parameter, t, instantiates as string& &&, which collapses to string&.

Thus, this call instantiates move<string&>, which is

1
string&& move(string &t)

The body of this instantiation returns static_cast<string&&>(t). In this case, the type of t is string&, which the cast converts to string&&.

Ordinarily, a static_cast can perform only otherwise legitimate conversions. However, there is again a special dispensation for rvalue references: Even though we cannot implicitly convert an lvalue to an rvalue reference, we can explicitly cast an lvalue to an rvalue reference using static_cast.

clobber: (Programming) To make a change or a call in a program which unintentionally overwrites the current value of a variable. RAII: Resource Acquisition Is Initialization, use destructor to free memory when there is an exception, so C++ do not need GC.

Binding an rvalue reference to an lvalue gives code that operates on the rvalue reference permission to clobber the lvalue. There are times, such as in our StrVec reallocate function, when we know it is safe to clobber an lvalue. By letting us do the cast, the language allows this usage. By forcing us to use a cast, the language tries to prevent us from doing so accidentally. Finally, although we can write such casts directly, it is much easier to use the library move function. Moreover, using std::move consistently makes it easy to find the places in our code that might potentially clobber lvalues.

Forwarding

Some functions need to forward one or more of their arguments with their types unchanged to another, forwarded-to, function. In such cases, we need to preserve everything about the forwarded arguments, including whether or not the argument type is const, and whether the argument is an lvalue or an rvalue. As an example, we’ll write a function that takes a callable expression and two additional arguments.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// template that takes a callable and two parameters
// and calls the given callable with the parameters ''flipped''
// flip1 is an incomplete implementation: top-level const and references are lost
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2)
{
    f(t2, t1);
}

void f(int v1, int &v2) // note v2 is a reference
{
    cout << v1 << " " << ++v2 << endl;
}

f(42, i); // f changes its argument i
flip1(f, j, 42); // f called through flip1 leaves j unchanged

The problem is that j is passed to the t1 parameter in flip1. That parameter has is a plain, nonreference type, int, not an int&.

To pass a reference through our flip function, we need to rewrite our function so that its parameters preserve the “lvalueness” of its given arguments. Thinking ahead a bit, we can imagine that we’d also like to preserve the constness of the arguments as well. We can preserve all the type information in an argument by defining its corresponding function parameter as an rvalue reference to a template type parameter. Using a reference parameter (either lvalue or rvalue) lets us preserve constness, because the const in a reference type is low-level. Through reference collapsing, if we define the function parameters as T1&& and T2&&, we can preserve the lvalue/rvalue property of flip’s arguments:

1
2
3
4
5
template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2)
{
    f(t2, t1);
}

This version of flip2 solves one half of our problem. Our flip2 function works fine for functions that take lvalue references but cannot be used to call a function that has an rvalue reference parameter. For example:

1
2
3
4
5
6
void g(int &&i, int& j)
{
    cout << i << " " << j << endl;
}

flip2(g, i, 42); // error: can't initialize int&& from an lvalue

what is passed to g will be the parameter named t2 inside flip2. A function parameter, like any other variable, is an lvalue expression. As a result, the call to g in flip2 passes an lvalue to g’s rvalue reference parameter.

We can use a new library facility named forward to pass flip2’s parameters in a way that preserves the types of the original arguments. Like move, forward is defined in the utility header. Unlike move, forward must be called with an explicit template argument. forward returns an rvalue reference to that explicit argument type. That is, the return type of forward is T&&.

Ordinarily, we use forward to pass a function parameter that is defined as an rvalue reference to a template type parameter. Through reference collapsing on its return type, forward preserves the lvalue/rvalue nature of its given argument:

1
2
3
4
5
template <typename Type> intermediary(Type &&arg)
{
    finalFcn(std::forward<Type>(arg));
    // ...
}

If that argument was an rvalue, then Type is an ordinary (nonreference) type and forward will return Type&&. If the argument was an lvalue, then—through reference collapsing—Type itself is an lvalue reference type. In this case, the return type is an rvalue reference to an lvalue reference type. Again through reference collapsing—this time on the return type—forward will return an lvalue reference type. Using forward, we’ll rewrite our flip function once more:

1
2
3
4
5
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
    f(std::forward<T2>(t2), std::forward<T1>(t1));
}

If we call flip(g, i, 42), i will be passed to g as an int& and 42 will be passed as an int&&.

Overloading and Templates

Function templates can be overloaded by other templates or by ordinary, nontemplate functions. As usual, functions with the same name must differ either as to the number or the type(s) of their parameters.

  • The candidate functions for a call include any function-template instantiation for which template argument deduction succeeds.

  • The candidate function templates are always viable, because template argument deduction will have eliminated any templates that are not viable.

  • As usual, the viable functions (template and nontemplate) are ranked by the conversions, if any, needed to make the call. Of course, the conversions used to call a function template are quite limited.

  • Also as usual, if exactly one function provides a better match than any of the others, that function is selected. However, if there are several functions that provide an equally good match, then:

  • If there is only one nontemplate function in the set of equally good matches, the nontemplate function is called.

  • If there are no nontemplate functions in the set, but there are multiple function templates, and one of these templates is more specialized than any of the others, the more specialized function template is called.

  • Otherwise, the call is ambiguous.

Writing Overloaded Templates

As an example, we’ll build a set of functions that might be useful during debugging.

1
2
3
4
5
6
7
// print any type we don't otherwise handle
template <typename T> string debug_rep(const T &t)
{
    ostringstream ret; // see § 8.3 (p. 321)
    ret << t; // uses T's output operator to print a representation of t
    return ret.str(); // return a copy of the string to which ret is bound
}

This function can be used to generate a string corresponding to an object of any type that has an output operator.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// print pointers as their pointer value, followed by the object to which the pointer points
// NB: this function will not work properly with char*;
template <typename T> string debug_rep(T *p)
{
    ostringstream ret;
    ret << "pointer: " << p; // print the pointer's own value
    if (p)
        ret << " " << debug_rep(*p); // print the value to which p points
    else
        ret << " null pointer"; // or indicate that the p is null
    return ret.str(); // return a copy of the string to which ret is bound
}

This version generates a string that contains the pointer’s own value and calls debug_rep to print the object to which that pointer points. Note that this function can’t be used to print character pointers, because the IO library defines a version of the « for char* values. That version of « assumes the pointer denotes a null-terminated character array, and prints the contents of the array, not its address.

1
2
string s("hi");
cout << debug_rep(s) << endl;

For this call, only the first version of debug_rep is viable. The second version of debug_rep requires a pointer parameter, and in this call we passed a nonpointer object. If we call debug_rep with a pointer:

1
cout << debug_rep(&s) << endl;

both functions generate viable instantiations:

  • debug_rep(const string* &), which is the instantiation of the first version of debug_rep with T bound to string*
  • debug_rep(string*), which is the instantiation of the second version of debug_rep with T bound to string

The instantiation of the second version of debug_rep is an exact match for this call. The instantiation of the first version requires a conversion of the plain pointer to a pointer to const. Normal function matching says we should prefer the second template, and indeed that is the one that is run.

Multiple Viable Templates

As another example, consider the following call:

1
2
const string *sp = &s;
cout << debug_rep(sp) << endl;

Here both templates are viable and both provide an exact match:

  • debug_rep(const string* &), the instantiation of the first version of the template with T bound to const string*
  • debug_rep(const string*), the instantiation of the second version of the template with T bound to const string

In this case, normal function matching can’t distinguish between these two calls. We might expect this call to be ambiguous. However, due to the special rule for overloaded function templates, this call resolves to debug_rep(T*), which is the more specialized template.

The reason for this rule is that without it, there would be no way to call the pointer version of debug_rep on a pointer to const. The problem is that the template debug_rep(const T&) can be called on essentially any type, including pointer types. That template is more general than debug_rep(T*), which can be called only on pointer types. Without this rule, calls that passed pointers to const would always be ambiguous. When there are several overloaded templates that provide an equally good match for a call, the most specialized version is preferred.

Nontemplate and Template Overloads

For our next example, we’ll define an ordinary nontemplate version of debug_rep to print strings inside double quotes:

1
2
3
4
5
// print strings inside double quotes
string debug_rep(const string &s)
{
    return '"' + s + '"';
}

Now, when we call debug_rep on a string,

1
2
string s("hi");
cout << debug_rep(s) << endl;

there are two equally good viable functions:

  • debug_rep(const string&), the first template with T bound to string
  • debug_rep(const string&), the ordinary, nontemplate function

In this case, both functions have the same parameter list, so obviously, each function provides an equally good match for this call. However, the nontemplate version is selected. For the same reasons that the most specialized of equally good function templates is preferred, a nontemplate function is preferred over equally good match(es) to a function template.

Overloaded Templates and Conversions

There’s one case we haven’t covered so far: pointers to C-style character strings and string literals. Now that we have a version of debug_rep that takes a string, we might expect that a call that passes character strings would match that version. However, consider this call:

1
cout << debug_rep("hi world!") << endl; // calls debug_rep(T*)

Here all three of the debug_rep functions are viable:

  • debug_rep(const T&), with T bound to char[10]
  • debug_rep(T*), with T bound to const char
  • debug_rep(const string&), which requires a conversion from const char* to string

Both templates provide an exact match to the argument—the second template requires a (permissible) conversion from array to pointer, and that conversion is considered as an exact match for function-matching purposes. As before, the T* version is more specialized and is the one that will be selected.

If we want to handle character pointers as strings, we can define two more nontemplate overloads:

1
2
3
4
5
6
7
8
9
// convert the character pointers to string and call the string version of debug_rep
string debug_rep(char *p)
{
    return debug_rep(string(p));
}
string debug_rep(const char *p)
{
    return debug_rep(string(p));
}

Missing Declarations Can Cause the Program to Misbehave

It is worth noting that for the char* versions of debug_rep to work correctly, a declaration for debug_rep(const string&) must be in scope when these functions are defined. If not, the wrong version of debug_rep will be called:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
template <typename T> string debug_rep(const T &t);
template <typename T> string debug_rep(T *p);
// the following declaration must be in scope

// for the definition of debug_rep(char*) to do the right thing
string debug_rep(const string &);
string debug_rep(char *p)
{
    // if the declaration for the version that takes a const string& is not in scope
    // the return will call debug_rep(const T&) with T instantiated to string
    return debug_rep(string(p));
}

Ordinarily, if we use a function that we forgot to declare, our code won’t compile. Not so with functions that overload a template function. If the compiler can instantiate the call from the template, then the missing declaration won’t matter. In this example, if we forget to declare the version of debug_rep that takes a string, the compiler will silently instantiate the template version that takes a const T&.

Declare every function in an overload set before you define any of the functions. That way you don’t have to worry whether the compiler will instantiate a call before it sees the function you intended to call.

Variadic Templates

A variadic template is a template function or class that can take a varying number of parameters. The varying parameters are known as a parameter pack. There are two kinds of parameter packs: A template parameter pack represents zero or more template parameters, and a function parameter pack represents zero or more function parameters.

We use an ellipsis to indicate that a template or function parameter represents a pack. In a template parameter list, class… or typename… indicates that the following parameter represents a list of zero or more types; the name of a type followed by an ellipsis represents a list of zero or more nontype parameters of the given type. In the function parameter list, a parameter whose type is a template parameter pack is a function parameter pack. For example:

1
2
3
4
5
// Args is a template parameter pack; rest is a function parameter pack
// Args represents zero or more template type parameters
// rest represents zero or more function parameters
template <typename T, typename... Args>
void foo(const T &t, const Args& ... rest);

declares that foo is a variadic function that has one type parameter named T and a template parameter pack named Args. As usual, the compiler deduces the template parameter types from the function’s arguments.

1
2
3
4
5
int i = 0; double d = 3.14; string s = "how now brown cow";
foo(i, s, 42, d); // three parameters in the pack
foo(s, 42, "hi"); // two parameters in the pack
foo(d, s); // one parameter in the pack
foo("hi"); // empty pack

the compiler will instantiate four different instances of foo:

1
2
3
4
void foo(const int&, const string&, const int&, const double&);
void foo(const string&, const int&, const char[3]&);
void foo(const double&, const string&);
void foo(const char[3]&);

When we need to know how many elements there are in a pack, we can use the sizeof… operator. Like sizeof, sizeof… returns a constant expression and does not evaluate its argument:

1
2
3
4
template<typename ... Args> void g(Args ... args) {
    cout << sizeof...(Args) << endl; // number of type parameters
    cout << sizeof...(args) << endl; // number of function parameters
}

We can use an initializer_list to define a function that can take a varying number of arguments. However, the arguments must have the same type (or types that are convertible to a common type). Variadic functions are used when we know neither the number nor the types of the arguments we want to process. As an example, we’ll define a function like our earlier error_msg function, only this time we’ll allow the argument types to vary as well.

Variadic functions are often recursive. The first call processes the first argument in the pack and calls itself on the remaining arguments. Our print function will execute this way—each call will print its second argument on the stream denoted by its first argument.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// function to end the recursion and print the last element
// this function must be declared before the variadic version of print is defined
template<typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t; // no separator after the last element in the pack
}
// this version of print will be called for all but the last element in the pack
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
    os << t << ", "; // print the first argument
    return print(os, rest...); // recursive call; print the other arguments
}

For the last call of print in the recursion, the variadic version is also viable. Unlike an ordinary argument, a parameter pack can be empty. However, a nonvariadic template is more specialized than a variadic template, so the nonvariadic version is chosen for this call.

Pack Expansion

When we expand a pack, we also provide a pattern to be used on each expanded element. Expanding a pack separates the pack into its constituent elements, applying the pattern to each element as it does so. We trigger an expansion by putting an ellipsis (. . . ) to the right of the pattern.

1
2
3
4
5
6
7
template <typename T, typename... Args>
ostream &
print(ostream &os, const T &t, const Args&... rest)// expand Args
{
    os << t << ", ";
    return print(os, rest...); // expand rest
}

The first expansion expands the template parameter pack and generates the function parameter list for print. The second expansion appears in the call to print. That pattern generates the argument list for the call to print.

The expansion of the function parameter pack in print just expanded the pack into its constituent parts. More complicated patterns are also possible when we expand a function parameter pack. For example, we might write a second variadic function that calls debug_rep on each of its arguments and then calls print to print the resulting strings:

1
2
3
4
5
6
7
// call debug_rep on each argument in the call to print
template <typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest)
{
    // print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an)
    return print(os, debug_rep(rest)...);
}

The call to print uses the pattern debug_rep(rest). That pattern says that we want to call debug_rep on each element in the function parameter pack rest. The resulting expanded pack will be a comma-separated list of calls to debug_rep. That is, a call such as

1
errorMsg(cerr,  fcnName,  code.num(),  otherData,  "other", item);

will execute as if we had written

1
2
print(cerr, debug_rep(fcnName), debug_rep(code.num()),
    debug_rep(otherData), debug_rep("otherData"), debug_rep(item));

In contrast, the following pattern would fail to compile:

1
2
// passes the pack to debug_rep; print(os, debug_rep(a1, a2, ..., an))
print(os, debug_rep(rest...)); // error: no matching function to call

Forwarding Parameter Packs

Under the new standard, we can use variadic templates together with forward to write functions that pass their arguments unchanged to some other function. To illustrate such functions, we’ll add an emplace_back member to our StrVec class. The emplace_back member of the library containers is a variadic member template that uses its arguments to construct an element directly in space managed by the container.

Our version of emplace_back for StrVec will also have to be variadic, because string has a number of constructors that differ in terms of their parameters. Because we’d like to be able to use the string move constructor, we’ll also need to preserve all the type information about the arguments passed to emplace_back.

As we’ve seen, preserving type information is a two-step process. First, to preserve type information in the arguments, we must define emplace_back’s function parameters as rvalue references to a template type parameter:

1
2
3
4
5
class StrVec {
public:
    template <class... Args> void emplace_back(Args&&...);
    // remaining members as in § 13.5 (p. 526)
};

The pattern in the expansion of the template parameter pack, &&, means that each function parameter will be an rvalue reference to its corresponding argument.

Second, we must use forward to preserve the arguments’ original types when emplace_back passes those arguments to construct:

1
2
3
4
5
6
7
template <class... Args>
inline
void StrVec::emplace_back(Args&&... args)
{
    chk_n_alloc(); // reallocates the StrVec if necessary
    alloc.construct(first_free++, std::forward<Args>(args)...);
}

By using forward in this call, we guarantee that if emplace_back is called with an rvalue, then construct will also get an rvalue. For example, in this call:

1
svec.emplace_back(s1 + s2); // uses the move constructor

the argument to emplace_back is an rvalue, which is passed to construct as

1
std::forward<string>(string("the end"))

The result type from forward is string&&, so construct will be called with an rvalue reference.

Template Specializations

In some cases, the general template definition is simply wrong for a type: The general definition might not compile or might do the wrong thing. When we can’t (or don’t want to) use the template version, we can define a specialized version of the class or function template.

Our compare function is a good example of a function template for which the general definition is not appropriate for a particular type, namely, character pointers. We’d like compare to compare character pointers by calling strcmp rather than by comparing the pointer values. Indeed, we have already overloaded the compare function to handle character string literals:

1
2
3
4
5
// first version; can compare any two types
template <typename T> int compare(const T&, const T&);
// second version to handle string literals
template<size_t N, size_t M>
int compare(const char (&)[N], const char (&)[M]);

However, the version of compare that has two nontype template parameters will be called only when we pass a string literal or an array. If we call compare with character pointers, the first version of the template will be called:

1
2
3
const char *p1 = "hi", *p2 = "mom";
compare(p1, p2); // calls the first template
compare("hi", "mom"); // calls the template with two nontype parameters

There is no way to convert a pointer to a reference to an array, so the second version of compare is not viable when we pass p1 and p2 as arguments. To handle character pointers (as opposed to arrays), we can define a template specialization of the first version of compare. A specialization is a separate definition of the template in which one or more template parameters are specified to have particular types.

When we specialize a function template, we must supply arguments for every template parameter in the original template. To indicate that we are specializing a template, we use the keyword template followed by an empty pair of angle brackets (< >). The empty brackets indicate that arguments will be supplied for all the template parameters of the original template:

1
2
3
4
5
6
// special version of compare to handle pointers to character arrays
template <>
int compare(const char* const &p1, const char* const &p2)
{
    return strcmp(p1, p2);
}

When we define a specialization, the function parameter type(s) must match the corresponding types in a previously declared template. Here we are specializing:

1
template <typename T> int compare(const T&, const T&);

in which the function parameters are references to a const type. As with type aliases, the interaction between template parameter types, pointers, and const can be surprising. We want to define a specialization of this function with T as const char*. Our function requires a reference to the const version of this type. The const version of a pointer type is a constant pointer as distinct from a pointer to const. The type we need to use in our specialization is const char* const &, which is a reference to a const pointer to const char.

Function Overloading versus Template Specializations

When we define a function template specialization, we are essentially taking over the job of the compiler. That is, we are supplying the definition to use for a specific instantiation of the original template. It is important to realize that a specialization is an instantiation; it is not an overloaded instance of the function name. Specializations instantiate a template; they do not overload it. As a result, specializations do not affect function matching. Whether we define a particular function as a specialization or as an independent, nontemplate function can impact function matching. For example, we have defined two versions of our compare function template, one that takes references to array parameters and the other that takes const T&. The fact that we also have a specialization for character pointers has no impact on function matching.

In order to specialize a template, a declaration for the original template must be in scope. Moreover, a declaration for a specialization must be in scope before any code uses that instantiation of the template. With ordinary classes and functions, missing declarations are (usually) easy to find—the compiler won’t be able to process our code. However, if a specialization declaration is missing, the compiler will usually generate code using the original template.

It is an error for a program to use a specialization and an instantiation of the original template with the same set of template arguments. However, it is an error that the compiler is unlikely to detect.

Templates and their specializations should be declared in the same header file. Declarations for all the templates with a given name should appear first, followed by any specializations of those templates.

Class Template Specializations

In addition to specializing function templates, we can also specialize class templates. As an example, we’ll define a specialization of the library hash template that we can use to store Sales_data objects in an unordered container. By default, the unordered containers use hash<key_type> to organize their elements. To use this default with our own data type, we must define a specialization of the hash template. A specialized hash class must define

  • An overloaded call operator that returns a size_t and takes an object of the container’s key type
  • Two type members, result_type and argument_type, which are the return and argument types, respectively, of the call operator
  • The default constructor and a copy-assignment operator (which can be implicitly defined)

The only complication in defining this hash specialization is that when we specialize a template, we must do so in the same namespace in which the original template is defined. For now, what we need to know is that we can add members to a namespace. To do so, we must first open the namespace:

1
2
3
// open the std namespace so we can specialize std::hash
namespace std {
} // close the std namespace; note: no semicolon after the close curly

Any definitions that appear between the open and close curlies will be part of the std namespace. The following defines a specialization of hash for Sales_data:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// open the std namespace so we can specialize std::hash
namespace std {
template <> // we're defining a specialization with
struct hash<Sales_data> // the template parameter of Sales_data
{
    // the type used to hash an unordered container must define these types
    typedef size_t result_type;
    typedef Sales_data argument_type; // by default, this type needs ==
    size_t operator()(const Sales_data& s) const;
    // our class uses synthesized copy control and default constructor
};
size_t hash<Sales_data>::operator()(const Sales_data& s) const
{
    return hash<string>()(s.bookNo) ^
    hash<unsigned>()(s.units_sold) ^
    hash<double>()(s.revenue);
}
} // close the std namespace; note: no semicolon after the close curly

Assuming our specialization is in scope, it will be used automatically when we use Sales_data as a key to one of these containers:

1
2
// uses hash<Sales_data> and Sales_data operator==
unordered_multiset<Sales_data> SDset;

Because hash<Sales_data> uses the private members of Sales_data, we must make this class a friend of Sales_data:

1
2
3
4
5
template <class T> class std::hash; // needed for the friend declaration
class Sales_data {
friend class std::hash<Sales_data>;
    // other members as before
};

To enable users of Sales_data to use the specialization of hash, we should define this specialization in the Sales_data header.

Class-Template Partial Specializations

Differently from function templates, a class template specialization does not have to supply an argument for every template parameter. We can specify some, but not all, of the template parameters or some, but not all, aspects of the parameters. A class template partial specialization is itself a template. Users must supply arguments for those template parameters that are not fixed by the specialization. We can partially specialize only a class template. We cannot partially specialize a function template.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// original, most general template
template <class T> struct remove_reference 
{
    typedef T type;
};
// partial specializations that will be used for lvalue and rvalue references
template <class T> struct remove_reference<T&> // lvalue references
{ 
    typedef T type;
};
template <class T> struct remove_reference<T&&> // rvalue references
{
    typedef T type;
};

The template parameter list of a partial specialization is a subset of, or a specialization of, the parameter list of the original template.

1
2
3
4
5
6
7
int i;
// decltype(42) is int, uses the original template
remove_reference<decltype(42)>::type a;
// decltype(i) is int&, uses first (T&) partial specialization
remove_reference<decltype(i)>::type b;
// decltype(std::move(i)) is int&&, uses second (i.e., T&&) partial specialization
remove_reference<decltype(std::move(i))>::type c;

All three variables, a, b, and c, have type int.

Specializing Members but Not the Class

Rather than specializing the whole template, we can specialize just specific member function(s). For example, if Foo is a template class with a member Bar, we can specialize just that member:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <typename T> struct Foo {
    Foo(const T &t = T()): mem(t) { }
    void Bar() { /* ... */ }
    T mem;
    // other members of Foo
};
template<> // we're specializing a template
void Foo<int>::Bar() // we're specializing the Bar member of Foo<int>
{
    // do whatever specialized processing that applies to ints
}

Here we are specializing just one member of the Foo class. The other members of Foo will be supplied by the Foo template:

1
2
3
4
Foo<string> fs; // instantiates Foo<string>::Foo()
fs.Bar(); // instantiates Foo<string>::Bar()
Foo<int> fi; // instantiates Foo<int>::Foo()
fi.Bar(); // uses our specialization of Foo<int>::Bar()