Advanced C++

Template techniques

Máté Cserép

April 2021, Budapest

Overview

  • From macros to templates
  • Parameter deduction, instantiation, specialization
  • Class templates, user-specialization
  • Template parameters, default parameters
  • Dependent types
  • Two Phase Lookup: differences from non-templates
  • Mixins, mixin usage
  • Curiously Recurring Template Pattern (CRTP),
    static polymorphism
  • Explicit template instantiations
  • Extern templates
  • Variadic templates
  • Template metaprogramming

Motivation

Independent concepts should be independently represented and should be combined only when needed. Otherwise unrelated concepts are bundled together or unnecessary dependencies are created.

Templates provide a simple way to represent a wide range of general concepts and simple ways to combine them.

A standard library requires a greater degree of generality, flexibility and efficiency.

 

Bjarne Stroustrup

Overloading

int max( int a, int b)
{
    if ( a > b )
        return a;
    else
        return b;
}
double max( double a, double b)
{
    if ( a > b )
        return a;
    else
        return b;
}

How many overloads should we have to create?

What about the enormous task of maintaining them?

Preprocessor macro

#define MAX(a,b)    a > b ? a : b

MAX(x, y) * 2    // x > y ? x : y * 2

C/C++ macros are typeless and not processed by the C++ compiler, therefore there are a number of "secondary effect".

There is no correct solution for a MAX function:

#define MAX(a,b)    ((a) > (b) ? (a) : (b))

MAX(++x, y)      // ((++x) > (y) ? (++x) : (y))

Since macros are typless, what should be the type of tmp?

#define SWAP(x,y) \
??? temp = x; \
x = y; \
y = temp

Introducing templates

template <class T>
T max( T a, T b)
{
    return a > b ? a : b;
}
template <typename T>
void swap(T& x, T& y)
{
    T temp = x;
    x = y;
    y = temp;
}

We need a facility to use type parameters.

Template depends only on the properties that is actually uses from its parameter types and does not require different types used as arguments to be explicitly related.

A template is not a single function! It is rather a schema. The process
of generating a concrete function or class declaration from a template and a template argument is often called template instantiation. A version of a template for a particular argument is called a specialization.

Template instantiation, parameter deduction

template <class T>
T max( T a, T b)
{
    return a > b ? a : b;
}
int       i = 3,    j = 4,    k;
double    x = 3.14, y = 4.14, z;
const int ci = 6;

k = max(i, j);     // -> max(int, int)
z = max(x, y);     // -> max(double, double)
k = max(i, ci);    // -> max(int, int), with trivial conversion
z = max(i, x);     // -> max(int, double) is ambiguous, no standard conversion

The instantiation is - in most cases - an automatic process.

Explicit specialization

z = max<double>(i, x);     // -> max(int, double), convert int to double

Template specialization

const char *s1 = "Hello";
const char *s2 = "world";

cout << max(s1, s2);       // Requires different definition

The general max template function does not apply to char pointers:

Specialize the definition of a template for a given parametrization:

template <>
const char* max<const char*>( const char *s1, const char *s2)
{
    return strcmp(s1, s2) < 0 ? s1 : s2;
}

Template classes

matrix<int>  m(10,20);
m.at(2,3) = 1;
std::cout << m(2,3) << std::endl;

Class template parameters regularly cannot be deduced from constructor parameters, objects from template classes must be explicitly specialized.

template <class T>
class matrix
{
   int r, c;
   T* items;
   // ... methods
};
std::pair p(2, 4.5)            // std::pair<int,double>
std::vector v = { 1, 2, 3, 4}; // std::vector<int>

Class template deduction is possible since C++17 if the compiler can deduce from the constructor arguments, e.g.:

Template codeblow

Consider all the member functions are template functions for a template class. Even those are templates, where there is no explicit use of parameter T.

// even this is a template
template <class T>
int matrix<T>::rows() const { return r; }
class matrixBase
{
   int r, c;
   // methods not dependant on T ...
};

A generally good technique to cut off the type-independent part of the class from those dependent from the parameter. Inheritance is a good solution here:

template <class T>
class matrix : public matrixBase
{
   T* items;
   // methods dependant on T ...
};

Template class specialization

template <class T>
class vector
{
    //
    // general implementation
    //
};

Partial specialization

template <class T, class V>
class A
{
    // general implementation
};
template <>
class vector<bool>
{
    //
    // specific implementation
    //
};
template <class V>
class A<B, V>
{
    // specific implementation for B
};

Default template parameter

template <class T, class C = std::deque<T> >
class std::stack
{
    /* ... */
};

It is possible to define default template arguments. They may even refer to previous template parameters:

Dependent name

template <typename T>
class MyClass
{
   typename T::SubType * ptr;
   /* ... */
};

The keyword typename was introduced into C++ during the standardization to clarify that an identifier inside a template is a type (or not).

Without the typename, the name SubType would be considered as a static member. Thus it would be a concrete variable or object. As a result, the expression would be a multiplication.

