Who is <afraid> of templates?
You are using templates already
- STL Containers
- Smart Pointers
- etc..
std::vector<double> awesome_data;
Templates allow you to create generic code and avoid duplication, in a way that is at the same time
type-safe and type-independent
Macros
And void*
Suck
Example
//duplicate code
unsigned char add(unsigned char a, unsigned char b)
{
return a+b;
}
int add(int a, int b)
{
return a+b;
}
double add(double a, double b)
{
return a+b;
}
// using template instead
template <typename T> T add(T a, T b)
{
return a+b;
}
//usage
double sum = add<double>( 1.0, 3.1 ); // 4.1
The compiler substitutes type T as needed
during compilation
(NEVER at run-time).
Intuitively, it resembles a macro, but type-safe and much, much more powerfull.
template <typename T> T add(T a, T b)
{
return a+b;
}
short sum = add<short>( 1, 3 ); // 4
Templates are not functions, they are template for making functions.
The compiler instantiates only the types that are used.
Sometimes the compiler understand the type by itself
template <typename T> const T& Max(const T& a, const T& b)
{
return (a>b) ? B : a;
}
float max_num = Max( 1f, 3f );
Struct
template <typename T> struct Statistics
{
T min;
T max;
T average;
T median;
int samples_count;
};
template <typename T> T calculateAverage( T* array, int asize)
{
T total = 0;
for (int i=0; i < asize; i++)
{
total += array[i];
}
return total / (T)asize;
};
Function
Classes
or
individual methods
template <typename T> class AverageCalculator
{
public:
AverageCalculator() : total(0), num_samples(0) {}
void addSample(T value) {
num_samples++;
total += value;
}
T getAverage() const {
return total / (T)num_samples;
}
template <typename S> //template method
void printAverage(S& stream) const
{
stream << getAverage();
}
private:
T total;
int num_samples;
};
Undefined References...
Remember that the compiler must be able to find the implementation of functions and methods during compilation!
// utils.h
template <typename T> const T& Max(const T& a, const T& b);
// utils.cpp
template <> const double& Max(const double& a, const double& b) { return (a>b) ? a : b; }
// file.cpp
#include "utils.h"
double max_d = Max( 1.0, 2.0); //OK
int max_i = Max( 1, 2 ) //undefined reference. Won't compile
For this reason most of the time templates are (or must be) implemented in header files
Declarations
template<class T, size_t SIZE>
class Array
{
public:
T& operator[](size_t index);
size_t size() const;
private:
T _data[SIZE];
};
// definitions outside class
template<typename T>
void size_t Array<T>::operator[](size_t index)
{
assert(index < SIZE);
return _data[index];
}
template<typename T>
void size_t Array<T>::size() const { return SIZE; }
Specialization
//default implementation
template<typename T> void accumulate(T& dest, const T& other)
{
dest += other; // will work just fine with numbers
};
// this type doesn't have the operator +=
typedef struct { double a,b; } Pair;
// specialization only for std::string
template<> void accumulate<std::string>( std::string& dest, const <std::string>& other)
{
dest.append( other );
}
// specialization only for Pair
template<> void accumulate<Pair>( Pair& dest, const Pair& other)
{
dest.a += other.a;
dest.b += other.b;
}
Type deduction
template <typename T> void Foo( T val )
{
std::cout << "T has type: " << typeid(T).name() << '\n';
}
Foo(1); // T has type: int
Foo(1.0); // T has type: double
Foo("hola"); // T has type: const char*
float val = 1.0;
Foo( &val ); // T has type: float*
//---------------------------------
template <typename T, typename U> void Bar( T a, U b) {
// implementation
}
Bar(1, 2u); // T is int, U is unsigned
Note: references are still the best way to do automatic type deduction
Type deduction
template <typename T> void Foo( std::vector<T> vect ) { /* implementation */ }
template <typename T> void Bar( T* value_ptr ) { /* implementation */ }
template <typename T> void ClearMe( T& cont ) { cont.clear(); );
// beware, this would copy T and will not modify the original one
template <typename T> void ClearMeWrong( T cont ) { cont.clear(); );
std::vector<int*> pointers_vector;
Foo( pointers_vector ); // T will be int*
Bar( pointers_vector.back() ); // T will be int
ClearMe( pointers_vector ); // T will be std::vector<int*>
ClaerMe( 1.0f ); // T will be double but it will not compile, because T::clear() does not exist
Type deduction problems
namespace std {
template< class T > const T& max( const T& a, const T& b );
}
short f();
std::max( f(), 1); // compilation error.
// argument 1 deduced T = short whilst argument 2 is T = int
// Solution 1
std::max( static_cast<int>( f() ), 1);
// Solution 2
std::max<int>( f() , 1); //disable automatic deduction
// by the way...
std::max<double>( f() , 1); // force both arguments to be double
"I know C++ templates"
Learn more
CppCon 2016: Arthur O'Dwyer
“Template Normal Programming"
Who is afraid of <templates>?
By Davide Faconti
Who is afraid of <templates>?
- 613