Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

C++ Templates : Apart from Generic Containers and Algorithms

4.93/5 (34 votes)
4 Jul 2014CPOL9 min read 39.6K   624  
This article explains some of the template programming techniques

Introduction

Templates form the basis for generic programming. So, templates are usually used to define generic containers or algorithms that operates on different types. But apart from it, templates can be used in various contexts to solve certain problems. This article tries to cover some of the template programming techniques and idioms, most of them falls in to the metaprogramming category.

Curiously Recurring Template Pattern (CRTP)

Imagine a situation, where you have to create a base class which has several static members; many derived classes inherit the base class, but you want the static members to be unique for each derived class. Curiously Recurring Template Pattern (CRTP) is the solution.

C++
template <class>
class Base // The base class template
{
public:
  static int a;
};
class Derived1 : public Base <Derived1>
{
public:
  Derived1()
  {	
    a = 1; // member of Base<Derived1>
  }
};
int Base<Derived1>::a;
class Derived2 : public Base <Derived2>
{
public:
  Derived2()
  {
    a = 2; // member of Base<Derived2>
  }
};
int Base<Derived2>::a;
int main() 
{
  Derived1 d1;
  Derived2 d2;
  cout << "Base<Derived1>::a = " << Base<Derived1>::a << endl; // 1
  cout << "Base<Derived2>::a = " << Base<Derived2>::a << endl; // 2
  return 0;
}

We know that template instantiation is class creation. In this example, each derived class is derived from each instantiation of the class template "Base".

Template Instantiation for Use Without Any Instance

You can also instantiate classes with static members to operate on different classes. For example, consider a class which maps enum items to strings. Here is a without-template version:

C++
// Two enums
enum Animals 
{
  Dog, Cat, Lion, Tiger
};
enum Directions
{
  North, South, East, West
};
class EnumMap
{
private: 
  EnumMap() // Make sure no instance can be created from this class
  {}
public:
  static map <int, string> m_Map;
};
// Definitions
map <int, string> EnumMap::m_Map; 

int main()
{ 
  EnumMap::m_Map[Dog] = "Dog is a pet animal";
  EnumMap::m_Map[North] = "North is a direction";
  cout << EnumMap::m_Map[Dog] << endl; // Output is "North is a direction"
  // This is because both Dog and North have the same integral value 0
  return 0;
}

The above program doesn't produce the desired output.  Templates can solve this problem.

C++
// Two enums
enum Animals 
{
  Dog, Cat, Lion, Tiger
};
enum Directions
{
  North, South, East, West
};
template <typename>
class EnumMap
{
private: 
  EnumMap() // Make sure no instance can be created from this class
  {}
public:
  static map <int, string> m_Map;
};
// Two definitions because they actually belong different classes.
// Instantiations take place here. That is, two new types are created here.
map <int, string> EnumMap<Animals>::m_Map; 
map <int, string> EnumMap<Directions>::m_Map; 

// For convenience and readability.
typedef EnumMap<Animals> AnimalsMap;
typedef EnumMap<Directions> DirectionsMap;

int main()
{ 
  AnimalsMap::m_Map[Dog] = "Dog is a pet animal";
  DirectionsMap::m_Map[North] = "North is a direction";
  cout << AnimalsMap::m_Map[Dog] << endl; // Output is "Dog is a pet animal"
  return 0;
}

In this code, the class template EnumMap is instantiated for each enum type. Though enums are integral, according to the compiler they are actually different types.

Traits

Traits are classes which encapsulate properties of different types. The following is a simple traits type:

C++
template <typename>
struct is_int
{
   enum { value = false };
};
template <>
struct is_int <int> // Specialization for the type "int"
{
   enum { value = true };
};
int main()
{
   int i = 2;
   char j = '2';
   // C++11 only..
   cout << is_int <decltype(i)>::value << endl; // Prints 1
   cout << is_int <decltype(j)>::value << endl; // Prints 0
   // Not C++11 only..
   cout << is_int <int>::value << endl; // Prints 1
   cout << is_int <char>::value << endl; // Prints 0
   return 0;
}

As you can see, traits are based on the concept of specialization of templates.

