Table of Contents for
Professional C++, 4th Edition

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Professional C++, 4th Edition by Marc Gregoire Published by Wrox, 2018
  1. COVER
  2. Table of Contents
  3. TITLE PAGE
  4. INTRODUCTION
  5. PART I: Introduction to Professional C++
  6. 1 A Crash Course in C++ and the Standard Library
  7. 2 Working with Strings and String Views
  8. 3 Coding with Style
  9. PART II: Professional C++ Software Design
  10. 4 Designing Professional C++ Programs
  11. 5 Designing with Objects
  12. 6 Designing for Reuse
  13. PART III: C++ Coding the Professional Way
  14. 7 Memory Management
  15. 8 Gaining Proficiency with Classes and Objects
  16. 9 Mastering Classes and Objects
  17. 10 Discovering Inheritance Techniques
  18. 11 C++ Quirks, Oddities, and Incidentals
  19. 12 Writing Generic Code with Templates
  20. 13 Demystifying C++ I/O
  21. 14 Handling Errors
  22. 15 Overloading C++ Operators
  23. 16 Overview of the C++ Standard Library
  24. 17 Understanding Containers and Iterators
  25. 18 Mastering Standard Library Algorithms
  26. 19 String Localization and Regular Expressions
  27. 20 Additional Library Utilities
  28. PART IV: Mastering Advanced Features of C++
  29. 21 Customizing and Extending the Standard Library
  30. 22 Advanced Templates
  31. 23 Multithreaded Programming with C++
  32. PART V: C++ Software Engineering
  33. 24 Maximizing Software Engineering Methods
  34. 25 Writing Efficient C++
  35. 26 Becoming Adept at Testing
  36. 27 Conquering Debugging
  37. 28 Incorporating Design Techniques and Frameworks
  38. 29 Applying Design Patterns
  39. 30 Developing Cross-Platform and Cross-Language Applications
  40. A: C++ Interviews
  41. B: Annotated Bibliography
  42. C: Standard Library Header Files
  43. D: Introduction to UML
  44. PROFESSIONAL C++
  45. Dedication
  46. ABOUT THE AUTHOR
  47. ABOUT THE TECHNICAL EDITOR
  48. CREDITS
  49. ACKNOWLEDGMENTS
  50. END USER LICENSE AGREEMENT

12
Writing Generic Code with Templates

OVERVIEW OF TEMPLATES

The main programming unit in the procedural paradigm is the procedure or function. Functions are useful primarily because they allow you to write algorithms that are independent of specific values and can thus be reused for many different values. For example, the sqrt() function in C++ calculates the square root of a value supplied by the caller. A square root function that calculates only the square root of one number, such as four, would not be particularly useful! The sqrt() function is written in terms of a parameter, which is a stand-in for whatever value the caller passes. Computer scientists say that functions parameterize values.

The object-oriented programming paradigm adds the concept of objects, which group related data and behaviors, but it does not change the way functions and methods parameterize values.

Templates take the concept of parameterization a step further to allow you to parameterize on types as well as values. Types in C++ include primitives such as int and double, as well as user-defined classes such as SpreadsheetCell and CherryTree. With templates, you can write code that is independent not only of the values it will be given, but also of the types of those values. For example, instead of writing separate stack classes to store ints, Cars, and SpreadsheetCells, you can write one stack class definition that can be used for any of those types.

Although templates are an amazing language feature, templates in C++ are syntactically confusing, and many programmers avoid writing templates themselves. However, every programmer needs to know at least how to use templates, because they are widely used by libraries. For example, the C++ Standard Library uses templates extensively.

This chapter teaches you about template support in C++ with an emphasis on the aspects that arise in the Standard Library. Along the way, you will learn about some nifty features that you can employ in your programs aside from using the Standard Library.

CLASS TEMPLATES

Class templates define a class where the types of some of the variables, return types of methods, and/or parameters to the methods are specified as parameters. Class templates are useful primarily for containers, or data structures, that store objects. This section uses a running example of a Grid container. In order to keep the examples reasonable in length and simple enough to illustrate specific points, different sections of the chapter add features to the Grid container that are not used in subsequent sections.

Writing a Class Template

Suppose that you want a generic game board class that you can use as a chessboard, checkers board, tic-tac-toe board, or any other two-dimensional game board. In order to make it general-purpose, you should be able to store chess pieces, checkers pieces, tic-tac-toe pieces, or any type of game piece.

Coding without Templates

Without templates, the best approach to build a generic game board is to employ polymorphism to store generic GamePiece objects. Then, you could let the pieces for each game inherit from the GamePiece class. For example, in a chess game, ChessPiece would be a derived class of GamePiece. Through polymorphism, the GameBoard, written to store GamePieces, could also store ChessPieces. Because it should be possible to copy a GameBoard, the GameBoard needs to be able to copy GamePieces. This implementation employs polymorphism, so one solution is to add a pure virtual clone() method to the GamePiece base class. Here is the basic GamePiece interface:

class GamePiece
{
    public:
        virtual std::unique_ptr<GamePiece> clone() const = 0;
};

GamePiece is an abstract base class. Concrete classes, such as ChessPiece, derive from it and implement the clone() method:

class ChessPiece : public GamePiece
{
    public:
        virtual std::unique_ptr<GamePiece> clone() const override;
};

std::unique_ptr<GamePiece> ChessPiece::clone() const
{
    // Call the copy constructor to copy this instance
    return std::make_unique<ChessPiece>(*this);
}

The implementation of GameBoard uses a vector of vectors of unique_ptrs to store the GamePieces.

class GameBoard
{
    public:
        explicit GameBoard(size_t width = kDefaultWidth,
            size_t height = kDefaultHeight);
        GameBoard(const GameBoard& src);   // copy constructor
        virtual ~GameBoard() = default;    // virtual defaulted destructor
        GameBoard& operator=(const GameBoard& rhs); // assignment operator

        // Explicitly default a move constructor and assignment operator.
        GameBoard(GameBoard&& src) = default;
        GameBoard& operator=(GameBoard&& src) = default;

        std::unique_ptr<GamePiece>& at(size_t x, size_t y);
        const std::unique_ptr<GamePiece>& at(size_t x, size_t y) const;

        size_t getHeight() const { return mHeight; }
        size_t getWidth() const { return mWidth; }

        static const size_t kDefaultWidth = 10;
        static const size_t kDefaultHeight = 10;

        friend void swap(GameBoard& first, GameBoard& second) noexcept;

    private:
        void verifyCoordinate(size_t x, size_t y) const;

        std::vector<std::vector<std::unique_ptr<GamePiece>>> mCells;
        size_t mWidth, mHeight;
};

In this implementation, at() returns a reference to the piece at a specified spot instead of a copy of the piece. The GameBoard serves as an abstraction of a two-dimensional array, so it should provide array access semantics by giving the actual object at an index, not a copy of the object. Client code should not store this reference for future use because it might become invalid. Instead, client code should call at() right before using the returned reference. This follows the design philosophy of the Standard Library std::vector class.

Here are the method definitions. Note that this implementation uses the copy-and-swap idiom for the assignment operator, and Scott Meyer’s const_cast() pattern to avoid code duplication, both of which are discussed in Chapter 9.

GameBoard::GameBoard(size_t width, size_t height)
    : mWidth(width), mHeight(height)
{
    mCells.resize(mWidth);
    for (auto& column : mCells) {
        column.resize(mHeight);
    }
}

GameBoard::GameBoard(const GameBoard& src)
    : GameBoard(src.mWidth, src.mHeight)
{
    // The ctor-initializer of this constructor delegates first to the
    // non-copy constructor to allocate the proper amount of memory.

    // The next step is to copy the data.
    for (size_t i = 0; i < mWidth; i++) {
        for (size_t j = 0; j < mHeight; j++) {
            if (src.mCells[i][j])
                mCells[i][j] = src.mCells[i][j]->clone();
        }
    }
}

