Introduction

Templates are filled in at compile time. This allows the compiler to generate code for different types. Templates are not limited to types, they can also be used for values and functions.

Instantiation

Templates are implicitly instantiated only when they are used. The code is therefore only generated for the types that are used. Template instantiation is repeated for each compilation unit (the compiler instantiates each template for every unit). The linker will then remove duplicate code. Explicit instantiation can lower compile time (mostly only useful for big projects).

Templates are also called compile-time polymorphism.

Types of Templates

Function Templates

Syntax: template<TemplateParamList> FunctionDefinition

  • TemplateParamList is a comma-separated list of template parameters. Valid template parameters are:
    • typename TypeIdentifier
    • class TypeIdentifier
    • template<TemplateParamList>
    • Value
  • TypeIdentifier is a type name. Either a basic type or a class.
  • FunctionDefinition is the function definition with the template parameters.

Class Templates

Syntax: template<TemplateParamList> ClassDefinition

  • TemplateParamList is a comma-separated list of template parameters. Valid template parameters are:
    • typename TypeIdentifier
    • class TypeIdentifier
    • template<TemplateParamList>
    • Value
  • TypeIdentifier is a type name. Either a basic type or a class.
  • ClassDefinition is the class definition with the template parameters.

Member Templates

Function templates that are applied to instance methods of a class.

Template Specialization

Specialization is used to provide a different implementation for a specific type.

  template<typename T>
T min(T a, T b) {
    return a < b ? a : b;
}
// Specialization must follow the template
template<>
char min<char>(char a, char b) {
    return tolower(a) < tolower(b) ? a : b;
}
  

Partial Specialization

Partial specialization is used to provide a different implementation for a specific type, but not all template parameters.

Instanciation of a partial specialization is done by pattern matching. The most specialized template found is used.

  template<typename T, class C> class MyClass {};
template<class C> class MyClass<char, C> {};
  

Specialization vs Overloading

Overloaded functions are always a better match than the specialization. Specialization is only used if no overloaded function is a better match.

  template<typename T> void foo(T x) { std::cout << "Generic" << std::endl; }
template<> void foo(int* x) { std::cout << "Specialization" << std::endl; }
template<typename T> void foo(T* x) { std::cout << "Overloaded" << std::endl; }

int main() {
    int i = 5;
    foo(i);  // Generic
    foo(&i); // Overloaded (even though specialization exists)
}
  

Alias Templates

Alias templates are used to define a type alias for a template.

  Array<std::vector<std::pair<std::string>>, 50> myArray;

// Alias template
template<typename Key, typename Value, size_t N>
using AKV = Array<std::pair<Key, Value>, N>;

// Partial
template<typename T> using A50 = Array<T, 50>;
template<size_t N> using Aint = Array<int, N>;

// Full
using AV50 = Array<std::vector<uint64_t>, 50>;
using Aint50 = Aint<50>;
  

Template Templates

Template parameters can be templates themselves if they are declared as such.

  // Only works for classes with two type parameters
template<template<typename, typename> class C, class T, class A>
std::ostream& operator<<(std::ostream& os, const C<T,A>& v) {
    os << '[';
    auto it = v.begin();
    if (it != v.end()) os << *it++;
    for (; it != v.end(); it++) os << ", " << *it;
    os << ']';
    return os;
}
  

Variadic Templates

Functions and classes that require a variable number of arguments of arbitrary types. Uses parameter packs (...).

  template<typename... Ts> class C {};
size_t types = sizeof...(Ts); // Number of parameters

template<typename... Ts> void foo(const Ts&... args) {
    std::cout << sizeof...(args) << std::endl;
}
  
  // first: anchor
template<typename T> void logging(const T& t) {
    std::cout << "[0] " << t << std::endl;
}

template<typename First, typename... Rest>
void logging(const First& first, const Rest&... rest) {
    std::cout << "[" << sizeof...(rest) << "] " << first << ", ";
    logging(rest...); // Unpack parameter pack
}
  

Perfect Forwarding

Forwarding of arguments to another function.

template<typename T> void f(T&& x) {...}: Generic right-value reference (T&&) is called universal reference.

  • x is a l-value reference (T& x) if f is called with a l-value
  • x is a r-value reference (T&& x) if f is called with a r-value

template<typename T> void g(T&& y) { f(y); }

  • inside g: T&& y is always a l-value reference
  • f is called with a l-value reference
  • Problem: f is called with a l-value reference, even if g was called with a r-value reference

Solution: template<typename T> void g(T&& y) { f(std::forward<T>(y)); }

std::forward<T>

Usage

Used for universal references. Forwards l-value references as l-value references and r-value references as r-value references.

Implementation

  • std::forward<T> is converted to std::move if a r-value reference is passed
  • std::forward<T> has no effect if a l-value reference is passed

Sample

Simplified implementation of std::make_unique:

  template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
  

Would also apply to the logging implementation from above.

  std::cout << "[" << sizeof...(std::forward<Rest>(rest)) << "] " << std::forward<First>(first) << ", ";
  

Dependant Typenames

Dependant types are member types that depend on a template parameter. They are accessed with the typename keyword.

  template<typename T>
struct Extrema {
    using type = typename T::value_type; // typename required to tell the compiler that value_type is a type
    type m_min, m_max;
    Extrema(const T& t)
    : m_min(*min_element(begin(t), end(t)))
    , m_max(*max_element(begin(t), end(t))) {}
};

Extrema<std::vector<int>> x({8,3,5,6,1,3});
  

The same result can be achieved with template templates, but this solution is more flexible.

Metaprogramming

SFINAE

SFINAE (Substitution Failure Is Not An Error) is used to remove functions from overload resolution.

Substitution is tried as long as there are options for substitution (pattern matching). If substitution fails, the function is removed from overload resolution. Compilation will never fail because of substitution failure.

Example

  struct Y {using type = int;};
template<typename T>
void func(typename T::type) { cout << "func(T::type)" << endl; }
template<typename T>
void func(T) { cout << "func(T)" << endl; }

func<Y>(1); // func(T::type), because Y has a member type "type"
func(1); // func(T), because int does not have a member type "type"
  

Concepts

Concepts are used to constrain template parameters. They are used to restrict the types that can be used as template parameters.

  template<typename T, typename U> struct is_same : integral_constant<bool, false> {};
template<typename T> struct is_same<T, T> : integral_constant<bool, true> {};
template<typename T, typename U> constexpr bool is_same_v = is_same<T, U>::value;

template<class T, class U> concept Same = is_same_v<T, U> && is_same_v<U, T>;

template<typename T> requires Same<T, int>
void foo(T t) { cout << "Overloaded" << endl; }
template<typename T>
void foo(T t) requires Same<T, int> { cout << "Overloaded" << endl; }
template<Same<int> T> // implicit: Same<T, int>
void foo(T t) { cout << "Overloaded" << endl; }
  

Combining Conditions

  template<typename T> requires
    std::integral<T> ||
    (std::invocable<T> && std::integral<typename std::invoke_result<T>::type>)

void function3(const T& x) {
    if constexpr (std::invocable<T>) { // compiler decides which branch to compile
        std::cout << "Result of call is " << x() << "\n"; // (): function operator
    } else {
        std::cout << "Value is " << x << "\n";
    }
}
  

Defining Concepts

Syntax:

  template<typename T> concept ConceptName = ConstraintExpression;
  
  • ConceptName is the name of the concept
  • ConstraintExpression is a constraint expression. Valid constraint expressions are:
    • logical constexpr expression
    • requires expression
    • other concepts