Note: You don't have to create type traits for primitive data types. They are already implemented in the STL. Include the standard header "type_traits" to use them.

Nested Class Template Specialization Depending on Template Parameter of the Containing Class

It is possible, that you can specialize a nested class template based on the template parameter of the class which contains it. See the following example which implements a complex way just to compare two integers:

C++
// This code is totally meaningless, but it can be used to demonstrate an example for the above trick..
template <int val1, int val2>
struct EqualityChecker
{
  template <int>
  struct Local
  {
     enum { value = false };
  };
  
  template <>
  struct Local <val1> // Specialization based on "val1"
  {
     enum { value = true };
  };

  enum { value = Local<val2>::value }; // If val1 == val2, Local<val2> refers the 
  // specialization
};
int main()
{ 
  cout << EqualityChecker<3, 3>::value << endl; // Prints 1
  cout << EqualityChecker<5, 3>::value << endl; // Prints 0
  return 0;
}

Visualize the instantiation of the template EqualityChecker<int, int=""> when the parameters are 3 and 3.

C++
// This is a visualization of an instantiation
struct EqualityChecker_3_3 // val1 = 3, val2 = 3 so the suffix "_3_3"
{
  template <int>
  struct Local
  {
     enum { value = false };
  };
  
  template <>
  struct Local <3> // val1 when the parameters are 3, 3
  {
     enum { value = true };
  };

  enum { value = Local<3>::value }; // val2 when the parameters are 3, 3
};

This is how the compiler instantiates EqualityChecker when we pass 3 and 3. You can see the how Local is specialized.

Substitution Failure Is Not An Error (SFINAE)

This occurs during overload resolution (when it resolves overloads) of function templates. When substituting the deduced type for the template parameter fails, the compiler won't stop with a compilation error. Instead, that specific specialization is discarded and the compiler skips to the next.

C++
struct Example
{
  typedef int type;
};
 
template <typename T> 
void Func(typename T::type t) // There is no int::type
{
  cout << "Function 1 is called" << endl;
} 
 
template <typename T>
void Func(T y) 
{
  cout << "Function 2 is called" << endl;
}          
int main() 
{
  Func<int>(10); // Prints "Function 2 is called" 
  return 0;
}

There are two potential overloads that take a single typename parameter. But, the first overload of "Func" is discarded as there is no int::type.

The Enable If Idiom

Using enable if you can let the compiler select a overload based on a compile-time boolean expression. This powerful technique is made possible by the feature of SFINAE. The following is a Enable_if class(we use capital "E" to avoid name clash with std::enable_if):

C++
template <bool, class T = void> 
struct Enable_if { };
 
template <class T>
struct Enable_if<true, T> 
{ 
  typedef T type; 
};

Enable If, like other techniques, is also based on specialization. The specialized version of enable_if with the parameter "true" defines the type; otherwise, no type is defined. See the following example:

C++
// if v is between 0 and 1000, this function is called
template <int v>
typename Enable_if<( v > 0 && v < 1000)>::type PrintNumber()
{
  cout << "(( v > 0 && v < 1000) == true) and v =  " << v << endl;
}
// if v is between 1000 and 2000, this function is called
template <int v>
typename Enable_if<( v > 1000 && v < 2000)>::type PrintNumber()
{
  cout << "(( v > 1000 && v < 2000) == true) and v =  " << v << endl;
}
// if v doesn't obey both the conditions, compilation error.
int main() 
{
  PrintNumber<500>(); // Calls #1.
  PrintNumber<1020>(); // Calls #2.
  return 0;
}

Here, there are two matching overloads. So the compiler goes to the first one and sees the Enable_if. We already know that the Enable_if won't contain "type" unless the boolean expression evaluates to true. So, if the expression evaluates to false, the compiler discards the overload and goes to the next (SFINAE). When the expression evaluates to true, it is already known that Enable_if's specialization for true has "type", so the function will get successfully compiled.

Note: If "v" doesn't obey both the conditions, a compilation error will occurs. Also, you can use std::enable_if instead of this implementation. The usage method is exactly the same, the above implementation is just to show how enable if works.

