Hace poco vimos como podiamos especializar un template en C++ para producir distintos resultados dependiendo de los parametros. Tomemos eso un paso más adelante y tratemos de hacer algo un poco más complicado: calcular el valor de un numero elevado a un poder, todo en tiempo de compilación.
// N^A template <long N, unsigned int A> struct pow { static long const value = N * pow<N, A-1>::value; }; // N^1 == N template <long N> struct pow<N, 1> { static long const value = N; }; // N^0 == 1 template <long N> struct pow<N, 0> { static long const value = 1; }; int main() { pow<10, 2>::value; // 100 pow<2, 3>::value; // 8 pow<1234, 0>::value; // 1 // un arreglo de 1024 elementos int arreglo[pow<2, 10>::value]; }
El base template representa al numero N elevado al poder A. Como pueden ver, es recursivo. Y como cualquier funcion recursiva, se ocupa un caso base para parar la recursion. Eso es lo que hacen las especializaciones de abajo, ya que sabemos que cualquier numero elevado al poder 1, es el mismo numero y cualquier numero elevado al poder 0 es siempre 1. El ultimo ejemplo muestra que el valor producido por pow se sabe al momento de compilar, asi que no hace nada en runtime. Eso si, puede que noten que tarda más en compilar. 😀
Un ejercicio que sale en el libro C++ Template Metaprogramming (por cierto, muy bueno) consiste en crear una clase que por medio de templates y especializaciones describa en palabras el tipo pasado como parametro.
template <typename T> struct type_descriptor { template <typename OS> OS& write(OS& os) const { return os << "unknown-type"; } }; template <> struct type_descriptor<int> { template <typename OS> OS& write(OS& os) const { return os << "int"; } }; template <> struct type_descriptor<char> { template <typename OS> OS& write(OS& os) const { return os << "char"; } }; template <> struct type_descriptor<long> { template <typename OS> OS& write(OS& os) const { return os << "long"; } }; template <> struct type_descriptor<short> { template <typename OS> OS& write(OS& os) const { return os << "short"; } }; template <typename T> struct type_descriptor<T const> { template <typename OS> OS& write(OS& os) const { return os << "const " << type_descriptor<T>(); } }; template <typename T> struct type_descriptor<T volatile> { template <typename OS> OS& write(OS& os) const { return os << "volatile " << type_descriptor<T>(); } }; template <typename T> struct type_descriptor<T&> { template <typename OS> OS& write(OS& os) const { return os << "reference to " << type_descriptor<T>(); } }; template <typename T> struct type_descriptor<T*> { template <typename OS> OS& write(OS& os) const { return os << "pointer to " << type_descriptor<T>(); } }; template <typename T> struct type_descriptor<T[]> { template <typename OS> OS& write(OS& os) const { return os << "array of " << type_descriptor<T>(); } }; template <typename T, int N> struct type_descriptor<T[N]> { template <typename OS> OS& write(OS& os) const { return os << "array [" << N << "] of " << type_descriptor<T>(); } }; template <typename R> struct type_descriptor<R ()> { template <typename OS> OS& write(OS& os) const { return os << "function returning " << type_descriptor<R>(); } }; template <typename OS, typename T> OS& operator<<(OS& os, type_descriptor<T> const & type) { return type.write(os); } int main() { using namespace std; cout << type_descriptor<int*>() << endl; cout << type_descriptor<long* (* const)()>() << endl; cout << type_descriptor<int volatile* const& (*[10])()>() << endl; }
En este ejemplo, el base template no sabe que tipo le fue pasado, entonces regresa “unknown-type.” Despues, pueden ver que varias especializaciones han sido definidas. Estas son usadas para identificar el tipo exacto del parametro, y varias especializaciones son recursivas. El programa imprime lo siguiente:
pointer to int const pointer to function returning pointer to long array [10] of pointer to function returning reference to const pointer to volatile int
Es un buen ejercicio para empezar a pensar diferentemente acerca de los templates. Si les gusto, esperense para cuando hable del Metaprogramming Library. 🙂