void GameBoard::verifyCoordinate(size_t x, size_t y) const
{
    if (x >= mWidth || y >= mHeight) {
        throw std::out_of_range("");
    }
}

void swap(GameBoard& first, GameBoard& second) noexcept
{
    using std::swap;

    swap(first.mWidth, second.mWidth);
    swap(first.mHeight, second.mHeight);
    swap(first.mCells, second.mCells);
}

GameBoard& GameBoard::operator=(const GameBoard& rhs)
{
    // Check for self-assignment
    if (this == &rhs) {
        return *this;
    }

    // Copy-and-swap idiom
    GameBoard temp(rhs); // Do all the work in a temporary instance
    swap(*this, temp); // Commit the work with only non-throwing operations
    return *this;
}

const unique_ptr<GamePiece>& GameBoard::at(size_t x, size_t y) const
{
    verifyCoordinate(x, y);
    return mCells[x][y];
}

unique_ptr<GamePiece>& GameBoard::at(size_t x, size_t y)
{
    return const_cast<unique_ptr<GamePiece>&>(as_const(*this).at(x, y));
}

This GameBoard class works pretty well:

GameBoard chessBoard(8, 8);
auto pawn = std::make_unique<ChessPiece>();
chessBoard.at(0, 0) = std::move(pawn);
chessBoard.at(0, 1) = std::make_unique<ChessPiece>();
chessBoard.at(0, 1) = nullptr;

A Template Grid Class

The GameBoard class in the previous section is nice, but insufficient. A first problem is that you cannot use GameBoard to store elements by value; it always stores pointers. Another, more serious issue is related to type safety. Each cell in a GameBoard stores a unique_ptr<GamePiece>. Even if you are storing ChessPieces, when you use at() to request a certain cell, you will get back a unique_ptr<GamePiece>. This means you have to downcast the retrieved GamePiece to a ChessPiece to be able to make use of ChessPiece’s specific functionality. Another shortcoming of GameBoard is that it cannot be used to store primitive types, such as int or double, because the type stored in a cell has to derive from GamePiece.

So, it would be nice if you could write a generic Grid class that you could use for storing ChessPieces, SpreadsheetCells, ints, doubles, and so on. In C++, you can do this by writing a class template, which allows you to write a class without specifying one or more types. Clients then instantiate the template by specifying the types they want to use. This is called generic programming. The biggest advantage of generic programming is type safety. The types used in the class and its methods are concrete types, and not abstract base class types, as is the case with the polymorphic solution from the previous section. For example, suppose there is not only a ChessPiece but also a TicTacToePiece:

class TicTacToePiece : public GamePiece
{
    public:
        virtual std::unique_ptr<GamePiece> clone() const override;
};

std::unique_ptr<GamePiece> TicTacToePiece::clone() const
{
    // Call the copy constructor to copy this instance
    return std::make_unique<TicTacToePiece>(*this);
}

With the polymorphic solution from the previous section, nothing stops you from storing tic-tac-toe pieces and chess pieces on the same chess board:

GameBoard chessBoard(8, 8);
chessBoard.at(0, 0) = std::make_unique<ChessPiece>();
chessBoard.at(0, 1) = std::make_unique<TicTacToePiece>();

The big problem with this is that you somehow need to remember what a cell is storing, so that you can perform the correct downcast when you call at().

The Grid Class Definition

In order to understand class templates, it is helpful to examine the syntax. The following example shows how you can modify the GameBoard class to make a templatized Grid class. The syntax is explained in detail following the code. Note that the class name has changed from GameBoard to Grid. The Grid should also be usable with primitive types such as int and double. That’s why I opted to implement this solution using value semantics without polymorphism, compared to the polymorphic pointer semantics used in the GameBoard implementation. A downside of using value semantics compared to pointer semantics is that you cannot have a true empty cell, that is, a cell must always contain some value. With pointer semantics, an empty cell stores nullptr. Luckily, C++17’s std::optional, defined in <optional>, comes to the rescue here. It allows you to use value semantics, while still having a way to represent empty cells.

template <typename T>
class Grid
{
    public:
        explicit Grid(size_t width = kDefaultWidth,
            size_t height = kDefaultHeight);
        virtual ~Grid() = default;

        // Explicitly default a copy constructor and assignment operator.
        Grid(const Grid& src) = default;
        Grid<T>& operator=(const Grid& rhs) = default;

        // Explicitly default a move constructor and assignment operator.
        Grid(Grid&& src) = default;
        Grid<T>& operator=(Grid&& rhs) = default;

        std::optional<T>& at(size_t x, size_t y);
        const std::optional<T>& at(size_t x, size_t y) const;

        size_t getHeight() const { return mHeight; }
        size_t getWidth() const { return mWidth; }

        static const size_t kDefaultWidth = 10;
        static const size_t kDefaultHeight = 10;

    private:
        void verifyCoordinate(size_t x, size_t y) const;

        std::vector<std::vector<std::optional<T>>> mCells;
        size_t mWidth, mHeight;
};

Now that you’ve seen the full class definition, take another look at it, one line at a time:

template <typename T>

This first line says that the following class definition is a template on one type. Both template and typename are keywords in C++. As discussed earlier, templates “parameterize” types in the same way that functions “parameterize” values. Just as you use parameter names in functions to represent the arguments that the caller will pass, you use template parameter names (such as T) in templates to represent the types that the caller will specify. There’s nothing special about the name T—you can use whatever name you want. Traditionally, when a single type is used, it is called T, but that’s just a historical convention, like calling the integer that indexes an array i or j. The template specifier holds for the entire statement, which in this case is the class definition.

In the earlier GameBoard class, the mCells data member is a vector of vectors of pointers, which requires special code for copying—thus the need for a copy constructor and copy assignment operator. In the Grid class, mCells is a vector of vectors of optional values, so the compiler-generated copy constructor and assignment operator are fine. However, as explained in Chapter 8, once you have a user-declared destructor, it’s deprecated for the compiler to implicitly generate a copy constructor or copy assignment operator, so the Grid class template explicitly defaults them. It also explicitly defaults the move constructor and move assignment operator. Here is the explicitly defaulted copy assignment operator:

Grid<T>& operator=(const Grid& rhs) = default;

As you can see, the type of the rhs parameter is no longer a const GameBoard&, but a const Grid&. You can also specify it as a const Grid<T>&. Within a class definition, the compiler interprets Grid as Grid<T> where needed. However, outside a class definition you need to use Grid<T>. When you write a class template, what you used to think of as the class name (Grid) is actually the template name. When you want to talk about actual Grid classes or types, you discuss them as Grid<T>, instantiations of the Grid class template for a certain type, such as int, SpreadsheetCell, or ChessPiece.

Because mCells is not storing pointers anymore, but optional values, the at() methods now return optional<T>& or const optional<T>& instead of unique_ptrs:

std::optional<T>& at(size_t x, size_t y);
const std::optional<T>& at(size_t x, size_t y) const;

The Grid Class Method Definitions

The template <typename T> specifier must precede each method definition for the Grid template. The constructor looks like this:

template <typename T>
Grid<T>::Grid(size_t width, size_t height)
    : mWidth(width), mHeight(height)
{
    mCells.resize(mWidth);
    for (auto& column : mCells) {
    // Equivalent to:
    //for (std::vector<std::optional<T>>& column : mCells) {
        column.resize(mHeight);
    }
}

Note that the class name before the :: is Grid<T>, not Grid. You must specify Grid<T> as the class name in all your methods and static data member definitions. The body of the constructor is identical to the GameBoard constructor.