Parameterized Base Class Idiom - Inheriting from T

This is a technique in which the instantiation created out of a template is inherited from the parameter itself.

C++
class Base
{
public:
  int a;
};
template <typename T>
class DerivedClass : public T
{
};
int main()
{
  DerivedClass<Base> test;
  test.a = 3;
  return 0;
}

The above code is self-explanatory. Here, DerivedClass<Base> is derived from Base.

Combining Parameterized Base Class and Enable if idioms

What if you want to make sure that your class is only inherited from a certain set of classes. The solution is simple - use Enable if. Example:

C++
// This program shows how to prevent the class from inheriting from any "unsupported" classes.
template <typename>
struct isSupportedClass
{
  enum { value = false };
};
// MACRO to add a supported class
#define ADD_SUPP_CLASS(x) template <> struct isSupportedClass<x> { enum { value = true }; };

// DerivedClass only will accept these three types as params
class SupportedClass1 {};
class SupportedClass2 {};
class SupportedClass3 {};

// Qualify the above classes as Supported Classes
ADD_SUPP_CLASS(SupportedClass1);
ADD_SUPP_CLASS(SupportedClass2);
ADD_SUPP_CLASS(SupportedClass3);

template <typename T>
// Make sure that this class is inherited only from one of the supported classes
class DerivedClass : public Enable_if<isSupportedClass<T>::value, T>::type
{};

class UnsupportedClass {};

int main()
{
  DerivedClass<SupportedClass1> test; // OK
  DerivedClass<UnsupportedClass> test2; // Produces a compiler error as UnsupportedClass is  not supported
  return 0;
}

As you can see, we have passed a boolean expression which checks whether "T" is a supported class using those specializations. Only if the condition is true, the compile will be successful.

Template Metaprogramming

What is it?

Template metaprogramming is a powerful technique used to write metaprograms using templates. C++ templates are a Turing complete language that means, everything that is computable is computable using C++ templates. While processing templates, the C++ compiler acts as an interpreter, or more like a calculator.
Template metaprogramming actually owes its origin to 1994. Surprisingly, no one knew about the feature until it was discovered.

How it Works

Template metaprogramming is made possible because of the following:

  • Non-type template parameters. Example:
    C++
    template <int i>
    struct Example {
      enum { sqr = i * i }; 
    };
    What can be a non-type parameter?
    The C++ Standard Says[14.1 - Template parameters]:
    "A non-type template-parameter shall have one of the following (optionally cv-qualified) types:
    — integral or enumeration type,
    — pointer to object or pointer to function
    — reference to object or reference to function,
    — pointer to member."
     
  • Expression parsing done by the compiler while creating a template instantiation. Example:
    C++
    cout << Example <(2 + 2) * 3>::sqr;
    As you can see, in the above code, the expression is actually calculated not in the runtime but compile time.
    What actually happens here? Firstly, the compiler creates instantiates the Example template and creates a new class named Example <12> after parsing "(2 + 2) * 3". Imagine how Example <12> looks like :
    C++
    // This is a visualization of an instantiation
    struct Example_12 
    {
      enum { sqr = 144 }; 
    };
  • Allowing recursion in static members and enum initialization.
    template <int i>
    class Example
    {
      enum { value = Example<i+1>::value };
    };
    // Don't try this. This is an infinite compile-time recursion. 
    By default, the MSVC compiler allows up to 500 recursion, whereas the GCC goes up to the depth of 900 recursion.

An Example

C++
template <int n>
struct factorial
{
  enum { value = n * factorial<n-1>::value };
};
 
template <>
struct factorial<0>
{
  enum { value = 1 };
};

int main()
{
  cout << factorial<5>::value; // 120.
  return 0;
}

In the above program, the result is calculated during compile-time by the compiler.
In the assembly file generated out of this code you can find,

; Note : this is actually a very small part of the file. The total file is around 150 KB.
mov	esi, esp
push	120	
mov	ecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
call	DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z

Surprisingly, the code just pushes 120 and calls "cout" - no recursive functions, not even the word "factorial" is in the assembly file. This is because, the factorial is calculated by the compiler.

