Template metaprogramming



// Erwin Unruh, untitled program,
// ANSI X3J16-94-0075/ISO WG21-462, 1994.

template <int i>
struct D
{
    D(void *);
    operator int();
};

template <int p, int i>
struct is_prime
{
    enum { prim = (p%i) && is_prime<(i>2?p:0), i>::prim };
};

template <int i>
struct Prime_print
{
    Prime_print<i-1>    a;
    enum { prim = is_prime<i,i-1>::prim };
    void f() { D<i> d = prim; }
};

struct is_prime<0,0> { enum { prim = 1 }; };
struct is_prime<0,1> { enum { prim = 1 }; };
struct Prime_print<2>
{
    enum { prim = 1 };
    void f() { D<2> d = prim; }
};

void foo()
{
    Prime_print<10> a;
}

// output:
// unruh.cpp 30: conversion from enum to D<2> requested in Prime_print
// unruh.cpp 30: conversion from enum to D<3> requested in Prime_print
// unruh.cpp 30: conversion from enum to D<5> requested in Prime_print
// unruh.cpp 30: conversion from enum to D<7> requested in Prime_print
// unruh.cpp 30: conversion from enum to D<11> requested in Prime_print
// unruh.cpp 30: conversion from enum to D<13> requested in Prime_print
// unruh.cpp 30: conversion from enum to D<17> requested in Prime_print
// unruh.cpp 30: conversion from enum to D<19> requested in Prime_print




Factorial function


Template metaprogramming can inprove efficiency:



int factorial( int n)
{
    return (n==0) ? 1 : n*factorial(n-1)l
}

int main()
{
    cout << factorial(5) << endl;
    return 0;
}

 

But we can compute the value of the factorial function in compile-time:



// factorial.cpp

#include <iostream>

template <int N>
struct Factorial
{
    enum { value = N * Factorial<N-1>::value };
};

template <>
struct Factorial<1>
{
    enum { value = 1 };
};

// example use
int main()
{
    const int fact5 = Factorial<5>::value;
    std::cout << fact5 << endl;
    return 0;
}


Template Recursion


Factorial<5> --> Factorial<4> --> ... -> Factorial<0>

intermediate classes are in pending state
recursive template instatiation nesting depth is limited

- ISO/ANSI recommend at least 17
- everyday compilers can do much more
- sometimes (g++) nesting depth is parameter



Meta Control Structures



In 1966 Bohm and Jacopini proved, that Turing machine implementation is
equivalent the exsistence of conditional and looping control structures.


// IF

template <bool condition, class Then, class Else>
struct IF
{
    typedef Then RET;
};

template <class Then, class Else>
struct IF<false, Then, Else>
{
    typedef Else RET;
};


This is a partial template specialization, we only specialize it on
condition==false, Then and Else remains parameter.

// if sizeof(int) < sizeof(long) then use long else use int
IF< sizeof(int)<sizeof(long), long, int>::RET  i;


template <typename T, typename S>
IF< sizeof(T)<sizeof(S), S, T>::RET max(T t, S s)
{
    if ( t < s)
        return s;
    else
        return t;          
}



Metafunctions as Parameter

In dynamic C++ one can use pointer to function as parameter or return value


// accumulate(n,f) := f(0) + f(1) + ... + f(n)

template <int n, template<int> class F>
struct Accumulate
{
    enum { RET = Accumulate<n-1,F>::RET + F<n>::RET };
};

template <template<int> class F>
struct Accumulate<0,F>
{
    enum { RET = F<0>::RET };
};

template <int n>
struct Square
{
    enum { RET = n*n };
};

cout << Accumulate<3,Square>::RET << endl;



Motivation

This is a motivating example:


int main()
{
    const unsigned int di = 12;
    const unsigned int oi = 014;
    const unsigned int hi = 0xc;

    const unsigned int bi0 = binary_value("1101");
    const unsigned int bi1 = binary<1100>::value;
}