The rest of the method definitions are also similar to their equivalents in the GameBoard class with the exception of the appropriate template and Grid<T> syntax changes:

template <typename T>
void Grid<T>::verifyCoordinate(size_t x, size_t y) const
{
    if (x >= mWidth || y >= mHeight) {
        throw std::out_of_range("");
    }
}

template <typename T>
const std::optional<T>& Grid<T>::at(size_t x, size_t y) const
{
    verifyCoordinate(x, y);
    return mCells[x][y];
}

template <typename T>
std::optional<T>& Grid<T>::at(size_t x, size_t y)
{
    return const_cast<std::optional<T>&>(std::as_const(*this).at(x, y));
}

Using the Grid Template

When you want to create grid objects, you cannot use Grid alone as a type; you must specify the type that will be stored in that Grid. Creating an object of a class template for a specific type is called instantiating the template. Here is an example:

Grid<int> myIntGrid; // declares a grid that stores ints,
                     // using default arguments for the constructor
Grid<double> myDoubleGrid(11, 11); // declares an 11x11 Grid of doubles

myIntGrid.at(0, 0) = 10;
int x = myIntGrid.at(0, 0).value_or(0);

Grid<int> grid2(myIntGrid);  // Copy constructor
Grid<int> anotherIntGrid;
anotherIntGrid = grid2;      // Assignment operator

Note that the type of myIntGrid, grid2, and anotherIntGrid is Grid<int>. You cannot store SpreadsheetCells or ChessPieces in these grids; the compiler will generate an error if you try to do so.

Note also the use of value_or(). The at() method returns an std::optional reference. This optional can contain a value or not. The value_or() method returns the value inside the optional if there is a value; otherwise it returns the argument given to value_or().

The type specification is important; neither of the following two lines compiles:

Grid test;   // WILL NOT COMPILE
Grid<> test; // WILL NOT COMPILE

The first line causes your compiler to complain with something like, “use of class template requires template argument list.” The second line causes it to say something like, “too few template arguments.”

If you want to declare a function or method that takes a Grid object, you must specify the type stored in that grid as part of the Grid type:

void processIntGrid(Grid<int>& grid)
{
   // Body omitted for brevity
}

Alternatively, you can use function templates, discussed later in this chapter, to write a function templatized on the type of the elements in the grid.

The Grid template can store more than just ints. For example, you can instantiate a Grid that stores SpreadsheetCells:

Grid<SpreadsheetCell> mySpreadsheet;
SpreadsheetCell myCell(1.234);
mySpreadsheet.at(3, 4) = myCell;

You can store pointer types as well:

Grid<const char*> myStringGrid;
myStringGrid.at(2, 2) = "hello";

The type specified can even be another template type:

Grid<vector<int>> gridOfVectors;
vector<int> myVector{ 1, 2, 3, 4 };
gridOfVectors.at(5, 6) = myVector;

You can also dynamically allocate Grid template instantiations on the heap:

auto myGridOnHeap = make_unique<Grid<int>>(2, 2); // 2x2 Grid on the heap
myGridOnHeap->at(0, 0) = 10;
int x = myGridOnHeap->at(0, 0).value_or(0);

Angle Brackets

Some of the examples in this book use templates with double angle brackets, as in this example:

std::vector<std::vector<T>> mCells;

This works perfectly fine since C++11. However, before C++11, the double angle brackets >> could mean only one thing: the >> operator. Depending on the types involved, this >> operator could be a right bit-shift operation, or a stream extraction operator. This was annoying with template code, because you were forced to put a space between double angle brackets. The previous declaration had to be written as follows:

std::vector<std::vector<T> > mCells;

This book uses the modern style without the spaces.

How the Compiler Processes Templates

In order to understand the intricacies of templates, you need to learn how the compiler processes template code. When the compiler encounters template method definitions, it performs syntax checking, but doesn’t actually compile the templates. It can’t compile template definitions because it doesn’t know for which types they will be used. It’s impossible for a compiler to generate code for something like x = y without knowing the types of x and y.

When the compiler encounters an instantiation of the template, such as Grid<int> myIntGrid, it writes code for an int version of the Grid template by replacing each T in the class template definition with int. When the compiler encounters a different instantiation of the template, such as Grid<SpreadsheetCell> mySpreadsheet, it writes another version of the Grid class for SpreadsheetCells. The compiler just writes the code that you would write if you didn’t have template support in the language and had to write separate classes for each element type. There’s no magic here; templates just automate an annoying process. If you don’t instantiate a class template for any types in your program, then the class method definitions are never compiled.

This instantiation process explains why you need to use the Grid<T> syntax in various places in your definition. When the compiler instantiates the template for a particular type, such as int, it replaces T with int, so that Grid<int> is the type.

Selective Instantiation

The compiler always generates code for all virtual methods of a generic class. However, for non-virtual methods, the compiler generates code only for those non-virtual methods that you actually call for a particular type. For example, given the preceding Grid class template, suppose that you write this code (and only this code) in main():

Grid<int> myIntGrid;
myIntGrid.at(0, 0) = 10;

The compiler generates only the zero-argument constructor, the destructor, and the non-const at() method for an int version of the Grid. It does not generate other methods like the copy constructor, the assignment operator, or getHeight().

Template Requirements on Types

When you write code that is independent of types, you must assume certain things about those types. For example, in the Grid template, you assume that the element type (represented by T) is destructible. The Grid template implementation doesn’t assume much. However, other templates could assume that their template type parameters support, for example, an assignment operator.

If you attempt to instantiate a template with a type that does not support all the operations used by the template in your particular program, the code will not compile, and the error messages will almost always be quite obscure. However, even if the type you want to use doesn’t support the operations required by all the template code, you can exploit selective instantiation to use some methods but not others.

Distributing Template Code between Files

Normally you put class definitions in a header file and method definitions in a source file. Code that creates or uses objects of the class #includes the header file and obtains access to the method code via the linker. Templates don’t work that way. Because they are “templates” for the compiler to generate the actual methods for the instantiated types, both class template definitions and method definitions must be available to the compiler in any source file that uses them. There are several mechanisms to obtain this inclusion.

Template Definitions in Header Files

You can place the method definitions directly in the same header file where you define the class itself. When you #include this file in a source file where you use the template, the compiler will have access to all the code it needs. This mechanism is used for the previous Grid implementation.

Alternatively, you can place the template method definitions in a separate header file that you #include in the header file with the class definitions. Make sure the #include for the method definitions follows the class definition; otherwise, the code won’t compile. For example:

template <typename T>
class Grid
{
    // Class definition omitted for brevity
};

#include "GridDefinitions.h"

Any client that wants to use the Grid template needs only to include the Grid.h header file. This division helps to keep the distinction between class definitions and method definitions.

Template Definitions in Source Files

Method implementations look strange in header files. If that syntax annoys you, there is a way that you can place the method definitions in a source file. However, you still need to make the definitions available to the code that uses the template, which you can do by #includeing the method implementation source file in the class template definition header file. That sounds odd if you’ve never seen it before, but it’s legal in C++. The header file looks like this:

template <typename T>
class Grid
{
    // Class definition omitted for brevity
};

#include "Grid.cpp"

When using this technique, make sure you don’t add the Grid.cpp file to your project, because it is not supposed to be, and cannot be, compiled separately; it should be #included only in a header file!

You can actually call your file with method implementations anything you want. Some programmers like to give source files that are included an .inl extension, for example, Grid.inl.

Limit Class Template Instantiations

If you want your class templates to be used only with certain known types, you can use the following technique.

Suppose you want the Grid class to be instantiated only for int, double, and vector<int>. The header file should look like this:

template <typename T>
class Grid
{
    // Class definition omitted for brevity
};

Note that there are no method definitions in this header file and that there is no #include at the end!