Why Template Metaprogramming?

Always, run-time computations make your program slower than compile-time computations. But at most places, if not all, compile-time computations is not of that use. Take for instance, you can't do anything with the above factorial sample during runtime. But there are also places where compile-time computations improves readability,

C++
template <unsigned long N>
struct binary
{
  enum { value = binary<N/10>::value << 1 | N%10 };
};
template <>
struct binary<0>
{
  enum { value = 0 };   
};

int main()
{
  // No performance trade-off is in the first method. These both are same.
  cout << binary<1101>::value << endl; // 13.
  cout << 13 << endl; // 13.
  return 0;
}

Using "binary<1101>::value" is more readable than using magic numbers. Also no loss is in performance. But if you implement this algorithm without metaprogramming, your program WILL lose performance.

Regarding other than compile-time computations, bear in mind that, the compiler's knowledge about your program in compile-time is much more than your program's knowledge about itself during run-time. This makes metaprogramming much safer.

Test of Convertibility

How can you check whether a type can be implicitly converted to another? The following trick can do it:

C++
// Test of Convertibility
// Check whether T implicitly converts to U
template<class T, class U>
class TestConversion
{
  typedef char Small; // Define Small type
  class Big { char dummy[2]; }; // Big is twice larger than Small
  
  static Small Test(U); // Accepts an object of type "U"
  static Big Test(...); // Variable Argument Lists. Accepts "anything". Here the logic resides.
  // When Test is called with a T object as a argument,
  // --> "Small Test(U)" is called when T implicitly converts to U.
  // --> Otherwise the second overload is called.
  
  static T MakeT(); // T may have private constructors
  // No functions here have a function body. 
public:
  // If T implicitly converts to U, then the value will be true 
  enum 
  {
    value = sizeof(Test(MakeT())) == sizeof(Small) 
  };
};

// Two classes
class Base {};
class Derived : public Base {};

int main()
{
   cout << TestConversion<int, char *>::value << endl; // 0
   cout << TestConversion<Derived, Base>::value << endl; // 1
   cout << TestConversion<Base, Derived>::value << endl; // 0
   // This means, Derived can be implicitly converted to Base but not vice versa.
   return 0;
}

In the class template "TestConversion", "value" is determined based on the template parameters. As you can see, the class compares the size of the "return types" of the two overloads. The class defines two typedefs Small and Big - both are intentionally of different sizes.
See here,

sizeof(Test(MakeT())) == sizeof(Small) 

In the left hand side, the "sizeof" operator is used. There MakeT() which returns a "T" is called. Compiler knows it - MakeT() is not executed.
If T implicitly converts to U, the first overload is selected and the size of "Test" will become the size of "Small" which is the value of RHS, so "value" evaluates to true.
See the following visualization of TestConversion<Derived, Base>:

C++
// This is a visualization of an instantiation
class TestConversion_Derived_Base // T = Derived; U = Base
{
  typedef char Small;
  class Big { char dummy[2]; };
  
  static Small Test(Base); // This overload is called because Derived can implicitly convert to Base 
  static Big Test(...); 
  
  static Derived MakeT();  
public:
  enum 
  {
    value = sizeof(Test(MakeT())) == sizeof(Small) // TRUE
  };
};

This is the way it is instantiated.

Typelists

Typelists are a way to create compile-time lists of type names. The following is a typelist:

C++
// The type to determine the end of the list
struct NullType {};

// The list class
template <typename val, typename next>
struct TypeList
{
  typedef val Val; // The value of the current item
  typedef next Next; // Next item
};

// Some Macros
#define TYPELIST_1(T1) TypeList<T1, NullType>
#define TYPELIST_2(T1, T2) TypeList<T1, TYPELIST_1(T2)>
#define TYPELIST_3(T1, T2, T3) TypeList<T1, TYPELIST_2(T2, T3)>
#define TYPELIST_4(T1, T2, T3, T4) TypeList<T1, TYPELIST_3(T2, T3, T4)>
#define TYPELIST_5(T1, T2, T3, T4, T5) TypeList<T1, TYPELIST_4(T2, T3, T4, T5)>

