Máté Cserép
April 2021, Budapest
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
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?
#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
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 <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
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;
}
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.:
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 T>
class vector
{
//
// general implementation
//
};
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
};
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:
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;
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:
template <typename T>
class Derived : public Base<T>
{
public:
void foo() { this->bar(); } // calls Base::bar()
};
Templates are compiled (at least) twice:
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;
/* ... */
};
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;
};
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.
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;
}
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
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
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
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
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.
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
To avoid multiple instantiations among translation units, in C++11 templates can be declared extern.
The problem:
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.
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.
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
}
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:
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 <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.