In this case, you need a real .cpp file added to your project, which contains the method definitions and looks like this:

#include "Grid.h"
#include <utility>

template <typename T>
Grid<T>::Grid(size_t width, size_t height)
    : mWidth(width), mHeight(height)
{
    mCells.resize(mWidth);
    for (auto& column : mCells) {
        column.resize(mHeight);
    }
}
// Other method definitions omitted for brevity...

For this method to work, you need to explicitly instantiate the template for those types that you want to allow clients to use. At the end of the .cpp file you can do this as follows:

// Explicit instantiations for the types you want to allow.
template class Grid<int>;
template class Grid<double>;
template class Grid<std::vector<int>>;

With these explicit instantiations, you disallow client code from using the Grid class template with other types, such as SpreadsheetCell.

Template Parameters

In the Grid example, the Grid template has one template parameter: the type that is stored in the grid. When you write the class template, you specify the parameter list inside the angle brackets, like this:

template <typename T>

This parameter list is similar to the parameter list in a function or method. As in functions and methods, you can write a class with as many template parameters as you want. Additionally, these parameters don’t have to be types, and they can have default values.

Non-type Template Parameters

Non-type parameters are “normal” parameters such as ints and pointers: the kind of parameters with which you’re familiar from functions and methods. However, non-type template parameters can only be integral types (char, int, long, and so on), enumeration types, pointers, references, and std::nullptr_t. Starting with C++17, you can also specify auto, auto&, auto*, and so on, as the type of a non-type template parameter. In that case, the compiler deduces the type automatically.

In the Grid class template, you could use non-type template parameters to specify the height and width of the grid instead of specifying them in the constructor. The principle advantage to specifying non-type parameters in the template list instead of in the constructor is that the values are known before the code is compiled. Recall that the compiler generates code for templatized methods by substituting the template parameters before compiling. Thus, you can use a normal two-dimensional array in your implementation instead of a vector of vectors that is dynamically resized. Here is the new class definition:

template <typename T, size_t WIDTH, size_t HEIGHT>
class Grid
{
    public:
        Grid() = default;
        virtual ~Grid() = default;

        // Explicitly default a copy constructor and assignment operator.
        Grid(const Grid& src) = default;
        Grid<T, WIDTH, HEIGHT>& operator=(const Grid& rhs) = default;

        std::optional<T>& at(size_t x, size_t y);
        const std::optional<T>& at(size_t x, size_t y) const;

        size_t getHeight() const { return HEIGHT; }
        size_t getWidth() const { return WIDTH; }

    private:
        void verifyCoordinate(size_t x, size_t y) const;

        std::optional<T> mCells[WIDTH][HEIGHT];
};

This class does not explicitly default the move constructor and move assignment operator, because C-style arrays do not support move semantics anyways.

Note that the template parameter list requires three parameters: the type of objects stored in the grid, and the width and height of the grid. The width and height are used to create a two-dimensional array to store the objects. Here are the class method definitions:

template <typename T, size_t WIDTH, size_t HEIGHT>
void Grid<T, WIDTH, HEIGHT>::verifyCoordinate(size_t x, size_t y) const
{
    if (x >= WIDTH || y >= HEIGHT) {
        throw std::out_of_range("");
    }
}

template <typename T, size_t WIDTH, size_t HEIGHT>
const std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y) const
{
    verifyCoordinate(x, y);
    return mCells[x][y];
}

template <typename T, size_t WIDTH, size_t HEIGHT>
std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y)
{
    return const_cast<std::optional<T>&>(std::as_const(*this).at(x, y));
}

Note that wherever you previously specified Grid<T> you must now specify Grid<T, WIDTH, HEIGHT> to represent the three template parameters.

You can instantiate this template and use it like this:

Grid<int, 10, 10> myGrid;
Grid<int, 10, 10> anotherGrid;
myGrid.at(2, 3) = 42;
anotherGrid = myGrid;
cout << anotherGrid.at(2, 3).value_or(0);

This code seems great, but unfortunately, there are more restrictions than you might initially expect. First, you can’t use a non-constant integer to specify the height or width. The following code doesn’t compile:

size_t height = 10;
Grid<int, 10, height> testGrid; // DOES NOT COMPILE

However, if you define height as a constant, it compiles:

const size_t height = 10;
Grid<int, 10, height> testGrid; // Compiles and works

constexpr functions with the correct return type also work. For example, if you have a constexpr function that returns a size_t, you can use it to initialize the height template parameter:

constexpr size_t getHeight() { return 10; }
...
Grid<double, 2, getHeight()> myDoubleGrid;

A second restriction might be more significant. Now that the width and height are template parameters, they are part of the type of each grid. That means that Grid<int, 10, 10> and Grid<int, 10, 11> are two different types. You can’t assign an object of one type to an object of the other, and variables of one type can’t be passed to functions or methods that expect variables of another type.

Default Values for Type Parameters

If you continue the approach of making height and width template parameters, you might want to provide defaults for the height and width non-type template parameters just as you did previously in the constructor of the Grid<T> class. C++ allows you to provide defaults for template parameters with a similar syntax. While you are at it, you could also provide a default for the T type parameter. Here is the class definition:

template <typename T = int, size_t WIDTH = 10, size_t HEIGHT = 10>
class Grid
{
    // Remainder is identical to the previous version
};

You need not specify the default values for T, WIDTH, and HEIGHT in the template specification for the method definitions. For example, here is the implementation of at():

template <typename T, size_t WIDTH, size_t HEIGHT>
const std::optional<T>& Grid<T, WIDTH, HEIGHT>::at(size_t x, size_t y) const
{
    verifyCoordinate(x, y);
    return mCells[x][y];
}

Now, you can instantiate a Grid without any template parameters, with only the element type, the element type and the width, or the element type, width, and height:

Grid<> myIntGrid;
Grid<int> myGrid;
Grid<int, 5> anotherGrid;
Grid<int, 5, 5> aFourthGrid;

Note that if you don’t specify any class template parameters, you still need to specify an empty set of angle brackets. For example, the following does not compile!

Grid myIntGrid;

The rules for default arguments in class template parameter lists are the same as for functions or methods; that is, you can provide defaults for parameters in order starting from the right.

image Template Parameter Deduction for Constructors

C++17 adds support to automatically deduce the template parameters from the arguments passed to a class template constructor. Before C++17, you always had to specify all the template parameters for a class template explicitly.

For example, the Standard Library has a class template called std::pair, defined in <utility>, and discussed in Chapter 17 in detail. For now, it suffices to know that a pair stores exactly two values of two possibly different types, which you have to specify as the template parameters:

std::pair<int, double> pair1(1, 2.3);

To avoid the need of having to write the template parameters, a helper function template called std::make_pair() is available. Details of function templates are discussed later in this chapter. Function templates have always supported the automatic deduction of template parameters based on the arguments passed to the function template. So, make_pair() is capable of automatically deducing the template type parameters based on the values passed to it. For example, the compiler deduces pair<int, double> for the following call:

auto pair2 = std::make_pair(1, 2.3);

With C++17, such helper function templates are not necessary anymore. The compiler now automatically deduces the template type parameters based on the arguments passed to the constructor. For the pair class template, you can simply write the following code:

std::pair pair3(1, 2.3);

Of course, this only works when all template parameters of a class template either have a default value, or are used as parameters in the constructor so they can be deduced.

User-Defined Deduction Guides

You can also write your own user-defined deduction guides. These allow you to write rules for how the template parameters have to be deduced. This is an advanced topic, so it is not discussed in detail, although one example will demonstrate their power.

Suppose you have the following SpreadsheetCell class template:

template<typename T>
class SpreadsheetCell
{
    public:
        SpreadsheetCell(const T& t) : mContent(t) { }