// Gets a type at specified index
template <typename TLIST, int pos> struct GetAt {};

template <typename HEAD, typename TAIL, int pos>
struct GetAt<TypeList<HEAD, TAIL>, pos>
{
  typedef typename GetAt<TAIL, pos-1>::type type;
};

template <typename T, typename TAIL> 
struct GetAt<TypeList<T, TAIL>, 0>
{
  typedef T type;
};

template <int pos> 
struct GetAt<NullType, pos> {};

// Check whether the list has the specified index
template <typename list, typename item>
struct HasItem { enum { value = false }; };

template <typename h, typename t, typename item>
struct HasItem<TypeList<h, t>, item>
{
  enum { value = HasItem<t, item>::value };
};

template <typename h, typename t>
struct HasItem<TypeList<h, t>, h>
{
  enum { value = true };
};

class Class1 {};
class Class2 {};

// Define our list. Typelists are defined using typedefs.
typedef TYPELIST_4(int, float, char, Class1) MyList;

int main()
{
  // Cast 97.03 in to each of the types in our typelist
  cout << (GetAt<MyList, 0>::type)(97.03) << endl; // 97 - int
  cout << (GetAt<MyList, 1>::type)(97.03) << endl; // 97.03 - float
  cout << (GetAt<MyList, 2>::type)(97.03) << endl << endl; // a - char
  cout << HasItem<MyList, Class1>::value << endl; // 1
  cout << HasItem<MyList, Class2>::value << endl; // 0
  return 0;
}

Typelists works like linked lists, but typelists are compile-time.

Image 1


GetAt gets the item at the specified index.
HasItem checks whether an item exists.

Combining Parameterized Base Class idiom, Typelists and Test of Convertibility

We have seen a class which restricts the template parameter to certain set of classes. Here is a equivalent of the same program using Typelists and TestConversion. Sometimes you may want to enable the user create classes that are supported.

C++
class SupportedClass1 {};
class SupportedClass2 {};
class SupportedClass3 {};

class SupportedBase {}; // "New" Classes should inherit this to be "supported"


typedef TYPELIST_3(SupportedClass1, SupportedClass2, SupportedClass3) SupportedClasses;

template <typename T>
class DerivedClass : public Enable_if<// Check whether it is in the "SupportedClasses" list
                                      (HasItem<SupportedClasses, T>::value || // or
                                      // It derives from SupportedBase
                                      TestConversion<T, SupportedBase>::value), 
                                      T
                                      >::type
{};

// Though SupportedClass4 is not in the type list, it is supported because, it derives from SupportedBase
class SupportedClass4 : public SupportedBase {};
class UnsupportedClass {};

int main()
{
   DerivedClass<SupportedClass1> a;   
   DerivedClass<SupportedClass2> b;  
   DerivedClass<SupportedClass3> c;  
   DerivedClass<SupportedClass4> d; 
   DerivedClass<UnsupportedClass> e; // Error
   return 0;
}

In this example, the "DerivedClass" accepts a class as a parameter if:

  • It is one of the classes in the typelist (or)
  • It derives from SupportedBase.

This example removes all those specializations and macros from the previous example and replaces them with typelists.

A Problem in the Above Program

The DerivedClass in the above program accepts a class if it can implicitly convert to SupportedBase. But what will happen if the class didn't derive from SupportedBase and it implements a "operator SupportedBase()"? DerivedClass will get cheated. Detecting whether a class derives from SupportedBase precisely may fix the problem.
The following class tests whether T is derived from X:

C++
// The following code is only supported in C++11.
// Condition: X should define a function "void testFunction()".

// This helper function returns an object of type T
template<class T, class R>
T base_of(R T::*);

template <typename T, typename X>
struct IsDerivedFromX // Roughly similar to TestConversion
{
  typedef char Small;
  class Big { char dummy[2]; }; // Big is twice larger than Small. Again.

  static Small test(X);
  static Big test(T); // If the arguments here is "..." the compiler will try to convert
  // T into X using the conversion routines and calls the first overload,
  // SO the second overload takes "T".