Why in compilation time

- Speed.
- Constantness (array size, enum value must be known in compilation time).
- Compiler control.


template <unsigned long N>
struct binary
{
    static unsigned const value
       = binary<N/10>::value * 2   // prepend higher bits
         + N%10;                   // to lowest bit
};

template <>                           // specialization
struct binary<0>                      // terminates recursion
{
    static unsigned const value = 0;
};



Expression templates


Object-orientation sometimes sacrifies efficiency for readibility and
ease of code maintenance:


Array a, b, c, d, e;

a = b + c + d + e;


This will generate the following pseudo-code:


// pseudocode for a = b + c + d + e

double* _t1 = new double[N];
for ( int i=0; i<N; ++i)
    _t1[i] = b[i] + c[i];

double* _t2 = new double[N];
for ( int i=0; i<N; ++i)
    _t2[i] = _t1[i] + d[i];

double* _t3 = new double[N];
for ( int i=0; i<N; ++i)
    _t3[i] = _t2[i] + e[i];

for ( int i=0; i<N; ++i)
    a[i] = _t3[i];

delete [] _t3;
delete [] _t2;
delete [] _t1;



Performace problems

- For small arrays new  andi delete result poor performance: 1/10 of C.
- For medium arrays, overhead of extra loops and memory access add +50%
- For large arrays, the cost of the temporaries are the limitations: by Veldhuizen


minimal expression template implementation


#include <iostream>

using namespace std;

// this class encapsulates the "+" operation.
struct plus
{
    static double apply( double a, double b)
    {
        return a+b;
    }
};


// the node in the parse tree.
template <typename Left, typename Op, typename Right>
struct X
{
    Left    left;
    Right   right;

    X( Left t1, Right t2) : left(t1), right(t2)  { }

    double operator[](int i)
    {
        return Op::apply( left[i], right[i] );
    }
};


struct Array
{
    // constructor
    Array( double *data, int N) : data_(data), N_(N) { }

    // assign an expression to the array
    template <typename Left, typename Op, typename Right>
    void operator=( X<Left,Op,Right> expr)
    {
        for ( int i = 0; i < N_; ++i)
            data_[i] = expr[i];
    }

    double operator[](int i)
    {
        return data_[i];
    }

    double *data_;
    int N_;
};

template <typename Left>
X<Left, plus, Array> operator+( Left a, Array b)
{
    return X<Left, plus, Array>(a,b);
}

int main()
{
    double a_data[] = { 2, 3, 5, 9 };
    double b_data[] = { 1, 0, 0, 1 };
    double c_data[] = { 3, 0, 2, 5 };
    double d_data[4];

    Array A(a_data,4);
    Array B(b_data,4);
    Array C(c_data,4);
    Array D(d_data,4);

    D = A + B + C;

    for ( int i = 0; i < 4; ++i )
        cout << D[i] << " ";
    cout << endl;
}




Traits and policy classes



template <class T>
class matrix
{
public:
    matrix( int i, int j );
    matrix( const matrix &other);
    ~matrix();
    matrix operator=( const matrix &other);

    int rows() const { return x; }
    int cols() const { return y; }

    T& at( int i, int j) throw(indexError);
    T  at( int i, int j) const throw(indexError);
    T& operator()( int i, int j);
    T  operator()( int i, int j) const;
    
    matrix operator+=( const matrix &other);
private:
    int  x;
    int  y;
    T   *v;
    void copy( const matrix &other);
    void check( int i, int j) const throw(indexError);
};


template <class T>
void matrix<T>::copy( const matrix &other)
{
    x = other.x;
    y = other.y;
    v = new T[x*y];
    for ( int i = 0; i < x*y; ++i )
        v[i] = other.v[i];
}



// Specializations for POD types

template <>
void matrix<long>::copy( const matrix &other)
{
    x = other.x;
    y = other.y;
    v = new long[x*y];
    memcpy( v, other.v, sizeof(long)*x*y);
}