        const T& getContent() const { return mContent; }

    private:
        T mContent;
};

With automatic template parameter deduction, you can create a SpreadsheetCell with an std::string type:

std::string myString = "Hello World!";
SpreadsheetCell cell(myString);

However, if you pass a const char* to the SpreadsheetCell constructor, then the type T is deduced as const char*, which is not what you want. You can create the following user-defined deduction guide to cause it to deduce T as std::string when passing a const char* as argument to the constructor:

SpreadsheetCell(const char*) -> SpreadsheetCell<std::string>;

This guide has to be defined outside the class definition but inside the same namespace as the SpreadsheetCell class.

The general syntax is as follows. The explicit keyword is optional. It behaves the same as explicit for single-parameter constructors, so it only makes sense for deduction rules with one parameter.

explicit TemplateName(Parameters) -> DeducedTemplate;

Method Templates

C++ allows you to templatize individual methods of a class. These methods can be inside a class template or in a non-templatized class. When you write a templatized class method, you are actually writing many different versions of that method for many different types. Method templates come in useful for assignment operators and copy constructors in class templates.

Consider the original Grid template with only one template parameter: the element type. You can instantiate grids of many different types, such as ints and doubles:

Grid<int> myIntGrid;
Grid<double> myDoubleGrid;

However, Grid<int> and Grid<double> are two different types. If you write a function that takes an object of type Grid<double>, you cannot pass a Grid<int>. Even though you know that the elements of an int grid could be copied to the elements of a double grid, because the ints could be coerced into doubles, you cannot assign an object of type Grid<int> to one of type Grid<double> or construct a Grid<double> from a Grid<int>. Neither of the following two lines compiles:

myDoubleGrid = myIntGrid;              // DOES NOT COMPILE
Grid<double> newDoubleGrid(myIntGrid); // DOES NOT COMPILE

The problem is that the copy constructor and assignment operator for the Grid template are as follows,

Grid(const Grid& src);
Grid<T>& operator=(const Grid& rhs);

which are equivalent to

Grid(const Grid<T>& src);
Grid<T>& operator=(const Grid<T>& rhs);

The Grid copy constructor and operator= both take a reference to a const Grid<T>. When you instantiate a Grid<double> and try to call the copy constructor and operator=, the compiler generates methods with these prototypes:

Grid(const Grid<double>& src);
Grid<double>& operator=(const Grid<double>& rhs);

Note that there are no constructors or operator= that take a Grid<int> within the generated Grid<double> class.

Luckily, you can rectify this oversight by adding templatized versions of the copy constructor and assignment operator to the Grid class to generate methods that will convert from one grid type to another. Here is the new Grid class definition:

template <typename T>
class Grid
{
    public:
        // Omitted for brevity

        template <typename E>
        Grid(const Grid<E>& src);

        template <typename E>
        Grid<T>& operator=(const Grid<E>& rhs);

        void swap(Grid& other) noexcept;

        // Omitted for brevity
};

Examine the new templatized copy constructor first:

template <typename E>
Grid(const Grid<E>& src);

You can see that there is another template declaration with a different typename, E (short for “element”). The class is templatized on one type, T, and the new copy constructor is also templatized on a different type, E. This twofold templatization allows you to copy grids of one type to another. Here is the definition of the new copy constructor:

template <typename T>
template <typename E>
Grid<T>::Grid(const Grid<E>& src)
    : Grid(src.getWidth(), src.getHeight())
{
    // The ctor-initializer of this constructor delegates first to the
    // non-copy constructor to allocate the proper amount of memory.

    // The next step is to copy the data.
    for (size_t i = 0; i < mWidth; i++) {
        for (size_t j = 0; j < mHeight; j++) {
            mCells[i][j] = src.at(i, j);
        }
    }
}

As you can see, you must declare the class template line (with the T parameter) before the member template line (with the E parameter). You can’t combine them like this:

template <typename T, typename E> // Wrong for nested template constructor!
Grid<T>::Grid(const Grid<E>& src)

In addition to the extra template parameter line before the constructor definition, note that you must use the public accessor methods getWidth(), getHeight(), and at() to access the elements of src. That’s because the object you’re copying to is of type Grid<T>, and the object you’re copying from is of type Grid<E>. They are not the same type, so you must use public methods.

The templatized assignment operator takes a const Grid<E>& but returns a Grid<T>&:

template <typename T>
template <typename E>
Grid<T>& Grid<T>::operator=(const Grid<E>& rhs)
{
    // no need to check for self-assignment because this version of
    // assignment is never called when T and E are the same

    // Copy-and-swap idiom
    Grid<T> temp(rhs); // Do all the work in a temporary instance
    swap(temp); // Commit the work with only non-throwing operations
    return *this;
}

You need not check for self-assignment in the templatized assignment operator, because assignment of the same types still happens in the old, non-templatized, version of operator=, so there’s no way you can get self-assignment here.

The implementation of this assignment operator uses the copy-and-swap idiom introduced in Chapter 9. However, instead of using a friend swap() function, the Grid template uses a swap() method because function templates are only discussed later in this chapter. Note that this swap() method can only swap Grids of the same type, but that’s okay because the templatized assignment operator first converts the given Grid<E> to a Grid<T> called temp using the templatized copy constructor. Afterward, it uses the swap() method to swap this temporary Grid<T> with this, which is also of type Grid<T>. Here is the definition of the swap() method:

template <typename T>
void Grid<T>::swap(Grid<T>& other) noexcept
{
    using std::swap;

    swap(mWidth, other.mWidth);
    swap(mHeight, other.mHeight);
    swap(mCells, other.mCells);
}

Method Templates with Non-type Parameters

Looking at the earlier example with integer template parameters for HEIGHT and WIDTH, you see that a major problem is that the height and width become part of the types. This restriction prevents you from assigning a grid with one height and width to a grid with a different height and width. In some cases, however, it’s desirable to assign or copy a grid of one size to a grid of a different size. Instead of making the destination object a perfect clone of the source object, you would copy only those elements from the source array that fit in the destination array, padding the destination array with default values if the source array is smaller in either dimension. With method templates for the assignment operator and copy constructor, you can do exactly that, thus allowing assignment and copying of different-sized grids. Here is the class definition:

template <typename T, size_t WIDTH = 10, size_t HEIGHT = 10>
class Grid
{
    public:
        Grid() = default;
        virtual ~Grid() = default;

        // Explicitly default a copy constructor and assignment operator.
        Grid(const Grid& src) = default;
        Grid<T, WIDTH, HEIGHT>& operator=(const Grid& rhs) = default;

        template <typename E, size_t WIDTH2, size_t HEIGHT2>
        Grid(const Grid<E, WIDTH2, HEIGHT2>& src);

        template <typename E, size_t WIDTH2, size_t HEIGHT2>
        Grid<T, WIDTH, HEIGHT>& operator=(const Grid<E, WIDTH2, HEIGHT2>& rhs);

        void swap(Grid& other) noexcept;

        std::optional<T>& at(size_t x, size_t y);
        const std::optional<T>& at(size_t x, size_t y) const;

        size_t getHeight() const { return HEIGHT; }
        size_t getWidth() const { return WIDTH; }

    private:
        void verifyCoordinate(size_t x, size_t y) const;

        std::optional<T> mCells[WIDTH][HEIGHT];
};

This new definition includes method templates for the copy constructor and assignment operator, plus a helper method swap(). Note that the non-templatized copy constructor and assignment operator are explicitly defaulted (because of the user-declared destructor). They simply copy or assign mCells from the source to the destination, which is exactly the semantics you want for two grids of the same size.

Here is the templatized copy constructor:

template <typename T, size_t WIDTH, size_t HEIGHT>
template <typename E, size_t WIDTH2, size_t HEIGHT2>
Grid<T, WIDTH, HEIGHT>::Grid(const Grid<E, WIDTH2, HEIGHT2>& src)
{
    for (size_t i = 0; i < WIDTH; i++) {
        for (size_t j = 0; j < HEIGHT; j++) {
            if (i < WIDTH2 && j < HEIGHT2) {
                mCells[i][j] = src.at(i, j);
            } else {
                mCells[i][j].reset();
            }
        }
    }
}

Note that this copy constructor copies only WIDTH and HEIGHT elements in the x and y dimensions, respectively, from src, even if src is bigger than that. If src is smaller in either dimension, the std::optional objects in the extra spots are reset using the reset() method.

Here are the implementations of swap() and operator=:

template <typename T, size_t WIDTH, size_t HEIGHT>
void Grid<T, WIDTH, HEIGHT>::swap(Grid<T, WIDTH, HEIGHT>& other) noexcept
{
    using std::swap;

    swap(mCells, other.mCells);
}

template <typename T, size_t WIDTH, size_t HEIGHT>
template <typename E, size_t WIDTH2, size_t HEIGHT2>
Grid<T, WIDTH, HEIGHT>& Grid<T, WIDTH, HEIGHT>::operator=(
    const Grid<E, WIDTH2, HEIGHT2>& rhs)
{
    // no need to check for self-assignment because this version of
    // assignment is never called when T and E are the same

    // Copy-and-swap idiom
    Grid<T, WIDTH, HEIGHT> temp(rhs); // Do all the work in a temp instance
    swap(temp); // Commit the work with only non-throwing operations
    return *this;
}

Class Template Specialization

You can provide alternate implementations of class templates for specific types. For example, you might decide that the Grid behavior for const char*s (C-style strings) doesn’t make sense. A Grid<const char*> will store its elements in a vector<vector<optional<const char*>>>. The copy constructor and assignment operator will perform shallow copies of this const char* pointer type. For const char*s, it might make sense to do a deep copy of the string. The easiest solution for this is to write an alternative implementation specifically for const char*s, which stores the strings in a vector<vector<optional<string>>> and converts C-style strings into C++ strings so that their memory is automatically handled.

Alternate implementations of templates are called template specializations. You might find the syntax to be a little weird. When you write a class template specialization, you must specify that it’s a template, and that you are writing the version of the template for that particular type. Here is the syntax for specializing the original version of the Grid for const char*s:

// When the template specialization is used, the original template must be
// visible too. Including it here ensures that it will always be visible
// when this specialization is visible.
#include "Grid.h"

template <>
class Grid<const char*>
{
    public:
        explicit Grid(size_t width = kDefaultWidth,
            size_t height = kDefaultHeight);
        virtual ~Grid() = default;

        // Explicitly default a copy constructor and assignment operator.
        Grid(const Grid& src) = default;
        Grid<const char*>& operator=(const Grid& rhs) = default;

        // Explicitly default a move constructor and assignment operator.
        Grid(Grid&& src) = default;
        Grid<const char*>& operator=(Grid&& rhs) = default;

        std::optional<std::string>& at(size_t x, size_t y);
        const std::optional<std::string>& at(size_t x, size_t y) const;

        size_t getHeight() const { return mHeight; }
        size_t getWidth() const { return mWidth; }

        static const size_t kDefaultWidth = 10;
        static const size_t kDefaultHeight = 10;

    private:
        void verifyCoordinate(size_t x, size_t y) const;

        std::vector<std::vector<std::optional<std::string>>> mCells;
        size_t mWidth, mHeight;
};

Note that you don’t refer to any type variable, such as T, in the specialization: you work directly with const char*s. One obvious question at this point is why this class is still a template. That is, what good is the following syntax?

template <>
class Grid<const char*>

This syntax tells the compiler that this class is a const char* specialization of the Grid class. Suppose that you didn’t use that syntax and just tried to write this:

class Grid

The compiler wouldn’t let you do that because there is already a class named Grid (the original class template). Only by specializing it can you reuse the name. The main benefit of specializations is that they can be invisible to the user. When a user creates a Grid of ints or SpreadsheetCells, the compiler generates code from the original Grid template. When the user creates a Grid of const char*s, the compiler uses the const char* specialization. This can all be “behind the scenes.”

Grid<int> myIntGrid;                 // Uses original Grid template
Grid<const char*> stringGrid1(2, 2); // Uses const char* specialization

const char* dummy = "dummy";
stringGrid1.at(0, 0) = "hello";
stringGrid1.at(0, 1) = dummy;
stringGrid1.at(1, 0) = dummy;
stringGrid1.at(1, 1) = "there";

Grid<const char*> stringGrid2(stringGrid1);

When you specialize a template, you don’t “inherit” any code: Specializations are not like derivations. You must rewrite the entire implementation of the class. There is no requirement that you provide methods with the same names or behavior. As an example, the const char* specialization of Grid implements the at() methods by returning an std::optional<std::string>, not an std::optional<const char*>. As a matter of fact, you could write a completely different class with no relation to the original. Of course, that would abuse the template specialization ability, and you shouldn’t do it without good reason. Here are the implementations for the methods of the const char* specialization. Unlike in the template definitions, you do not repeat the template<> syntax before each method definition.

Grid<const char*>::Grid(size_t width, size_t height)
    : mWidth(width), mHeight(height)
{
    mCells.resize(mWidth);
    for (auto& column : mCells) {
        column.resize(mHeight);
    }
}

void Grid<const char*>::verifyCoordinate(size_t x, size_t y) const
{
    if (x >= mWidth || y >= mHeight) {
        throw std::out_of_range("");
    }
}

const std::optional<std::string>& Grid<const char*>::at(
    size_t x, size_t y) const
{
    verifyCoordinate(x, y);
    return mCells[x][y];
}

std::optional<std::string>& Grid<const char*>::at(size_t x, size_t y)
{
    return const_cast<std::optional<std::string>&>(
        std::as_const(*this).at(x, y));
}

This section discussed how to use class template specialization. Template specialization allows you to write a special implementation for a template, with the template types replaced by specific types. Chapter 22 continues the discussion of specialization with a more advanced feature called partial specialization.

Deriving from Class Templates

You can inherit from class templates. If the derived class inherits from the template itself, it must be a template as well. Alternatively, you can derive from a specific instantiation of the class template, in which case your derived class does not need to be a template. As an example of the former, suppose you decide that the generic Grid class doesn’t provide enough functionality to use as a game board. Specifically, you would like to add a move() method to the game board that moves a piece from one location on the board to another. Here is the class definition for the GameBoard template:

#include "Grid.h"

template <typename T>
class GameBoard : public Grid<T>
{
    public:
        explicit GameBoard(size_t width = Grid<T>::kDefaultWidth,
            size_t height = Grid<T>::kDefaultHeight);
        void move(size_t xSrc, size_t ySrc, size_t xDest, size_t yDest);
};

This GameBoard template derives from the Grid template, and thereby inherits all its functionality. You don’t need to rewrite at(), getHeight(), or any of the other methods. You also don’t need to add a copy constructor, operator=, or destructor, because you don’t have any dynamically allocated memory in the GameBoard.

The inheritance syntax looks normal, except that the base class is Grid<T>, not Grid. The reason for this syntax is that the GameBoard template doesn’t really derive from the generic Grid template. Rather, each instantiation of the GameBoard template for a specific type derives from the Grid instantiation for that type. For example, if you instantiate a GameBoard with a ChessPiece type, then the compiler generates code for a Grid<ChessPiece> as well. The “: public Grid<T>” syntax says that this class inherits from whatever Grid instantiation makes sense for the T type parameter. Note that the C++ name lookup rules for template inheritance require you to specify that kDefaultWidth and kDefaultHeight are declared in, and thus dependent on, the Grid<T> base class.

