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"

https://www.youtube.com/watch?v=vwrXHznaYLA

Who is afraid of <templates>?

By Davide Faconti

Who is afraid of <templates>?

  • 594