Using traits



template <typename T>
struct copy_trait
{
    static void copy( T* to, const T* from, int n)
    {
        for( int i = 0; i < n; ++i )
            to[i] = from[i];
    }
};
template <>
struct copy_trait<long>
{
    static void copy( long* to, const long* from, int n)
    {
        memcpy( to, from, n*sizeof(long));
    }
};


template <class T, class Cpy = copy_trait<T> >
class matrix
{
    //...
};

template <class T, class Cpy>
void matrix<T,Cpy>::copy( const matrix &other)
{
    x = other.x;
    y = other.y;
    v = new T[x*y];

    Cpy::copy( v, other.v, x*y);
}



Policy classes



template <typename T>
struct is_pod
{
    enum { value = false };
};

template <>
struct is_pod<long>
{
    enum { value = true };
};

template <typename T, bool B>
struct copy_trait
{
    static void copy( T* to, const T* from, int n)
    {
        for( int i = 0; i < n; ++i )
            to[i] = from[i];
    }
};

template <typename T>
struct copy_trait<T, true>
{
    static void copy( T* to, const T* from, int n)
    {
        memcpy( to, from, n*sizeof(T));
    }
};

template <class T, class Cpy = copy_trait<T,is_pod<T>::value> >
class matrix
{
    // ...
};



Typelist



class NullType {};

struct EmptyType {};        // could be instantiated



We now can construct a null-terminated list of typenames:


typedef Typelist< char, Typelist<signed char,
        Typelist<unsigned char, NullType> > > Charlist;


For the easy maintenance, precompiler macros are defined to create Typelists:


#define TYPELIST_1(x)           Typelist< x, NullType>
#define TYPELIST_2(x, y)        Typelist< x, TYPELIST_1(y)>
#define TYPELIST_3(x, y, z)     Typelist< x, TYPELIST_2(y,z)>
#define TYPELIST_4(x, y, z, w)  Typelist< x, TYPELIST_3(y,z,w)>
// :

typedef TYPELIST_3(char, signed char, unsigned char)  Charlist;



//
//  Length
//
template <class TList> struct Length;

template <>
struct Length<NullType>
{
    enum { value = 0 };
};

template <class T, class U>
struct Length <Typelist<T,U> >
{
    enum { value = 1 + Length<U>::value };
};



template <class TList, class T> struct IndexOf;

template <class T>
struct IndexOf< NullType, T>
{
    enum { value = -1 };
};

template <class T, class Tail>
struct IndexOf< Typelist<Head, Tail>, T>
{
private:
    enum { temp = IndexOf<Tail, T>::value };
public:
    enum { value = (temp == -1) ? -1 : 1+temp };
};



Usage



typedef TYPELIST_4(char, signed char, unsigned char, int)  Pod_types;

template <typename T>
struct is_pod
{
    enum { value = ::Loki::TL::IndexOf<Pod_types,T>::value != -1 };
};

template <typename T, bool B>
struct copy_trait
{
    static void copy( T* to, const T* from, int n)
    {
        for( int i = 0; i < n; ++i )
            to[i] = from[i];
    }
};
template <typename T>
struct copy_trait<T, true>
{
    static void copy( T* to, const T* from, int n)
    {
        memcpy( to, from, n*sizeof(T));
    }
};

template <class T, class Cpy = copy_trait<T,is_pod<T>::value> >
class matrix
{
    // ...
};

template <class T, class Cpy>
void matrix<T,Cpy>::copy( const matrix &other)
{
    x = other.x;
    y = other.y;
    v = new T[x*y];
    
    Cpy::copy( v, other.v, x*y);
}



Static interface checking


//  Alex Stepanov and Jeremy Siek

template <class T>
class HasClone
{
public:
    static void Constraints()
    {
        T* (T::*test)() const = &T::Clone;
        test;
    }
    HasClone() { void (*p)() = Constraints; }
};