Here are the implementations of the constructor and the move() method. Note the use of Grid<T> in the call to the base class constructor. Additionally, although many compilers don’t enforce it, the name lookup rules require you to use the this pointer or Grid<T>:: to refer to data members and methods in the base class template.

template <typename T>
GameBoard<T>::GameBoard(size_t width, size_t height)
    : Grid<T>(width, height)
{
}

template <typename T>
void GameBoard<T>::move(size_t xSrc, size_t ySrc, size_t xDest, size_t yDest)
{
    Grid<T>::at(xDest, yDest) = std::move(Grid<T>::at(xSrc, ySrc));
    Grid<T>::at(xSrc, ySrc).reset();  // Reset source cell
    // Or:
    // this->at(xDest, yDest) = std::move(this->at(xSrc, ySrc));
    // this->at(xSrc, ySrc).reset();
}

You can use the GameBoard template as follows:

GameBoard<ChessPiece> chessboard(8, 8);
ChessPiece pawn;
chessBoard.at(0, 0) = pawn;
chessBoard.move(0, 0, 0, 1);

Inheritance versus Specialization

Some programmers find the distinction between template inheritance and template specialization confusing. The following table summarizes the differences.

INHERITANCE SPECIALIZATION
Reuses code? Yes: Derived classes contain all base class data members and methods. No: you must rewrite all required code in the specialization.
Reuses name? No: the derived class name must be different from the base class name. Yes: the specialization must have the same name as the original.
Supports polymorphism? Yes: objects of the derived class can stand in for objects of the base class. No: each instantiation of a template for a type is a different type.

Alias Templates

Chapter 11 introduces the concept of type aliases and typedefs. They allow you to give other names to specific types. To refresh your memory, you could, for example, write the following type alias to give a second name to type int:

using MyInt = int;

Similarly, you can use a type alias to give another name to a templatized class. Suppose you have the following class template:

template<typename T1, typename T2>
class MyTemplateClass { /* ... */ };

You can define the following type alias in which you specify both class template type parameters:

using OtherName = MyTemplateClass<int, double>;

A typedef can also be used instead of such a type alias.

It’s also possible to only specify some of the types, and keep the other types as template type parameters. This is called an alias template. For example:

template<typename T1>
using OtherName = MyTemplateClass<T1, double>;

This is something you cannot do with a typedef.

FUNCTION TEMPLATES

You can also write templates for stand-alone functions. For example, you could write a generic function to find a value in an array and return its index:

static const size_t NOT_FOUND = static_cast<size_t>(-1);

template <typename T>
size_t Find(const T& value, const T* arr, size_t size)
{
    for (size_t i = 0; i < size; i++) {
        if (arr[i] == value) {
            return i; // Found it; return the index
        }
    }
    return NOT_FOUND; // Failed to find it; return NOT_FOUND
}

The Find() function template can work on arrays of any type. For example, you could use it to find the index of an int in an array of ints, or a SpreadsheetCell in an array of SpreadsheetCells.

You can call the function in two ways: explicitly specifying the type parameter with angle brackets, or omitting the type and letting the compiler deduce the type parameter from the arguments. Here are some examples:

int myInt = 3, intArray[] = {1, 2, 3, 4};
const size_t sizeIntArray = std::size(intArray);

size_t res;
res = Find(myInt, intArray, sizeIntArray);      // calls Find<int> by deduction
res = Find<int>(myInt, intArray, sizeIntArray); // calls Find<int> explicitly
if (res != NOT_FOUND)
    cout << res << endl;
else
    cout << "Not found" << endl;

double myDouble = 5.6, doubleArray[] = {1.2, 3.4, 5.7, 7.5};
const size_t sizeDoubleArray = std::size(doubleArray);

// calls Find<double> by deduction
res = Find(myDouble, doubleArray, sizeDoubleArray);
// calls Find<double> explicitly
res = Find<double>(myDouble, doubleArray, sizeDoubleArray);
if (res != NOT_FOUND)
    cout << res << endl;
else
    cout << "Not found" << endl;

//res = Find(myInt, doubleArray, sizeDoubleArray); // DOES NOT COMPILE!
                                       // Arguments are different types.
// calls Find<double> explicitly, even with myInt
res = Find<double>(myInt, doubleArray, sizeDoubleArray);

SpreadsheetCell cell1(10), cellArray[] =
        {SpreadsheetCell(4), SpreadsheetCell(10)};
const size_t sizeCellArray = std::size(cellArray);

res = Find(cell1, cellArray, sizeCellArray);
res = Find<SpreadsheetCell>(cell1, cellArray, sizeCellArray);

The previous implementation of the Find() function requires the size of the array as one of the parameters. Sometimes the compiler knows the exact size of an array, for example, for stack-based arrays. It would be nice to be able to call Find() with such arrays without the need to pass it the size of the array. This can be accomplished by adding the following function template. The implementation just forwards the call to the previous Find() function template. This also demonstrates that function templates can take non-type parameters, just like class templates.

template <typename T, size_t N>
size_t Find(const T& value, const T(&arr)[N])
{
    return Find(value, arr, N);
}

The syntax of this version of Find() looks a bit strange, but its use is straightforward, as in this example:

int myInt = 3, intArray[] = {1, 2, 3, 4};
size_t res = Find(myInt, intArray);

Like class template method definitions, function template definitions (not just the prototypes) must be available to all source files that use them. Thus, you should put the definitions in header files if more than one source file uses them, or use explicit instantiations as discussed earlier in this chapter.

Template parameters of function templates can have defaults, just like class templates.

Function Template Specialization

Just as you can specialize class templates, you can specialize function templates. For example, you might want to write a Find() function for const char* C-style strings that compares them with strcmp() instead of operator==. Here is a specialization of the Find() function to do this:

template<>
size_t Find<const char*>(const char* const& value,
    const char* const* arr, size_t size)
{
    for (size_t i = 0; i < size; i++) {
        if (strcmp(arr[i], value) == 0) {
            return i; // Found it; return the index
        }
    }
    return NOT_FOUND; // Failed to find it; return NOT_FOUND
}

You can omit the <const char*> in the function name when the parameter type can be deduced from the arguments, making your prototype look like this:

template<>
size_t Find(const char* const& value, const char* const* arr, size_t size)

However, the deduction rules are tricky when you involve overloading as well (see the next section), so, in order to avoid mistakes, it’s better to note the type explicitly.

Although the specialized find() function could take just const char* instead of const char* const& as its first parameter, it’s best to keep the arguments parallel to the non-specialized version of the function for the deduction rules to function properly.

You can use this specialization as follows:

const char* word = "two";
const char* words[] = {"one", "two", "three", "four"};
const size_t sizeWords = std::size(words);
size_t res;
// Calls const char* specialization
res = Find<const char*>(word, words, sizeWords);
// Calls const char* specialization
res = Find(word, words, sizeWords);

Function Template Overloading

You can also overload function templates with non-template functions. For example, instead of writing a Find() template specialization for const char*, you could write a non-template Find() function that works on const char*s:

size_t Find(const char* const& value, const char* const* arr, size_t size)
{
    for (size_t i = 0; i < size; i++) {
        if (strcmp(arr[i], value) == 0) {
            return i; // Found it; return the index
        }
    }
    return NOT_FOUND; // Failed to find it; return NOT_FOUND
}

This function is identical in behavior to the specialized version in the previous section. However, the rules for when it is called are different:

const char* word = "two";
const char* words[] = {"one", "two", "three", "four"};
const size_t sizeWords = std::size(words);
size_t res;
// Calls template with T=const char*
res = Find<const char*>(word, words, sizeWords);
res = Find(word, words, sizeWords);   // Calls non-template function!