In general, typename has to be used whenever a name depends on a template type.

typename T::const_iterator pos;

Two Phase Lookup

void bar() {
  std::cout << "::bar()" << std::endl;
}

template <typename T>
class Base
{
public:
    void bar() { std::cout << "Base::bar()" << std::endl; }
};

template <typename T>
class Derived : public Base<T>
{
public:
    void foo() { bar(); }       // calls external bar() or error !!
};

int main() {
    Derived<int> d;
    d.foo();
    return 0;
}

For class templates with base classes, using a name x by itself is not always equivalent to this->x, even though a member x is inherited:

Two Phase Lookup

template <typename T>
class Derived : public Base<T>
{
public:
    void foo() { this->bar(); }       // calls Base::bar()
};

Templates are compiled (at least) twice:

  1. Without instantiation the template code itself is checked for syntax. E.g.: syntax errors errors.
  2. At the time of instantiation (when the exact type is known), the template code is checked again to ensure all calls are valid for that particular type.
    E.g.: The template might in turn call to functions which might not be present for that particular type.

Template template parameters

std::stack<int, std::vector<int> > vstack;

It can be useful to allow a template parameter itself to be a class template in order to simply the following type:

It would be nice to write it in the following form:

std::stack<int, std::vector> vstack;

To provide it, let us define the stack in the following way:

template <typename T,
   template <typename ITEM> class CONT = std::deque> // Naming ITEM can be omitted
class stack
{
protected:
        CONT<T> c;
        /* ... */
};

Explicit call of default constructor

The fundamental types, like int, double, etc. has no default constructor. In order to ensure initialization of such values as template parameters, we can use the explicit call of constructor.

template <typename T>
void foo()
{
    T x = T();
    /* ... */
}

To make sure, that this happens with the sub-objects to, you should use the initializer list of the constructor:

template <typename T>
class MyClass
{
public:
    MyClass() : x() {}
private:
    T x;
};

Mixins

class Base { /* ... */ };

template <class T>
class Mixin : public T  { /* ... */ };

Mixin<Base> m;

When a template class is derived from at least one of its template parameter, we call it a mixin, because subtype- and parametric-polymorphism become mixed in.

The interesting thing is, that in normal object-oriented style base is always implemented before derived. Here we can implement inheritance delaying the definition of the base. As a usage, a policy or strategy can be injected.

Mixin usage

class Task
{
public:
  virtual void Execute() = 0;
};
class FactorialTask : public Task
{
public:
  FactorialTask(unsigned int n) : _n(n) { }
  void Execute();

private:
  unsigned int _n;
};
template <class T>
class LoggingTask : public T
{
public:
  LoggingTask(unsigned int n) : T(n) { }
  void Execute();
};

template <class T>
void LoggingTask<T>::Execute()
{
  // Do pre-logging ...
  T::Execute();
  // Do post-logging ...
}
int main()
{
  FactorialTask facTask(20);
  facTask.Execute();

  LoggingTask<FactorialTask> logFacTask(20);
  logFacTask.Execute();
  
  return 0;
}

Liskov substitutional principle

In 1977 Barbara Liskov defined her substitutional principle: an object from the subtype can appear in every place where its supertype's objects can.

That is one of the most fundamental rule in object-orientation: a derived object can appear everywhere the base object can.

Mixins look like subtypes, but they behave differently:

class Base { /* ... */ };
class Derived : public Base { /* ... */ };

template <class T>
class Mixin : public T { /* ... */ };

Base            b;
Derived         d;

Mixin<Base>     mb;
Mixin<Derived>  md;

b  = d          // ok
mb = md;        // syntax error

The Curiously Recurring Template Pattern (CRTP)

template <typename T>
struct Base
{
    /* ... */
};

struct Derived : Base<Derived>
{
    /* ... */
};
class Derived : public std::enable_shared_from_this<Derived>
{
public:
    std::shared_ptr<Derived> getPtr() {
        return shared_from_this();
    }
};

int main()
{
    std::shared_ptr<Derived> p(new Derived);
    std::shared_ptr<Derived> q = p->getPtr();
    assert(p == q);
}

Use case:
enable shared from this

The Curiously Recurring Template Pattern (CRTP)

template <typename T>
struct counter {
   static int objects_created;
   static int objects_alive;
   counter() {
      ++objects_created;
      ++objects_alive;
   }
   counter(const counter&) {
      ++objects_created;
      ++objects_alive;
   }
protected:
   ~counter()
   {
      --objects_alive;
   }
};

template <typename T> int counter<T>::objects_created( 0 );
template <typename T> int counter<T>::objects_alive( 0 );
class X : counter<X> { ... };
class Y : counter<Y> { ... };

Use case: object counter

The Curiously Recurring Template Pattern (CRTP)

template <typename T>
struct Base
{
    /* ... */
};

struct Derived : Base<Derived>
{
    /* ... */
};
template <class T>
struct Base
{
  void interface()
  {
     /* ... */
     static_cast<T*>(this)->implementation();
     /* ... */
  }
};

