Templates in C++ primer (1)

Types of template

There are three different types of templated entity in C++, they are:

  • Class templates
  • Function templates
  • Variable templates

Of these, class templates are the most flexible as they can optionally be both fully or partially specialized. Function templates (including member function templates) can be optionally (only) fully specialized. A template declaration or definition differs from a normal declaration or definition by being prefixed with: template<parameters...>

Types of template parameters

There are (again) three different types of template parameters in C++, they are:

  • Template type parameters (the most common type, and the only type used with variable templates)
  • Template non-type parameters (only built-in integral value types are allowed)
  • Template template parameters (no, I didn’t mis-type that!)

Each of these are described below.

Template type parameters

When we write std::vector<int> vi; we define a vector that can hold (only) ints. The use of <int> causes the compiler to instantiate a specialization of the generic vector class for the template type parameter of its definition:

template<typename T> // actually a simplification, allocator not shown
class vector {
// ...

Thus, as far as the template definition is concerned, T is a template type parameter. Template type parameters are usually written as (or start with) a capital letter and can be specified with either typename or class (the former being the newer syntax). They can be provided with default type values (template<typename T = int>) which can be overridden at compile time, or be variadic (template<typename... Args>); the latter causes the creation of a parameter pack.

To provide a specialization of a class for a particular type, the following syntax is used:

template<> // the "<>" is necessary
class vector<bool> { // doesn't use generic definition for type "bool"
// ...

Template non-type parameters

By far the most common (built-in) type used with template non-type parameters is size_t, however other integral types such as int or bool can be used as well. The value of these is fixed at compile time so can be used where a constexpr value is required, such as for array dimensions:

template<size_t X, size_t Y>
class Matrix {
    double data[X][Y]; // can be allocated on the stack (as alocal variable of type Matrix)
public:
// ...

Also, template non-type parameters are used in TMP (“Template Metaprogramming”) with the recursion stopping at (most often) a specialization for zero. Here is one version of the factorial function from Mathematics implemented in this way:

// factorial_tmp.cpp : calculate factorial value using TMP

#include <cstdlib>

template<std::size_t N>
struct Factorial {
    static const int value = N * Factorial<N - 1>::value;
};

template<> // specialization must appear after generic declaration or definition
struct Factorial<0> {
    static const int value = 1;
};

static_assert(Factorial<5>::value == 120);

Template template parameters

Say we want to have a function that accesses all of the elements of a Standard container, without knowing whether it will be used with a std::vector or std::list, for example. We might be tempted to write something like this:

template<typename T, typename C> // won't compile
void print(const C<T>& container) {
// ...

What needs to be recognized is that as C‘s type depends upon T, it is itself a template template parameter, and needs to be specified as such (with apologies for the syntax necessary to be used):

template<typename T, template<typename = T, typename = allocator<T>> typename C>
void print(const C<T>& container) {
// ...

As previously the keywords typename and class are interchangeable here. Notice that the exact signature of a Standard container occurs immediately before typename C, this is essential if the code is to compile.

So far so good (the complete function is shown later). Now suppose we only want to access int elements from the container, that is to have a function that is general for container type but specialized for element type. This is possible too:

template<template<typename = int, typename = allocator<int>> typename C>
void print_int(const C<int>& container) {
// ...

Here the template type parameter T is omitted altogether leaving the function with a single template template parameter C. Note that the <int> in the function parameter type is necessary; we have been able to specialize the function for element type while leaving it general for container type.

Here is the complete working program:

// container_types.cpp : print contents of any container with given value_type

#include <vector>
#include <list>
#include <iostream>

using namespace std;

template<typename T, template<typename = T, typename = allocator<T>> typename C>
void print(const C<T>& container) {
    cout << "{ ";
    for (auto separator = ""; const T& element : container) {
        cout << separator << element;
        separator = ", ";
    }
    cout << " }\n";
}

template<template<typename = int, typename = allocator<int>> typename C>
void print_int(const C<int>& container) {
    cout << "{ ";
    for (auto separator = ""; int element : container) {
        cout << separator << element;
        separator = ", ";
    }
    cout << " }\n";
}

int main() {
    vector vi{ 5, 4, 3, 2 ,1 };
    list ld{ 1.1, 2.2, 3.3, 4.4 };
    print(vi);
    print(ld);
    print_int(vi);
    // print_int(ld); // note: does not compile as ld contains doubles
}

The output from running this program is:

{ 5, 4, 3, 2, 1 }
{ 1.1, 2.2, 3.3, 4.4 }
{ 5, 4, 3, 2, 1 }

That’s all for now, in the second article of this mini-series we’ll take a look at use of CRTP and SFINAE.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s