  // See "value" below to see how it works.
  // Note: If T is not derived from X, a compilation error will occur, stating that,
  // testFunction is not a member of T. If the error doesn't appear and 
  static decltype(base_of(&T::testFunction)) MakeBase();
  // The type of MakeBase() may vary

  enum { value = (sizeof(test(MakeBase())) == sizeof(Small)) };
  // The Size of the above MakeBase is passed to the test.
  // We know that there are two overloads of test - one taking "T", another takes "X".
  // "test" which returns Small takes X.
  // "test" which returns Big takes T.
	
  // Important: If T is not derived from X and if value evaluates to false(0),
  // that means T has intentionally defined a function named testFunction, without deriving 
  // from X.
   
	
  // Another Important Note: T should not have a function named testFunction.
};

Use it in the previous code:

C++
class SupportedBase  // "New" Classes should inherit this to be "supported"
{
public:
  void testFunction(); // This function helps us to check whether SupportedBase is the base
}; 

template <typename T>
class DerivedClass : public Enable_if<// Check whether it derives from SupportedBase
                                       IsDerivedFromX<T, SupportedBase>::value, 
                                       T
                                     >::type
{};

class SupportedClass4 : public SupportedBase {};

class UnsupportedClass // Though this class can implicitly convert to SupportedBase, 
// this class remains unsupported
{
public:
  operator SupportedBase()
  {
    return SupportedBase();
  }
};
int main()
{
  DerivedClass<SupportedClass4> a;
  DerivedClass<UnsupportedClass> e; // Error
  // DerivedClass only accepts a class, if the class is derived from SupportedBase.
  return 0;
}

Here, the typelists and the TestConversion are removed. Instead the checking is done using IsDerivedFromX class.

Disadvantages of Templates

"Be careful about the complexity of the templates you write or use; it is easy to get overenthusiastic and write template code that is too clever to be useful as maintainable production code." - Bjarne Stroustrup

Be cautious about when and why to use templates. I also like to mention some disadvantages of using templates.

Code Bloat. This is one of the most frequently regarded disadvantages of templates. It is already said that, generic programming is a technique of writing algorithms that can be used with any type. That being said, We know what is instantiation. But what happens when any template, let it be class of function, is instantiated? Simple, a new function or class is created. Consider the following code:

C++
template <typename> class MyClass {}; // This is not a class, this is a class template.

int main()
{
  MyClass<int> a; // +1.
  vector<MyClass<char>> b; // +2.
  vector<MyClass<MyClass<float>>> c; // +3.
  vector<MyClass<MyClass<MyClass<char *>>>> d; // +4.
  return 0;
}

In this very small program, 10 new classes are created.

Slow Compilation Speeds. Templates, especially metaprogramming can easily contribute to slow compile speeds, as it is obvious that the calculations are done in the compile-time. Given the strength of today's CPUs and RAM, this may not be a real issue. But what if your project has about 1000000 lines of codes. 

Incomprehensible Error Messages. Some C++ compilers fail to define template errors precisely. This makes finding the error difficult. A very simple error may produce some 100 lines of error messages. The following image shows a typical error message from the GCC compiler:

Image 2


Member function templates can't be virtual(!). You can't make any function templates that are members of classes virtual. For example,

C++
class Class
{
public:
  template <typename>
  virtual void Function1()
  {
  
  }
};

This code will produce a compilation error stating that,

error C2898: 'void Class::Function1(void)' : member function templates cannot be virtual

Conclusion

This article has explained some template techniques and idioms. These techniques show the uses of templates other than in generics algorithms and container classes. Though, what I have presented here is very few; there are always more than these. You can also have a look at the Boost C++ libraries.

Bibliography

Modern C++ Design: Generic Programming and Design Patterns Applied - by Andrei Alexandrescu

C++ Templates: The Complete Guide - by David Vandevoorde and Nicolai M. Josuttis

C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond - By David Abrahams and Aleksey Gurtovoy

Online

Boost Website

A gentle introduction to Template Metaprogramming with C++

TMPP: Template Metaprogramming Programming

History

30/6/2013 - Initial Post

4/7/2013 - Corrected some typos

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)