Thus, if you want your function to work both when const char* is explicitly specified and via deduction when it is not, you should write a specialized template version instead of a non-template, overloaded version.

Function Template Overloading and Specialization Together

It’s possible to write both a specialized Find() template for const char*s and a stand-alone Find() function for const char*s. The compiler always prefers the non-template function to a templatized version. However, if you specify the template instantiation explicitly, the compiler will be forced to use the template version:

const char* word = "two";
const char* words[] = {"one", "two", "three", "four"};
const size_t sizeWords = std::size(words);
size_t res;
// Calls const char* specialization of the template
res = Find<const char*>(word, words, sizeWords);
res = Find(word, words, sizeWords); // Calls the Find non-template function

Friend Function Templates of Class Templates

Function templates are useful when you want to overload operators in a class template. For example, you might want to overload the addition operator (operator+) for the Grid class template to be able to add two grids together. The result will be a Grid with the same size as the smallest Grid of the two operands. Corresponding cells are only added together if both cells contain an actual value. Suppose you want to make your operator+ a stand-alone function template. The definition, which should go directly in Grid.h, looks as follows:

template <typename T>
Grid<T> operator+(const Grid<T>& lhs, const Grid<T>& rhs)
{
    size_t minWidth = std::min(lhs.getWidth(), rhs.getWidth());
    size_t minHeight = std::min(lhs.getHeight(), rhs.getHeight());

    Grid<T> result(minWidth, minHeight);
    for (size_t y = 0; y < minHeight; ++y) {
        for (size_t x = 0; x < minWidth; ++x) {
            const auto& leftElement = lhs.mCells[x][y];
            const auto& rightElement = rhs.mCells[x][y];
            if (leftElement.has_value() && rightElement.has_value())
                result.at(x, y) = leftElement.value() + rightElement.value();
        }
    }
    return result;
}

To query whether an std::optional contains an actual value, you use the has_value() method, while value() is used to retrieve this value.

This function template works on any Grid, as long as there is an addition operator for the type of elements stored in the grid. The only problem with this implementation is that it accesses private member mCells of the Grid class. The obvious solution is to use the public at() method, but let’s see how you can make a function template a friend of a class template. For this example, you can make the operator a friend of the Grid class. However, both the Grid class and the operator+ are templates. What you really want is for each instantiation of operator+ for a particular type T to be a friend of the Grid template instantiation for that type. The syntax looks like this:

// Forward declare Grid template.
template <typename T> class Grid;

// Prototype for templatized operator+.
template<typename T>
Grid<T> operator+(const Grid<T>& lhs, const Grid<T>& rhs);

template <typename T>
class Grid
{
    public:
         // Omitted for brevity
        friend Grid<T> operator+ <T>(const Grid<T>& lhs, const Grid<T>& rhs);
        // Omitted for brevity
};

This friend declaration is tricky: you’re saying that, for an instance of the template with type T, the T instantiation of operator+ is a friend. In other words, there is a one-to-one mapping of friends between the class instantiations and the function instantiations. Note particularly the explicit template specification <T> on operator+ (the space after operator+ is optional). This syntax tells the compiler that operator+ is itself a template.

More on Template Parameter Deduction

The compiler deduces the type of the template parameters based on the arguments passed to the function template. Template parameters that cannot be deduced have to be specified explicitly.

For example, the following add() function template requires three template parameters: the type of the return value, and the types of the two operands:

template<typename RetType, typename T1, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }

You can call this function template specifying all three parameters as follows:

auto result = add<long long, int, int>(1, 2);

However, because the template parameters T1 and T2 are parameters to the function, the compiler can deduce those two parameters, so you can call add() by only specifying the type for the return value:

auto result = add<long long>(1, 2);

Of course, this only works when the parameters to deduce are last in the list of parameters. Suppose the function template is defined as follows:

template<typename T1, typename RetType, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }

You have to specify RetType, because the compiler cannot deduce that type. However, because RetType is the second parameter, you have to explicitly specify T1 as well:

auto result = add<int, long long>(1, 2);

You can also provide a default value for the return type template parameter so that you can call add() without specifying any types:

template<typename RetType = long long, typename T1, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }
...
auto result = add(1, 2);

Return Type of Function Templates

Continuing the example of the add() function template, wouldn’t it be nice to let the compiler deduce the type of the return value? It would; however, the return type depends on the template type parameters, so how can you do this? For example, take the following templatized function:

template<typename T1, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }

In this example, RetType should be the type of the expression t1+t2, but you don’t know this because you don’t know what T1 and T2 are.

As discussed in Chapter 1, since C++14 you can ask the compiler to automatically deduce the function return type. So, you can simply write add() as follows:

template<typename T1, typename T2>
auto add(const T1& t1, const T2& t2)
{
    return t1 + t2;
}

However, using auto to deduce the type of an expression strips away reference and const qualifiers; decltype does not strip those. Before continuing with the add() function template, let’s look at the differences between auto and decltype using a non-template example. Suppose you have the following function:

const std::string message = "Test";

const std::string& getString()
{
    return message;
}

You can call getString() and store the result in a variable with the type specified as auto as follows:

auto s1 = getString();

Because auto strips reference and const qualifiers, s1 is of type string, and thus a copy is made. If you want a const reference, you can explicitly make it a reference and mark it const as follows:

const auto& s2 = getString();

An alternative solution is to use decltype, which does not strip anything:

decltype(getString()) s3 = getString();

In this case, s3 is of type const string&; however, there is code duplication because you need to specify getString() twice, which can be cumbersome when getString() is a more complicated expression.

This is solved with decltype(auto):

decltype(auto) s4 = getString();

s4 is also of type const string&.

So, with this knowledge, we can write our add() function template using decltype(auto) to avoid stripping any const and reference qualifiers:

template<typename T1, typename T2>
decltype(auto) add(const T1& t1, const T2& t2)
{
    return t1 + t2;
}

Before C++14—that is, before function return type deduction and decltype(auto) were supported—the problem was solved using decltype(expression),  introduced with C++11. For example, you would think you could write the following:

template<typename T1, typename T2>
decltype(t1+t2) add(const T1& t1, const T2& t2) { return t1 + t2; }

However, this is wrong. You are using t1 and t2 in the beginning of the prototype line, but these are not yet known. t1 and t2 become known once the semantic analyzer reaches the end of the parameter list.

This problem used to be solved with the alternative function syntax. Note that in this syntax, the return type is specified after the parameter list (trailing return type); thus, the names of the parameters (and their types, and consequently, the type t1+t2) are known:

template<typename T1, typename T2>
auto add(const T1& t1, const T2& t2) -> decltype(t1+t2)
{
    return t1 + t2;
}

However, now that C++ supports auto return type deduction and decltype(auto), it is recommended to use one of these mechanisms, instead of the alternative function syntax.

VARIABLE TEMPLATES

In addition to class templates, class method templates, and function templates, C++14 adds the ability to write variable templates. The syntax is as follows:

template <typename T>
constexpr T pi = T(3.141592653589793238462643383279502884);

This is a variable template for the value of pi. To get pi in a certain type, you use the following syntax:

float piFloat = pi<float>;
long double piLongDouble = pi<long double>;

You will always get the closest value of pi representable in the requested type. Just like other types of templates, variable templates can also be specialized.

SUMMARY

This chapter started a discussion on using templates for generic programming. You saw the syntax on how to write templates and examples where templates are really useful. It explained how to write class templates, how to organize your code in different files, how to use template parameters, and how to templatize methods of a class. It further discussed how to use class template specialization to write special implementations of a template where the template parameters are replaced with specific arguments. The chapter finished with an explanation of function templates and variable templates.

Chapter 22 continues the discussion on templates with some more advanced features such as variadic templates and metaprogramming.