template <class T>
class C : HasClone<T>
{
    // ...
};




Conversion Checking

Most fundamental check is whether a type could be converted to an other.


template <class T, class U>
class Conversion
{
public:
    enum
    {
        exists = sizeof(Test(MakeT()))==sizeof(Small)
    };
    enum
    {
        sameType = false;
    };
private:
    typedef char Small;
    class Big { char dummy[2]; };

    static Small Test(U);
    static Big   Test(...);
    static T     MakeT();
};

typename <class T>
class Conversion<T,T>
{
public:
    enum { exists = 1, sameType = 1 };
};

int main()
{
    std::cout   << Conversion<double, int>::exists <<
                << Conversion<char, char*>::exists <<
                << Conversion<std::vector<int>,std::list<int> >::exists
                << std::endl;
    return 0;
}


Inheritance

The next step is to chech inheritance:


#define SUPERSUBCLASS(T,U)                              \
    ((Conversion<const U*, const T*>::exists  &&        \
    ! (Conversion<const T*, const void*>::sameType))




Compile-Time Assertion


Based on compile-time decision we can decide to abort the program.
This is parallel to run-time assertions:


//  pure declaration
template <bool> class CompileTimeAssert;

//  specialization for true
template <> class CompileTimeAssert<true>  {};


The trick is, that only specialization is exist: the general case has only
declaration. This declaration is neccessary, because otherwise the
specialization would be invalid.


//  usage:
CompileTimeAssert< SUPERSUBCLASS( X, Y) >   MUST_BE_SUPERCLASS;




#include <iostream>

static inline int f(char *s){ return 1;}


template <int N>
struct Fact
{
  enum { begin = sizeof (f("")) };
  enum { value = Fact<N-1>::value * N };
  enum { end = sizeof (f("")) };
};

template <>
struct Fact<1>
{
  enum { begin = sizeof (f("")) };
  enum { value = 1 };
  enum { end = sizeof (f("")) };
};

int main()
{
  std::cout << Fact<6>::value << std::endl;
  return 0;
}
~              

$ g++ 31_fact.cpp
31_fact.cpp:18:30: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp:20:28: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<6>’:
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:10:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<5>’:
31_fact.cpp:11:3:   instantiated from ‘Fact<6>’
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:10:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<4>’:
31_fact.cpp:11:3:   instantiated from ‘Fact<5>’
31_fact.cpp:11:3:   instantiated from ‘Fact<6>’
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:10:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<3>’:
31_fact.cpp:11:3:   instantiated from ‘Fact<4>’
31_fact.cpp:11:3:   instantiated from ‘Fact<5>’
31_fact.cpp:11:3:   instantiated from ‘Fact<6>’
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:10:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<2>’:
31_fact.cpp:11:3:   instantiated from ‘Fact<3>’
31_fact.cpp:11:3:   instantiated from ‘Fact<4>’
31_fact.cpp:11:3:   instantiated from ‘Fact<5>’
31_fact.cpp:11:3:   instantiated from ‘Fact<6>’
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:10:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp:12:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<3>’:
31_fact.cpp:11:3:   instantiated from ‘Fact<4>’
31_fact.cpp:11:3:   instantiated from ‘Fact<5>’
31_fact.cpp:11:3:   instantiated from ‘Fact<6>’
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:12:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<4>’:
31_fact.cpp:11:3:   instantiated from ‘Fact<5>’
31_fact.cpp:11:3:   instantiated from ‘Fact<6>’
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:12:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<5>’:
31_fact.cpp:11:3:   instantiated from ‘Fact<6>’
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:12:3: warning: deprecated conversion from string constant to ‘char*’
31_fact.cpp: In instantiation of ‘Fact<6>’:
31_fact.cpp:25:23:   instantiated from here
31_fact.cpp:12:3: warning: deprecated conversion from string constant to ‘char*’