struct Derived : Base<Derived>
{
  /* ... */
  void implementation();
  /* ... */
};

Use case: Static polymorphism

Implementing the Singleton Pattern
with CRTP

template <class Actual> 
class Singleton
{
public:
  static Actual& getInstance()
  {
    if(p == nullptr)
      p = new Actual;
    return *p; 
  }

protected:
  static Actual* p;

  Singleton() { }
  virtual ~Singleton() { delete p; }
  Singleton(const Singleton<Actual>&) = delete;
  Singleton& operator=(const Singleton<Actual>&) 
    = delete; 
};

template <class Actual>
Actual* Singleton<Actual>::p = nullptr;
class A: public Singleton<A>
{
   // Rest of functionality 
};
int main()
{
  /* ... */
  A& obj1 = A::getInstance();  // ok
  A obj2;                      // bad
  A *obj3 = new A;             // bad
  A obj4 = obj1;               // bad
  /*  ... */
  return 0;
}

What is p?

Variable template (since C++14), must be a constant expression.

Explicit Template Instantiation

Templates are instantiated in a lazy way, only those functions and classes will be generated which are explicitly called / used.

This default rule can be overruled be the explicit instantiation:

template<typename T>
void print(const T& ) { /* ... */ }

template<typename T>
class MyClass
{
  /* ... */
};
template void print<int>(const int &);  // generate function

template MyClass<double>::MyClass();    // generate constructor 

template class MyClass<long>;           // generate the whole class

Extern templates

To avoid multiple instantiations among translation units, in C++11 templates can be declared extern.

The problem:

  • we define the template in header
  • include the header into many source files
    and use and instantiate templates in every source.
  • the linker drops the unnecessary objects.

Result: code bloat. Every object will contain all of the instantiations.

In many cases we would like to place the instantiations into one place, like a shared object, and the client code will only use it.

Extern templates

For ordinary functions we can declare a function:

int foo(int);   // declaration only

However for templates we have had no syntax for that before C++11:

template   int bar(int);   // explicit instantiation

template<> int bar(int);   // explicit specialization

Since then:

extern template class X<int>;   // suppress implicit instantiation

void f( X<int>& xx )
{
    // use X<int>, but not instantiate 
}

Additional advantage: associated types are not instantiated.

Template arguments

In C++98/03 local or unnamed types can not be used as template arguments. In C++11 this restriction was removed:

struct {           // unnamed type
    void operator()(int n) const
    {
        std::cout << n << " ";
    }
} unnamed;

template <typename T>
void f(const std::vector<T>& v)
{
    struct func {  // local type
        void operator()(T n) const
        {
            std::cout << n << " ";
        }
    };
    std::for_each( v.begin(), v.end(), func());  // error in C++98
                                                 // works since C++11
    std::for_each( v.begin(), v.end(), unnamed); // error in C++98
                                                 // works since C++11
    std::for_each( v.begin(), v.end(), [](int n) // lambda also works
                                       {
                                           std::cout << n << " ";
                                       });       //  ok in C++11
}

Variadic templates

template<typename T>
T sum(T v)
{
  return v;
}

template<typename T, typename... Args>
T sum(T first, Args... args)
{
  return first + sum(args...);
}

int main()
{
  long lsum = sum(1, 2, 3, 4, 5);

  std::string s1 = "He", s2 = "llo ", s3 = "Wor", s4 = "ld";
  std::string ssum = sum(s1, s2, s3, s4);

  std::cout << lsum << std::endl << ssum << std::endl;
  return 0;
}

Similarly to variadic parameters of functions, variadic template arguments was introduced in C++11:

Fold expressions

template< typename... Args>
auto sum(Args... args)
{
  return (args + ...);
}

int main()
{
  long lsum = sum(1, 2, 3, 4, 5);

  std::string s1 = "He", s2 = "llo ", s3 = "Wor", s4 = "ld";
  std::string ssum = sum(s1, s2, s3, s4);

  std::cout << lsum << std::endl << ssum << std::endl;
  return 0;
}

In C++17 we can even omit the recursion with fold expressions:

Template metaprogramming

template <int N>
struct Factorial
{
  enum
  {
    value = N * Factorial<N - 1>::value
  };
};
 
template <>
struct Factorial<0>
{
  enum
  {
    value = 1
  };
};
unsigned int factorial(unsigned int n)
{
  return (n == 0)? 1 : n * factorial(n - 1); 
}

int main()
{
  const int x = factorial(0);
  const int y = factorial(10);
  const int z = Factorial<0>::value;
  const int v = Factorial<10>::value;

  cout << x << endl
       << y << endl
       << z << endl
       << v << endl;

  return 0;
}

Template metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by with the rest of the source code and then compiled. The output of these templates include compile-time constants, data structures, and complete functions. The use of templates can be thought of as compile-time execution.

Advanced C++: Template techniques

By Cserép Máté

Advanced C++: Template techniques

  • 151