Templates: Sintaxis

El propósito de las templates:

Como vimos en en el anterior capítulo, las templates son la herramienta que provee C++ para implementar el paradigma de la programación genérica. Su propósito original era proveer de estructuras de datos verdaderamente genéricas, sin tener que lidiar con los engorros de void* heredados de C.

Además de parametrizar tipos, las templates de C++ permiten parametrizar valores evaluables en tiempo de compilación, como números enteros por ejemplo. Esto puede resultar muy útil en ciertos casos, el wrapper de array que vimos en el anterior capítulo es uno de ellos:

template<typename T>
class array_wrapper
{
private:
    T _array[10];
public:
    T& operator[](size_t index)
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }

    const T& operator[](size_t index) const
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }
};

Como ya hice notar al presentar esta class template, el principal problema que tiene es que solo permite simular un array de diez elementos. Lo ideal sería que pudiéramos elegir el tamaño del array. Este problema es fácilmente solucionado añadiendo un parámetro de valor a la plantilla:

template<typename T , size_t LENGTH>
class array_wrapper
{
private:
    T _array[LENGTH];

public:
    T& operator[](size_t index)
    {
        if(index >= LENGTH) throw "Index out of bounds";

        return _array[index];
    }

    const T& operator[](size_t index) const
    {
        if(index >= LENGTH) throw "Index out of bounds";

        return _array[index];
    }
};

Así el usuario de esta template class puede especificar el tipo y el tamaño del array que más le convenga. Por ejemplo:

int main()
{
    array_wrapper<int,5> my_array;
    my_array[0] = 2;
    std::cout << my_array[0] << std::endl;
}

Al ejecutarse la salida del programa sería la siguiente:

2

Este ejemplo de wrapper de array no es únicamente anecdótico, es un contenedor muy utilizado que provee la librería estándar: std::array. Hoy en día se considera muy buena práctica utilizar std::arrayen lugar de los arrays de C, ya que std::array no es implícitamente convertible a un puntero, lo que elimina ciertos problemas de seguridad que dicha conversión provoca. Además std::arrayprovee la misma interfaz que el resto de contenedores de la librería estándar.


A lo largo de los años fue haciéndose evidente que las capacidades de las templates iban mucho más allá, hasta tal punto que se ha demostrado en varias ocasiones que son Turing Completo. Este hecho es la base para una de las prácticas más potentes que C++ provee, template-metaprogramming, que examinaremos en profundidad en capítulos posteriores.

Sin llegar a los extremos de crear verdaderos programas ejecutables en tiempo de compilación a base de template-metaprogramming, si es cierto que las templates son un gran mecanismo para parametrizar y automatizar la generación de código, rasgo que le da su verdadera importancia en aplicaciones reales.
Las templates de C++ permiten regenerar la implementación completa de nuestras clases y funciones, adaptándolas a nuestras necesidades, con solo cambiar un par de parámetros. Los contenedores de la librería estándar son un buen ejemplo de ello: El allocator que usa el contenedor está parametrizado, así que puedes proveer el que quieras, o implementar uno propio acorde a lo que necesites.

La sintaxis de las templates en profundidad

La sintaxis de una template, tanto para clases como funciones, es la siguiente:

template<lista de parámetros> declaración de clase/función

Donde los parámetros, separados por comas, pueden ser de dos tipos:

Type parameters

El parámetro representa un tipo. Se especifican con la sintaxis ” typename/class nombre_del_parámetro “.
C++ acepta tanto la palabra clave “typename” como la palabra clave “class” para indicar pàrámetros de tipo. Es decir, es lo mismo escribir la template así:
template<typename T>
que así:
template<class T>
Ambas son plantillas con un parámetro de tipo.
La diferencia es púramente histórica: En la primera especificación de templates, Stroupstrup recicló la palabra clave class para utilizarla también como especificador de tipo en las plantillas. Así no tenía que añadir una palabra clave nueva al lenguaje y hacer incompatible el código antiguo. Lo malo es que dicha palabra puede conducir a errores, ya que al leer class cualquier persona inexperta podría pensar que dicho parámetro solo permite clases, y no tipos de datos básicos.
Al comenzar la estandarización de C++ a mediados de los noventa, se encontraron ciertos contextos en los cuales podían generarse ambiguedades al usar parámetros de tipo. Por ejemplo:

template<typename T>
class Foo
{
    T::a *pointer_of_type_a;
};

Como es lógico, la intención del programador es declarar un puntero cuyo tipo es una clase interna a la clase pasada como parámetro. Como cuando necesitamos iterar sobre un vector: std::vector<T>::iterator it = v.begin(); Nosotros sabemos que iterator es un tipo, un tipo definido dentro de la clase vector, pero el compilador no puede estar seguro. También puede ser una variable estática. La expresión es ambigua sintácticamente. Nótese que en el caso de que se esté usando un tipo concreto, std::vector<int>por ejemplo, dicha ambigüedad ya no se produce.

En efecto en el ejemplo que estabamos viendo el compilador interpreta la expresión como una operación aritmética entre un atributo estático de T y una variable de nombre “pointer_of_type_a”.
Para resolver este tipo de ambigüedades, el comité de estandarización decidió añadir una nueva palabra clave al lenguaje, typename, con el propósito de indicar cuando cierta expresión hace referencia a un tipo:

template<typename T>
class Foo
{
    typename T::a *pointer_of_type_a; //everything ok. T::a is a type. Now compiler understands.
};

Aprovechando la nueva palabra clave, arreglaron el desaguisado que había supuesto el uso de class como especificador de parámetros de tipo. typenamees mucho más legible, y no da lugar a confusiones. Personalmente, siempre uso typename.

Non-type parameters

El parámetro representa un valor evaluable en tiempo de compilación. Dicho valor puede ser de los siguientes tipos:

  • Un valor entero: Cualquier valor entero que sea evaluable en tiempo de compilación, esto es, resultado de una operación evaluada en tiempo de compilación o un valor entero propiamente dicho.
    Ejemplos:

    template<usnigned int n>
    struct Foo {};
    
    struct Bar
    {
        static const unsigned int a = 10;
        const unsigned int b;
    
        Bar(unsigned int _b) : b( _b ) {}
    };
    
    enum ENUM {ENUM_1 , ENUM_2};
    
    int main()
    {
        unsigned int i = 12;
        Bar bar( i );
    
        using foo_instance_1 = Foo<0>;      //OK
        using foo_instance_2 = Foo<2+2>;    //OK
        using foo_instance_3 = Foo<Bar::a>; //OK
        using foo_instance_4 = Foo<(Bar::a > 2) ? 1 : 0>; //OK
        using foo_instance_5 = Foo<sizeof( bool )>;       //OK
        using foo_instance_6 = Foo<ENUM_1>; //OK (Ver nota)
        using foo_instance_7 = Foo<bar.b>;  //ERROR
        using foo_instance_8 = Foo<i>;      //ERROR
    }
    

    NOTA: El compilador realizará las conversiones implícitas necesarias para “encajar” el valor del parámetro. En este caso los valores de las enumeraciones son int, y el parámetro unsigned int. Algunos compiladores, pero no todos, generan warnings al instanciar templates que utilizan este tipo de conversiones potencialmente peligrosas (int a unsigned int, long int a int, etc). Por supuesto las conversiones explícitas también son válidas.

Nótese que bool también se considera entero, al igual que char (Realmente son enteros ):

template<bool FLAG , char CHARACTER>
struct Foo{};

Un puntero a una función con linkage: Esto descarta funciones que no sean globales ni funciones estáticas de una clase.
Un puntero a un dato con linkage: Esto descarta cualquier dato que no sea global, y dato miembro no estático de una clase. Nótese que esto implica que no pueden utilizarse raw-strings como parámetros de una plantilla. Los arrays globales son implícitamente convertidos a puntero, y por tanto si pueden ser utilizados, pero no un puntero a un elemento del array, ya que este no tiene linkage.
Referencia a lvalue con linkage: Al igual que en el caso de los punteros, descarta cualquier variable no global o miembro estático, además de no ser válidas referencias a objetos temporales (rvalues).
Puntero a miembro de clase: Dicho puntero debe ser de la forma &Clase::miembro, es decir, solo son válidos punteros a funciones miembro y a atributos estáticos.

Como puede verse, exceptuando los tipos enteros, la validez de los argumentos depende directamente de si dicho argumento posee linkage o no. El linkage indica como los nombres pueden hacer referencia o no a un mismo elemento a lo largo de un módulo o el programa completo. Es lógico que el linkage de este tipo de elementos sea necesario en los parámetros de las templates, ya que dichos elementos deben ser conocidos y accesibles en tiempo de compilación.

Template template parameters

Son un tipo especial de type-parameters los cuales especifican que el tipo es una class template. Su sintaxis es template<typename> class nombre_del_parámetro. Por ejemplo:

template<template<typename> class T>
struct Foo;

template<typename T>
struct Bar;

using foo_alias = Foo<Bar>;

Por supuesto dicha class template puede tener los parámetros que necesitemos. Es decir, se aplican las mismas reglas que una template (Y así recursivamente). Veamoslo con un ejemplo algo enrevesado:

template<template<template<typename,unsigned int> class> class T>
struct Foo {};

template<template<typename,unsigned int> class T>
struct Bar {};

using foo = Foo<Bar>;

Foo es una class template cuyo parámetro debe ser una class template con un type-parameter como primer parámetro y un entero sin signo como segundo parámetro.
¿Para que son útiles los template template parameters? Principalmente para especialización de templates un concepto que trataremos en capítulos posteriores.
Pero también existen otros casos en los que pueden resultar útiles. Por ejemplo, una template que nos devuelve una instancia concreta de un contenedor que se le pasa como parámetro:

template<template<typename> class CONTAINER_TYPE>
struct bool_container
{
    using type = CONTAINER_TYPE<bool>;
};

using my_bool_vector = typename bool_container<std::vector>::type; //my_bool_vector es std::vector<bool>;

Este ejemplo es púramente ilustrativo, en realidad no funcionaría: std::vector tiene dos parámetros, el tipo y el allocator, y bool_container espera una plantilla de un solo argumento. De echo, los contenedores de la librería estándar tienen un número de argumentos muy variados, así que hacer esto de verdad no sería tan sencillo. La solución sería hacer que CONTAINER_TYPE fuera una variadic-template (Ver apartado siguiente).

Extras: Argmentos opcionales y variadic templates

Además de especificar una lista de parámetros de plantilla, C++ permite especificar parámetros opcionales y conjuntos de un número indeterminado de parámetros.

Parámetros opcionales

Al igual que con los parámetros de las funciones, podemos especificar parámetros de plantilla opcionales, con valores predeterminados:

template<typename T = int , typename U = bool>
struct Foo;

int main()
{
    Foo<> foo; //Ojo! <> es necesario
}

Por supuesto, al igual que en las declaraciones de funciones, los parámetros opcionales deben aparecer al final.

Esta característica es altamente explotada por los contenedores de la librería estandar: El ejemplo que veíamos antes del allocator. Dicho allocator está parametrizado, si, pero tiene como valor predeterminado el allocator predefinido por la librería estandar, para que no tengamos que especificarlo manualmente.

Los parámetros opcionales únicamente estaban permitidos para class templates hasta que C++11 añadió esta característica para funciones también. Según palabras del propio Stroupstrup:

The prohibition of default template arguments for function templates is a misbegotten remnant of the time where freestanding functions were treated as second class citizens and required all template arguments to be deduced from the function arguments rather than specified.

The restriction seriously cramps programming style by unnecessarily making freestanding functions different from member functions, thus making it harder to write STL-style code.

Variadic templates

Las variadic templates son una nueva característica de C++11 que permite utilizar tempates con un número indeterminado de parámetros (Ninguno inclusive). La sintaxis es la siguiente:

template<typename... Ts>
struct Foo{};

O con non-type params:

template<unsigned int... Ns>
struct Foo{};

Los puntos suspensivos, ..., indican que el parámetro en cuestión no es un parámetro de pantilla, sino lo que se denomina “variadic pack”. Esto es muy importante: Un variadic pack no es un parámetro de plantilla, y no puede ser tratado como tal. No es más que una representación abstracta de un conjunto no determinado de parámetros.
Dentro de una class template podemos definir alias de tipos utilizando como base los parámetros de tipo de la plantilla, como hacen los contenedores de la librería estandar por ejemplo:

template<typename KEY , typename VALUE>
class unordered_map_traits
{
    using key_type = KEY;
    using value_type = VALUE;
    using pair_type = std::pair<KEY,VALUE>;
};

Esto no se puede hacer con un variadic pack, ya que como hemos dicho no es un tipo:

template<typename... Ts>
struct Foo
{
    using types = Ts; //ERROR
};

Este problema puede solucionarse guardando el variadic pack en una typelist, una construcción que estudiaremos más adelante.

Lo que si podemos hacer es averiguar cuantos parámetros forman el paquete: Para ello C++ provee el operador sizeof...:

template<unsigned int... Ns>
struct Foo
{
    static const size_t number_of_tparams = sizeof...Ns;
};

Así podemos saber cuántos parámetros se han pasado al instanciar la plantilla.

Como ya he dicho, los variadic-packs no son más que una representación abstracta de un conjunto de parámetros. Así pues, podemos pasar dicho conjunto de perámetros a otro lado. Para ello, necesitamos desempaquetar el conjunto, y hacer que el compilador lo trate como un conjunto de parámetros per se. Veamos un ejemplo:

template<typename... Ts>
struct Foo;

template<typename... Ts>
using foo_alias = Foo<Ts...>;

Como podemos ver, los puntos suspensivos ... se utilizan para desenpaquetar el conjunto de parámetros y pasárselos a otra plantilla. Esto mismo es válido para function templates con variadic templates:

template<typename... Ts>
void f(const Ts& args...)
{
    return f(args...); //Recursividad infinita
}

En primer lugar deinimos los argumentos de la función, cuyo tipo es el conjunto de tipos representados por el variadic pack (Pasados como referencia constante). Expandimos para que dicha definición se aplique a todos los tipos del paquete.
Al igual que un variadic-pack no es un tipo, sino una representación abstracta de un conjunto de argumentos de plantilla, args no es un argumento de una función es una representación abstracta de un conjunto de argumentos de función. Por tanto para poder usarse dicho conjunto de argumentos, el paquete tiene que ser expandido, como efectivamente hace la sentencia return f(args...);.

Es muy importante entender que esos packs (Tanto los de argumento de template como los de función) no son más que representaciones abstractas de conjuntos de parámetros, y como tal tienen la misma categoría que un conjunto de parámetros que podrías haber escrito tu a mano. Veamos paso a paso como el compilador instancia una variadic template:
Tenemos la siguiente plantilla de clase:

template<typename... Ts>
struct Foo{};

int main()
{
    Foo<int,char,double> my_foo_variable;
}

Esto es equivalente a que hubieramos escrito la plantilla Foo con tres parámetros de tipo. El mecanismo de instanciación es exactamente el mismo que hemos visto hasta ahora:

template<typename T , typename U , typename W>
struct Foo{};

int main()
{
    Foo<int,char,double> my_foo_variable;
}

Se traduce a:

struct __Foo_int_char_double {};

int main()
{
    __Foo_int_char_double my_foo_variable;
}

La única diferencia es que la variadic template no nos exige introducir exactamente tres tipos, sino que nos permite introducir el número de tipos que queramos. Es decir, la template es aún más genérica.
Como he dicho antes, los variadic-packs tienen la misma categoría que un conjunto de parámetros que hubiéramos escrito a mano. Porque realmente es lo que son, un conjunto abstracto de esos parámetros. Esto quiere decir que podemos combinar variadic-packs con listas de parámetros escritos a mano sin ningún problema. Por ejemplo:

template<typename... Ts>
struct Foo{};

template<typename... Ts>
using my_Foo_alias_1 = Foo<int,Ts...>;
template<typename... Ts>
using my_Foo_alias_1 = Foo<Ts...,char,bool>;
template<typename... Ts>
using my_Foo_alias_1 = Foo<Ts...,char,Ts...>; //Le pasamos dos veces, por qué no?

int main()
{
    my_Foo_alias_1<char,char,char> a; //a es de tipo Foo<int,char,char,char>
    my_Foo_alias_1<>               b; //b es de tipo Foo<int>
    my_Foo_alias_2<int,int>        c; //c es de tipo Foo<int,int,char,bool>
    my_Foo_alias_3<bool,int,float> d; //d es de tipo Foo<bool,int,float,char,bool,int,float>;
}

Por supuesto podemos escribir templates que utilicen a su vez variadic packs y parámetros normales. Por ejemplo:

template<typename T , typename U , unsigned int... Ns>
struct Foo {};

using foo_1 = Foo<int,bool,1,2,3>;
using foo_2 = Foo<int,char>;

La única limitación es que solo puede haber un variadic pack, y debe ser el último parámetro de la template. Esta limitación no existe con function templates, ya que los tipos son la mayoría de las veces deducidos en base a los argumentos de la función. Ya hablaremos de esto en profundidad más adelante.

Conclusión:

Aunque todavía quedan algunos aspectos conceptuales por explicar, en este capítulo hemos aprendido para que se utilizan las template, y hemos visto su sintaxis en profundidad.
El objetivo del capítulo era conocer el contexto en el que se usan las templates, como fin de los contenidos introductorios, y conocer con exactitud la sintaxis de las templates. Hemos visto que las templates permiten utilizar diferentes tipos de parámetros, cada uno con su uso y limitaciones. Ahora que ya conocemos esto podemos empezar a tratar esos importantes conceptos de su uso y funcionamiento más allá de la mera sintaxis.

Apéndices:

Apéndice A: ¿Cuando debo utilizar la palabra clave typename?

Como vimos en el apartado type-parameters, la palabra clave typename se utiliza para resolver ciertas ambigüedades sintácticas inherentes al uso de templates.
El uso de dicha palabra clave, esto es, el reconocimiento por parte del programador de si cierta expresión es ambigua o no, es uno de los conceptos más importantes (Y peor explicados en general) de las templates de C++.

¿Por qué y en que casos hay ambigüedad?

El problema es la gramática del lenguaje. C++ es conocido por ser el lenguaje de programación con la gramática más compleja, y esto deriva directamente en que los parsers de C++ son muy complejos de implementar, y la compilación no es precisamente sencilla (Tarda muuuucho tiempo). Dicha complejidad está relacionada principalmente con las templates, y con el echo que ya mencionamos de que este mecanismo es Turing Completo.
En teoría los lenguajes de programación utilizan gramáticas de tipo dos, es decir, gramáticas libres de contexto ; pero en el caso de C++ eso no es del todo cierto.
Por ejemplo: Es muy facil diseñar un programa en C++ cuya validez sintáctica depende directamente de si cierto número es primo o no:

template<bool flag , unsigned int N>
struct value_holder;

template<unsigned int N>
struct value_holder<true,N>
{
    static const int value = N;
};

template<unsigned int N>
struct value_holder<false,N> 
{
    using value = unsigned int;
};

template<unsigned int N>
struct is_prime
{
    static const bool result = /* La implementación la dejaremos para
                                  otro día, demasiada template-metaprogramming
                                  para ahora. Pero se puede hacer, y es
                                  sencillo */
};

template<unsigned int N>
struct Foo : public value_holder<is_prime<N>::value,N> {};

int main()
{
    unsigned int i = Foo<7>::value; //Esto compila
    unsigned int j = Foo<4>::value; //Esto no compila
}

Este ejemplo demuestra que:
– El mecanismo de instanciación de las templates es Turing Completo (Algo que ya sabíamos).
– La gramática de C++ no puede ser estrictamente de tipo dos, ya que este tipo de condición (Validez sintáctica dependiente de primalidad) no puede ser expresada con este tipo de gramáticas.

En efecto existen casos en los que la validez sintáctica de una sentencia depende directamente del contexto de esta, y en muchos de ellos dicho contexto no puede ser determinado por el compilador directamente, con lo que se genera la ambigüedad.
Hay casos en los cuales el compilador utiliza lo que se denomina most vexing parse, una situación en la cual la ambigüedad es eliminada en base a una regla del estándar que produce resultados inesperados para un programador novel. La regla es la siguiente:

Cualquier situación en la cual una expresión puede ser evaluada tanto como una declaración, como alguna otra cosa; la expresión deberá ser evaluada como una declaración.

Esto produce que la siguiente expresión:

struct Foo {};

struct Bar
{
    Bar(const Foo&); //Bar ctor
};

int main()
{
    Bar bar(Foo()); //Inicialización llamando al constructor de Foo. 
}

sea evaluada como la declaración de una función llamada bar que devuelve Bar por valorque tiene como parámetro (Parámetro sin nombre) una función sin parámetros que devuelve Foo por valor. Este en problema en concreto se soluciona muy fácilmente en C++11 utilizando uniform initializers:

int main()
{
    Bar bar{Foo()}; //Inicialización llamando al constructor de Foo. OK (no most vexing parse)
    Bar bar{Foo{}}; //Mejor (Coherencia sintáctica/estilo)
}

Una situación similar ocurría al utilizar templates: Debido a como el parser tokeniza el código fuente, el compilador entendía >> como operator>> no como el cierre de dos templates.

Pero también hay muchos casos en los que el compilador no es capaz por si solo de resolver la ambigüedad. Uno de los más importantes es el uso de nombres dependientes de templates

Qualified-ids, non-qualified-ids y nested-name-specifiers

Un qualified-id es un identificador que contiene algún tipo de marca o señal que indica en que lugar está declarado, es decir, a que ámbito pertenece. Un nested-name-specifier es precisamente la parte de un qualified-id que indica el ámbito de este. Veámos un par de ejemplos:

namespace foo
{
    struct Bar
    {
        void quux();
    }
}

foo es un unqualified-id
::foo es un qualified-id sin nested-name-specifier (:: sin nested-name-specifier denota el ámbito actual).
foo::Bar es un qualified-id, y foo::es su nested-name-specifier.
foo::Bar::quux()es un qualified-id y tanto Bar:: como foo:: como foo::Bar:: son nested-name-specifiers.

Qualified-ids y templates: Ambigüedad y typename

Veamos el siguiente ejemplo:

struct Foo 
{
    using type = int;
};

template<typename T>
struct Bar
{
    using type = T::type; //ERROR (type es un tipo o que?)
};

¿Que pasa al hacer referencia al qualified-id T::type? Ambigüedad El compilador no puede saber por si mismo si typeserá un tipo o cualquier otra cosa, ya que no sabe que es T, y por tanto que será type. Por supuesto si al instanciar la plantilla el parámetro no tiene un miembro type, la instanciación fallará.
Para asegurar al compilador que en ese contexto typetiene que ser un tipo, utilizamos la palabra clave typename:

template<typename T>
struct Bar
{
    using type = typename T::type; //OK: type es un tipo (Si no lo es, error)
};

Esto mismo ocurre en cualquier contexto en el cual un nested-name-specifier dependa de un parámetro de plantilla. Por ejemplo:

template<typename T>
using vector_value_type = std::vector<T>::value_type; //ERROR (Que es value_type?)

En este tipo de casos typenamees necesario porque, aunque conocemos la plantilla, no conocemos la instancia de dicha plantilla, y value_typepuede ser una cosa en una instancia, y otra en otras. Esto se debe a que la plantilla puede estar especializada, un mecanismo que estudiaremos en profundidad más adelante.
La sintaxis correcta sería:

template<typename T>
using vector_value_type = typename std::vector<T>::value_type; //OK: value_type tiene que ser un tipo. (Si no lo es, error)

En concreto, el estandar dice lo siguiente:

A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename.

Apéndice B: ¿Cuando debo utilizar la palabra clave template?

Consideremos la siguiente expresión:

std::function<int()> f;

Obviamente es la declaración de un functor llamado f, que representa a una función sin parámetros que devuelve int por valor. ¿Obviamente? Para el compilador no es tan obvio. Veamos la siguiente declaración:

namespace std
{
    int function = 0;
}

¿Y ahora que? Si esa es la declaración de std::function, la expresión anterior se evalúa como una comparación entre std::functiony cero (int() es una llamada a la inicialización de int, que es cero), y el resultado de dicha comparación es comparado con f.
Por supuesto en la vida real std::function es una template, y el compilador así lo reconoce (Estándar 12.2/3):

After name lookup (3.4) finds that a name is a template-name, if this name is followed by a <, the < is always taken as the beginning of a template-argument-list and never as a name followed by the less-than operator

Pero, y si el nombre depende de una template y el compilador no puede estar seguro de que el nombre al que nos refereimos es una template? Por ejemplo:

struct Foo
{
    template<bool flag>
    void quux(); //quux es una function template    
};

template<typename T>
struct Bar
{
    T variable;

    void execute()
    {
        variable.quux<false>(); /* ERROR: El compilador no sabe si quux es una
                                   template function /*
    }
};

using Bar_instance_using_Foo = Bar<Foo>;

Al igual que en el caso de typename(Estamos intentando referirnos a un tipo), el problema aquí es que el nombre quuxdepende de una template, y el compilador no puede saber si quux es una function template o no (No sabe lo que es T, y por tanto no sabe lo que es quux). Lo primero que intenta hacer el compilador es parsear la expresión como una comparación, interpretando que quux es un atributo.
Para indicarle que nos referimos a una template, usamos la palabra clave template:

variable.template quux<false>(); //OK: quux es una template

Como puede verse, las razones de la ambigüedad son exactamente las mismas que en el caso de typename: El nombre depende de una template, y por tanto el compilador no sabe lo que es ese nombre (O al menos no puede estar seguro).

Por último, existen casos en los cuales es necesario utilizar this->templatepara referirse a una function template miembro de la plantilla. Estos casos ocurren cuando dicho miembro es un miembro heredado de una class template. Una vez más, si el nombre depende de una template, la palabra clave templatees necesaria para desambiguar la expresión. En un caso como este el sitio donde aplicar templatees la misma clase, es decir, this:

template<typename T>
struct Foo
{
    template<bool flag>
    void quux();
};

template<typename T>
struct Bar : public Foo<T>
{
    void execute()
    {
        quux<false>(); //ERROR: quux depende de Foo<T>, y por tanto no puedo 
                       //       saber si quux es una function template. 
    }
}

Solución:

this->template quux<false>(); //OK: quux es una function template.

Apéndice C: Herencia con templates y name-lookup

Cuando dentro del ámbito de una clase se hace referenia a un nombre, el compilador recorre el ámbito de dicha clase y el de sus clases bases para encontrar la definición de dicho nombre. Por ejemplo:

struct Foo
{
    int x;
};

struct Bar : public Foo
{
    int f()
    {
        return x; //Sin problema, x se encuentra en la clase base Foo
    }
};

Pero existe una excepción: Las templates.

Fases de la compilación de las templates

Las templates son compiladas en dos fases:

  • Antes de la sustitución de los argumentos: En esta primera fase todos aquello que no dependa de los parámetros de la plantilla es chequeado y localizado. Esto implica que los nombres dependientes de un argumento de la plantilla son evaludados en la segunda fase, cuando se realiza la instanciación y se sustituyen los parámetros por sus valores.
  • Tras sustituir los parámetros por sus valores: En la segunda fase, todas aquellas construcciones y nombres que dependían de algún argumento son examinadas y chequeadas.

El problema

Modifiquemos nuestro ejemplo inicial usando templates:

template<typename T>
struct Foo
{
    int x;
};

template<typename T>
struct Bar : public Foo<T>
{
    int f()
    {
        return x; //ERROR: x no está declarado en este ámbito
    }
};

De buenas a primeras no hay nada que indique que x depende de un argumento de la plantilla. Y por tanto x es resuelto en la primera fase. El problema está en que el ámbito de x, Foo<T> sì que depende de un parámetro, y por tanto el compilador no puede examinar con seguridad dicho ámbito en la primera fase.
Una solución al problema consiste en utilizar this->para hacer que xsea un nombre dependiente de la clase, y por tanto su análisis sea pospuesto hasta la segunda fase:

return this->x; //OK: x se convierte en dependiente, por tanto su análisis 
                //    queda pospuesto hasta la segunda fase, y en ésta el 
                //    compilador examina el ámbito Foo<T>, donde encuentra
                //    a x.

Apéndice D: ¿Por qué las templates tienen que implementarse por completo en la cabecera?

Uno de los primeros problemas que surgen con el uso de las templates es que estas no pueden escribirse siguiendo el esquema típico de declaraciones en .hpp e implementación en .cpp.
El problema surge por el modelo de compilación de C++ y las dos fases de la compilación de las templates.

El modelo de compilación de C++: Translation units

Una unidad de traducción es el conjunto de todo el código fuente que es incluido directa o indirectamente por un archivo de código, después de su preprocesamiento. En esencia es la unidad mínima de código que el compilador trata como un todo e intenta compilar. Es decir, por cada unidad de traducción se generará un archivo de código objeto.
Por ejemplo, al compilar el archivo main.cpp:

#include <iostream>

int main()
{
    std::cout << "hello world!" < std::endl;
}

tras preprocesar el archivo el compilador procederá a compilar todo el código que ha sido incluido en este tras el preprocesamiento, y generar con ello un archivo de código objeto. En este caso por tanto, dicha unidad de traducción incluirá el código de main.cpp y todo el código incluido a través de <iostream>.
Una de las razones por las cuales la compilación de código de C++ es tan lenta es porque al incluir el mismo archivo de código en varias unidades de traducción distintas, dicho código tiene que ser recompilado por completo en cada unidad de traducción, ya que su configuración y contenido puede variar dependiento de las directivas del preprocesador que incluya el archivo raiz.

Cuando se compila el típico programa de C++:

/* Foo.h */

void foo();

/* Foo.cpp */

void foo()
{
    /* blah, blah, blah ... */
}

/* main.cpp */

#include "Foo.h"

int main()
{
    foo();
}

g++ main.cpp Foo.cpp

El compilador comienza por compilar la unidad de traducción que incluye el punto de inicio del programa. En este caso, main.cpp. Tras preprocesar e incluir todo el código de Foo.hen main, se pondrá a compilar el programa.
Cuando el compilador encuentra declaraciones, y dichas declaraciones se usan (Como foo()en este caso), lo que hace es dejar marcas en el código que usa las declaraciones, para que las vea el linker. Como diciendole “Oye en este archivo estas haciendo referencia a una cosa de la que solo tengo la declaración. Te pongo una banderita aquí por si encuentras la definición, así ya sabrás que este uso hace referencia a ese código que acabas de encontrar (La definición)”. Ese es el trabajo del linker: Unir el uso de nombres (Declaraciones) con el código de estas, para resolver adecuadamente los saltos (Llamadas) y demás.
Tras terminar de compilar la unidad de traducción de main.cpp, el compilador continúa con la del resto de archivos a compilar, en este caso Foo.cpp. Al compilar Foo.cppse encuentra con la implementación de cosas marcadas con nombres (foo() en este caso). Compila dicha implementación y deja las marcas correspondientes en el código objeto (“Esto se llama foo(), será la implementación de algo, digo yo”).
Al terminar de compilar el código, es el turno del linker: Este examina los distintos archivos objeto buscando correspondencias entre las marcas: “Anda! Pero si esta marca de aquí concuerda con esta banderita! O sea que esta es la implementación de foo()y por tanto esa bandera en main.obj a lo que hace referencia es a este cacho de código de foo.obj. Ya se como resolver la llamada!!!”.
Si por alguna casualidad el enlazador se queda con alguna banderita sin resolver, es decir, alguna declaración en uso (Banderita) para la que no encuentra implementación, generará un error. Un error bastante común y que a la gente novel le trae verdaderos dolores de cabeza, porque los nombres ya han pasado el mangling y no saben que significa esa especie de balbuceo del compilador (Recordemos que el error se detecta en la fase de linking, es decir, trabajando con código objeto. Por eso el mangling ya está hecho, y el compilador no puede escribir el error con nombres más bonitos).

¿Y que pasa con las templates?

El probema con las templates es que, como hemos visto, se compilan en dos fases (Antes de instanciación y después de instanciación). Y como el .h está en una unidad de traducción diferente del .cpp, la unidad de traducción del .cpp no tiene ni idea de que instancia de la plantilla ha usado la otra unidad de traducción. Las unidades de traducción son completamente independientes, y lo que nosotros le estamos pidiendo es que haga la primera fase de la compilación de las templates en una, y la segunda fase la continue en la otra. Por tanto, imposible.

Anuncios

¿Que son las templates?

Imaginemos una clase normal y corriente. Por ejemplo, un wrapper de un array de diez elementos:

class array_wrapper
{
private:
    int _array[10];

public:
    int& operator[](size_t index)
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }

    int operator[](size_t index) const
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }
};

Rápidamente se observa un problema inherente a esta implementación: Efectivamente esta clase actua como un array de diez elementos. Pero solo como un array de enteros. ¿Como hacer que dicho wrapper valga para cualquier tipo?
Está claro que que el wrapper sea de diez elementos, no de un número de elementos decidido por el usuario también es un problema. Ya hablaremos de eso más adelante.

Es aquí donde entran en juego las templates.

¿Que son las templates?

Las templates son el mecanismo que provee C++ para implementar programación genérica (Siendo más concreto y a la vez más pedante, polimorfismo paramétrico) . Las templates permiten a una función o una clase, y en un futuro a una constante, funcionar con tipos de datos genéricos, evitando tener que implementar una versión para cada tipo de dato.
En nuestro caso:

template<typename T>
class array_wrapper
{
private:
    T _array[10];

public:
    T& operator[](size_t index)
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }

    const T& operator[](size_t index) const
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }
};

Lo primero que tiene que quedar bien claro es que las templates no son clases ni funciones. Son, como su nombre indica, plantillas para crear clases o funciones. El compilador utiliza las templates como plantillas para generar las diferentes versiones de las clases que usamos en nuestros programas.
Por ejemplo:

 #include "array_wrapper"

 int main()
 {
    array_wrapper<int>  a;
    array_wrapper<bool> b;
 }

El código que genera el compilador tiene más o menos esta pinta:

class __array_wrapper_int
{
private:
    int _array[10];

public:
    int& operator[](size_t index)
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }

    const int& operator[](size_t index) const
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }
};

class __array_wrapper_bool
{
private:
    bool _array[10];

public:
    bool& operator[](size_t index)
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }

    const bool& operator[](size_t index) const
    {
        if(index >= 10) throw "Index out of bounds";

        return _array[index];
    }
};

int main()
{
    __array_wrapper_int  a;
    __array_wrapper_bool b;
}

Como puede verse, el abuso en la utilización de templates puede llegar a incrementar de manera importante el tamaño del código generado, y por tanto del ejecutable. Aunque esto no es importante en un contexto normal, puede ser necesario tenerlo en cuenta en entornos con recursos muy limitados, como sistemas empotrados con una cantidad muy limitada de memoria RAM.

Otra cosa a tener en cuenta es que el uso intensivo de templates puede incrementar de manera significativa el tiempo de compilación. Pero a su vez, este viejo argumento anti-templates no es cierto en muchos de los casos en los que se suele utilizar como crítica.
Veamos por ejemplo, el típico ejemplo que se pone de template-metaprogramming: El factorial

#include <iostream>

template<unsigned int n>
struct factorial
{
    static const unsigned int value = n * factorial<n-1>::value;
};

template<>
struct factorial<1>
{
    static const unsigned int value = 1;
};

template<>
struct factorial<0>
{
    static const unsigned int value = 0;
};

int main()
{
    std::cout << factorial<5>::value << std::endl;
    std::cout << factorial<7>::value << std::endl;
}

Ya no estamos en los 80, C++ tiene casi treinta años, y los compiladores no son idiotas: Por supuesto la instanciación de factorial<5>requerirá que el compilador instancie cinco veces la plantilla, una por cada “llamada” recursiva. Pero la instanciación de factorial<7> requerirá solo dos, no siete. Instancia factorial<7>y factorial<6>, pero factorial<5> ya ha sido instanciada antes. Así que no necesita seguir bajando. Cualquier compilador de hoy en día mantiene un registro con las plantillas que va instanciando, para no tener que instanciar dos veces la misma plantilla, reduciendo así el tiempo de compilación.
Además, el problema del code-bloat no es del todo cierto en contextos como este: Según lo que explicamos antes, el código que generaría el compilador sería algo así como:

struct __factorial_1
{
    static const unsigned int value = 1;
};

struct __factorial_2
{
    static const unsigned int value = 2;
};

...

struct __factorial_7
{
    static const unsigned int value = 5040;
};

int main()
{
    std::cout << factorial<5>::value << std::endl;
    std::cout << factorial<7>::value << std::endl;
}

Pero eso no es del todo cierto: Nótese que las únicas instancias que realmente se utilizan son factorial<5> y factorial<7>, el resto son únicamente utilizadas durante la compilación para realizar los cálculos. Y una vez más, el compilador no es idiota, no incluirá en el código instancias que no utilizas. E incluso si nos ponemos muy tiquismiquis, el código realmente generado será:

int main()
{
    std::cout << 120 << std::endl;
    std::cout << 5040 << std::endl;
}

Conclusión:

Ante todo en este capítulo tenía como intención hacer entender que son las templates, como funcionan, y que efectos tienen nuestros programas. En el próximo capítulo veremos en profundidad su sintaxis, como se utilizan, y lo que permiten hacer.

C++ templates tutorial

C++ es sin lugar a dudas uno de los lenguajes de programación más potentes, si no el más potente, en cuanto a eficiencia, abstracción, y productividad se refiere.

Es eficiente

Su padre, el lenguaje de programación C, fue diseñado con la eficiencia en mente. A grandes rasgos C fue diseñado para que cada instrucción fuera equivalente a unas pocas instrucciones en ensamblador. Tanto es así que hoy en día se tiende a considerar C como un ensamblador portable de alto nivel.
C++ hereda esta filosofía, “efficiency is our first goal”, añadiendo mecanismos de abstracción que en la mayoría de los casos no tienen ningún coste en cuanto a eficiencia se refiere, pero que aumentan en gran medida la productividad y mantenibilidad del código.

Este aumento de la abstracción sin runtime-overhead permite que C++ sea igual de eficiente que C (Un 99% de la eficiencia de C), y que la única diferencia real sea el tamaño de los ejecutables (Hoy en día no tan acusada como en los inicios de C++, y que puede ser ajustada mediante optimizaciones) y el overhead que puede provocar el sistema de control de excepciones de C++ y el dynamic-binding. Nótese que esto último puede no ser cierto, ya que el mismo overhead que introduce el compilador de C++ para conseguir polimorfismo a base de vtables puede introducirlo un programador de C a base de void* y castings (La única manera de obtener polimorfismo y generalidad en C).

Es productivo

Incluso teniendo en cuenta esa pequeña posible diferencia de eficiencia está claro que los beneficios de abstracción y productividad de C++ son enormes.
Todo intento de producir código genérico en C es inherentemente una bola de código ilegible, inseguro, y propenso a errores. Un gran ejemplo de ésto son las funciones de ordenación que provee la librería estandar, std::sort y qsort (Transparencia 11) . Otro buen ejemplo es la archiconocida printf().

C++ provee mecanismos, posiblemente de los cuales el más importante son las templates, que hacen que C++ sea uno de los pocos lenguajes de programación que permiten implementar código verdaderamente genérico sin ningún efecto en el rendimiento. Sugiero intentar implementar una biblioteca genérica de, por ejemplo, algebra de matrices en Java. Algo que en C++ se hace sin ninguna complicación. Es cierto que una de las características de java es complicar al extremo hasta la cosa más simple. Pero esa es otra historia… También es problemático hacerlo en C#, no me refiero únicamente a Java y ese chiste que llaman tan orgullosamente “Java generics”.

¿De qué va ha tratar este tutorial?

El objetivo de este tutorial es estudiar uno de esos mecanismos, las templates, y ver como su uso provee de soluciones genéricas y eficientes para aumentar la productividad de nuestro código.

Hace algún tiempo escribí un un pequeño tutorial sobre este mismo tema para un amigo, a base de ejemplos en archivos de cabecera y bloques de comentarios . Como es lógico, bloques de comentarios en .hs no es un muy buen formato para leer algo, y además me quedaron algunas cosas en el tintero que me gustaría haber contado, o haber explicado con mayor detenimiento.
Así pues desde que decidí escribir un blog sobre programación en general estoy pensando en transcribir dicho tutorial a un formato mucho más cómodo, como puede ser una serie de posts en el blog.

Ya veremos que tal queda.

std::chrono_io

C++11 dio un gran empujón a la STL. Ha mejorado muchas de las APIs existentes, y ha añadido otras nuevas que sustituyen las viejas APIs de C.

Entre ellas se encuentra std::chrono.
Chrono es la API estándar para el manejo de tiempo que provee C++11, y es un gran improvement con respecto a la vieja ctime. Pero sigue habiendo algo que chrono no provee, y por lo que seguimos teniendo que tirar de las APIs de C: Entrada y salida.

La sintaxis que utiliza ctime me parece francamente horrible, y hecho de menos una API moderna para hacer estas cosas.
Entre las propuestas para el estándar ISO que no se aceptaron (No se sabe si por falta de tiempo, o por problemas varios) se encuantra la biblioteca chrono_io : http://home.roadrunner.com/~hinnant/bloomington/chrono_io.html. Por lo que se puede ver, tiene muy buena pinta. Ya veremos en un futuro como acaba.

Template alias specialization

Después de comer me he puesto un ratillo a intentar diseñar los elementos básicos de una API de cálculo en tiempo de compilación basada en TMP, básicamente para empezar a picar un poco el render 3D que mencioné en este post..

Tras decidir que voy a utilizar wrappers para los valores en lugar de valores integrales a pelo (creo que así la API es más uniforme, solo utiliza una sintaxis, en lugar de proveer ambas; una para unos casos y otra para otros), me he puesto a implementar operadores básicos.
Para ello, he implementado una interaz básica operator, la cual tiene un typedef que guarda el resultado de la operación:


template<typename RESULT>
struct operator
{
    using result = RESULT;
};

A continuación he definido la interfaz común para todos los operadores: El resultado de la operación y el conjunto de operandos. He aprovechado las variadic template de C++11 para definir una interfaz n-ary operator, así las más comunes binary_operator y unary_operator están definidas como alias de la primera, a la vez que la API permite generar operadores de cualquier número de operandos:


template<typename RESULT , typename... OPERANDS>
struct nary_operator : public operator
{
    static_assert( sizeof... OPERANDS &gt; 0 , "An operator must take at least one operand" );
};


template<typename OP , typename RESULT>
using unary_operator = nary_operator<RESULT , OP>;

template<typename LHS , typename RHS , typename RESULT>
using binary_operator = nary_operator<RESULT , LHS , RHS>;

De ésta forma, he implementado operadores comunes (Operadores lógicos, operadores a nivel de bits, operadores de comparación, operadores aritméticos) como alias de las interfaces antes definidas:


/* Logical operators */

template<typename OP>
using logical_not = unary_operator<OP , bool_wrapper<!OP::value>>;

template<typename LHS , typename RHS>
using logical_or  = binary_operator<LHS , RHS , bool_wrapper<LHS::value || RHS::value>>;

template<typename LHS , typename RHS>
using logical_and = binary_operator<LHS , RHS , bool_wrapper<LHS::value && RHS::value>>;

template<typename LHS , typename RHS>
using logical_xor = binary_operator<LHS , RHS , bool_wrapper<LHS::value ^ RHS::value>>;


/* Comparison operators */

template<typename LHS , typename RHS>
using equal = binary_operator<LHS,RHS,bool_wrapper<LHS::value == RHS::value>>;

template<typename LHS , typename RHS>
using not_equal = logical_not<equal<LHS,RHS>>;

template<typename LHS , typename RHS>
using less_than = binary_operator<LHS,RHS,bool_wrapper<LHS::value < RHS::value>>;

template<typename LHS , typename RHS>
using bigger_than = less_than<RHS,LHS>;

template<typename LHS , typename RHS>
using less_or_equal = logical_not<bigger_than<LHS,RHS>>;

template<typename LHS , typename RHS>
using bigger_or_equal = logical_not<less_than<LHS,RHS>>;


Ahora supongamos que queremos “sobrecargar” el operador de igualdad para nuestro nuevo tipo Foo:


template<typename VALUE>
struct Foo
{
	using x = VALUE;
};

¡No podemos!: No se pueden especializar alias de plantillas.
Tiene sentido (Algunos dicen que no tiene sentido técnico, que es una decisión puramente subjetiva del comité), ya que en sentido estricto los alias no son templates. Solo son otra manera de nombrar a una plantilla. Así que no se pueden especializar. Puedes especializar la plantilla en cuestión.
Me ha llamado la atención que no se pueda hacer, ya que la diferencia entre usar template aliases y utilizar herencia es mínima para éste tipo de cosas. Por ejemplo, el operador de igualdad definido utilizando herencia:


template<typename LHS , typename RHS>
struct equal : public binary_operator<LHS , LHS , bool_wrapper<LHS::value == RHS::VALUE>> {};

Casi podría decirse que son idénticas.
Pero claro, la versión que utiliza herencia si es una plantilla, y por tanto si se puede especializar:


template<typename VALUE1 , typename VALUE2>
struct equal<Foo<VALUE1>,Foo<VALUE2>> : public equal<VALUE1,VALUE2> {};

NOTA: La especialización que he hecho puede parecer un poco extraña, ya que la especialización hereda de otra instancia de la misma plantilla. Lo que he hecho es utilizar el operador de comparación para comparar los dos valores de las dos instancias de Foo. Es equivalente a:


struct Foo
{
	OtherClass value;

	friend operator==(const Foo&amp; lhs , const Foo&amp; rhs)
	{
		return lhs.value == rhs.value;
        };
};

Suponiendo que OtherClass tiene sobrecargado operator==. En el caso de nuestro metaoperador equal, también suponemos que hay una especialización de equal para el tipo de VALUE1 y VALUE2.

Por supuesto, no hay ningún problema. Utilizas herencia y punto. Pero me ha llamado la atención. He preguntado en stackoverflow al respecto. A ver que me cuentan.

Auto + expression templates = Undefined Behavior?

Una de las características más interesantes de C++11 es la inferencia de tipos, gracias a la palabra clave auto.

Auto indica al compilador que utilice como tipo de una variable el tipo de la expresión de su inicialización. Por ejemplo:


int foo() { return 12; }

int main()
{
	auto result = foo(); //El tipo de result es int.
}

Esto permite que el código sea mucho más mantenible, ya que si no se declara explícitamente el tipo de la variable, sino que se usa el de la inicialización, al cambiar la implementación de la inicialización no ocurre nada. Veamos un ejemplo:


/*
* Tenemos como ejemplo ésta factoría.
* La versión original utiliza raw-pointers:
*/
Foo* create_foo();

/*
* Pero decidimos dejar de usar raw-pointers, ya que son propensos a errores: ¿Quien se encarga
* de la destrucción de la instancia? La interfaz que utiliza raw-pointers no lo deja nada claro.
* Por ello pensamos que sería mejor usar std::unique_ptr. Así indicamos al usuario que la instancia 
* se destruye cuando él deje de usarla. Además no le permitimos que la copie:
*/
std::unique_ptr<Foo> create_foo();

La ventaja de auto consiste en que si el usuario inicializa las instancias de Foo utilizando auto en lugar del tipo explícito de la factoría, cuando hacemos la refactorización no tendrá que modificar su código; ya que el compilador se encarga de inferir el tipo correcto. Y la interfaz de un smart pointer es idéntica a la de un raw pointer. En cambio, si hubiera definido una instancia de Foo como Foo* my_foo_instance = create_foo();, se vería obligado a cambiar el tipo de la variable.

Todo parece maravilloso, hasta que encuentras con un pequeño problema: ¿Que pasa con las expression templates?

Las expression templates son un mecanismo por el cual se utilizan plantillas para representar una expresión. Esto era especialmente útil para eliminar la creación de objetos temporales en expresiones muy largas, tales como std::string result = a + b + c + d; . “Eran” porque con las move semantics de C++11, esto ya no es estrictamente necesario, ya que si la sobrecarga de operator+ y el operador de asignación admiten rvalues, no tiene por qué generarse ningún string temporal. Se utiliza el mismo durante toda la expresión.
Aún así existen casos en los que siguen siendo útiles. Por ejemplo, para hacer que las operaciones aritméticas con matrices sean cache-friendly, y por tanto más eficientes. En “C++ Template Metaprogramming”, capítulo 10.5.1 se da una muy buena explicación de éste tema.
Como he dicho, las template expressions utilizan templates para representar expresiones. Esto significa que, utilizando el ejemplo de la string, la expresión “a + b + c + d” no devuelve string, sino que devuelve una plantilla del estilo de string::concat<string::concat<string::concat>>;. Por supuesto, dicha plantilla es convertible a string, y es precisamente ahí donde se realiza la “magia”.
La gracia está en que al convertir implícitamente la template expression al tipo del resultado (String en éste caso), el usuario no tiene por qué saber que la concatenación utiliza una template expression. El mecanismo es completamente trasparente. Por ello, normalmente el proxy (La template que representa la expresión) se implementa como una clase interna privada a la clase en cuestión, String en nuestro ejemplo. Así el usuario no puede acceder al proxy.

Así pues, si la clase string implementara la concatenación utilizando template expressions, la sentencia siguiente es completamente válida, y como he dicho, totalmente trasparente para el usuario:


string result = a + b + c + d;

Pero, ¿que pasa si utilizamos la inferencia de tipos?:


auto result = a + b + c + d;

Como dijimos en un principio, auto indica al compilador que infiera el tipo de una variable a partir del tipo de la expresión que la inicializa. ¿Y cual es el tipo de esa concatenación?: string::concat<string::concat<string::concat>> , que es privado. Anda, y ¿ahora que hacemos?

Lo más gracioso es que esto no genera ningún error de compilación (What?, pero si hemos quedado que concat es privado!), sino que durante la ejecución genera undefined behaviour. ¿Como? ¿Pero si poniendo string funciona perfectamente, por qué va a ser undefined behaviour usando auto?
Veamos: La expression template guarda referencias a los operandos izquierdo y derecho de la expresión original, para poder realizar la operación. Por ejemplo, “a+b+c” se traduce a una template expression de tipo string::concat<string::concat>:


class string
{
	template
	class concat
	{
		const T&amp; left;
		const string&amp; right;
	};
};

Es decir, la expression template guarda una referencia a la subexpresión a su izquierda, y una referencia al operando a su derecha. Y por tanto la template expression que representa la expresión completa es como una “lista recursiva” de las subexpresiones que la forman.
Al utilizar la versión con string, llama a operator string(), realiza la concatenación devolviendo la cadena resultante, y todos contentos.
Pero, cuando usas auto, el tipo de la variable no es string, también es una expression template, y por tanto no se realiza conversión y no se genera ningún nuevo string con el resultado. Por supuesto, al terminar la sentencia “a + b + c + d” se elimina la template expression (RAII). Pero como ya hemos asignado el resultado a una variable (Hemos copiado la expresión resultante en una variable), no hay problema. Pues si, hay un gran problema: La expresión guarda referencias. Referencias a las subexpresiones que acaban de ser eliminadas. Por tanto, al usar la variable, undefined behavior (Estás accediendo a una referencia a un objeto que ha sido eliminado).

Fuente: http://lanzkron.wordpress.com/2011/02/21/inferring-too-much/

PD: He de decir que me ha costado bastante entender donde estaba el problema, no era algo especialmente sencillo. Me ha llamado la atención la sugerencia de un posible operator auto(). Tiene sentido que si ya nos dejan hacer conversiones implícitas en runtime, nos dejen también hacerlas en tiempo de compilación (Sería algo así como: “Trata a esta clase como si fuera ésta otra”).
Me recuerda a cierta pregunta que hice en stackoverflow hace algún tiempo. Este “operator auto()” podría solucionar el problema.

ETMP: “Extreme Template Metaprogramming”

Desde hace un tiempo vengo jugando con TMP, es decir, la metaprogramación en C++ basada en el uso intensivo de templates.

En un primer momento es una cosa que te llama la atención: ¿Como que puedo hacer cálculos es tiempo de compilación? Si si, mira, el cálculo del factorial por ejemplo:


template<unsigned int n>
struct factorial
{
	static const unsigned int result = ( n == 0 ) ? 0 : n * factorial<n-1>::result; 
};

El problema que suele tener la gente con TMP es que cuando buscas tutoriales sobre el tema, lo que suelen encontrar son sitios donde el tutorial en cuestión se centra en éste tipo de metafunciones, es decir, en hacer cálculos en tiempo de compilación.

Aunque de buenas a primeras el hecho de hacer que el compilador ejecute cálculos pueda parecer fascinante, realmente su uso real (como el ejemplo del factorial de arriba) es meramente anecdótico, además de que la sintaxis es francamente horrible. Para los pocos casos en los que hacer cálculos en tiempo de compilación puede resultar útil, C++11 ha añadido el especificador constexpr, el cual indica que una función, variable, o parámetro es evaluable en tiempo de compilación. Esto permite escribir funciones que se ejecutan en tiempo de compilación con la misma sintaxis que una función normal.

El verdadero potencial de TMP radica en la metaprogramación en si, ésto es, hacer que las clases que implementamos sean totalmente flexibles, haciendo que el compilador genere una versión u otra de la clase dependiendo de los parámetros que le pasemos. Esto se denomina “Parametrized polymorphism”, y es algo que en C++ se lleva a su máxima expresión gracias a el diseño basado en policies, una metodología con la cual tus clases se comportan casi como si fueran LEGOs, permitiendo que tu las montes con los componentes que quieras, cambiando por completo su funcionalidad.

Pero TMP tiene una propiedad interesante: Es Turing completo. Y por tanto deberíamos ser capaces de hacer cualquier cosa con ella. Esto nos permite considerar a TMP como un sublenguaje de tipo funcional dentro de C++.
Con TMP operas con tipos como si fueran elementos de primera clase, al igual que en los lenguajes funcionales operas con funciones. Además, también carece del concepto de estado, ya que no puedes modificar un tipo, solo puedes crear tipos nuevos. Al igual que la programación funcional, no existe el concepto de iteración, y todo tipo de recorridos se basan en la recursividad.

Por ejemplo, podemos escribir una sencilla metafunción que dada una secuencia de tipos y un determinado predicado lógico, nos diga cuantos de esos tipos pasan el test (Cuantos evalúan el predicado a true):


//Forward declaration:
template<template<typename> class PREDICATE , typename... Ts>
struct true_predicate_count;

//Caso recursivo:
template<template<typename> class PREDICATE , typename HEAD , typename... TAIL>
struct true_predicate_count<PREDICATE , HEAD , TAIL...>
{
	static const unsigned int value = PREDICATE<HEAD>::value + true_predicate_count<PREDICATE , TAIL...>::value;
};

//Caso base:
template<template<typename> class PREDICATE>
{
	static const unsigned int value = 0;
};

//Podríamos usarla así, por ejemplo:

//Un sencillo predicado: Pregunta si un cierto tipo es mas grande que float: 
template<typename T>
struct is_bigger_than_float
{
    static const bool result = sizeof T > sizeof float;
};

const unsigned int count = true_predicate_count<my_predicate , char , int , long int , float , double>::value;

Como ya he dicho antes, trabajas con tipos como si fueran elementos de primera clase. Un buen ejemplo de esto es la biblioteca de type_traits de la STL. Hacen auténticas maravillas.

Estos son usos sencillos y medianamente moderados de TMP. Son cosas útiles, a la vez que no demasiado complicadas.
Pero, ¿y si queremos llevar al compilador a su máxima potencia? Esto es lo que yo llamo “Extreme Template Metaprogramming”.

Extreme Template Metaprogramming consiste en dejar de pensar en TMP como si fueran plantillas de C++. Empezar a pensar en ello como si fuera un lenguaje en si mismo. Y de éste modo, utilizar todas sus capacidades.
Podemos utilizar TMP para definir nuevos lenguajes. Por ejemplo, podemos utilizarlo para diseñar lo que se denominan DSEL (Domain Specific Embeded Langauges), como se describe en el capítulo 11 de C++ Template Metaprogramming. Esto nos permitiría implementar una biblioteca de álgebra matricial, en la cual la sintaxis para las operaciones es legible, a la vez que eficiente.
E incluso podemos esto último aún más allá: Utilizar TMP para describir una gramática independiente del contexto y generar el parser correspondiente, tal y como hace Boost.Spirit. Boost en general es un gran ejemplo del buen uso de TMP.

Como se puede ver, la potencia que tiene TMP es sencillamente increíble, y no no son pocos los usos que se le pueden dar.

Pero imaginemos por un momento que símplemente estamos chalados, y queremos hacer lo que hablábamos al principio: Hacer cálculos en tiempo de compilación, con TMP. Pero no solo cosas sencillas, como el factorial o la función de Fibonacci, los dos típicos ejemplos que tan cansados estamos de ver. Queremos escribir programas que hagan algo. Programas de verdad.
Propongo como introducción mi propia implementación del algoritmo de ordenación Quickosort: https://github.com/Manu343726/cpp_lib32/blob/BigRefactoring/code/tests/refactoring/quicksort.cpp NOTA: Para aquellos locos que quieran intentar probarlo, C++11 establece 1024 como profundidad máxima de instanciación recursiva de plantillas. Eso solo permitirá la compilación (Ejecución) del ordenamiento de listas de como mucho diez elementos. Mejor aumentar el límite, pero corréis el riesgo de consumir toda la memoria RAM del equipo durante la compilación.

Increíble, verdad? Efectivamente el metaprograma ordena la lista durante la compilación utilizando quicksort. La implementación es idéntica a la implementación clásica de quicksort que todos conocemos, solo que en lugar de utilizar listas, utiliza listas de tipos, y se ejecuta en tiempo de compilación. Pero ya digo, dejando a un lado la peculiar sintaxis de TMP, el algoritmo es idéntico: Cojo la lista, elijo un pivote, pongo todos los elementos mayores a un lado del pivote y los menores al otro, hago esto mismo recursivamente para las dos sublistas generadas…

Y aquí es donde se ve claramente lo que mencioné antes: TMP es Turing completo. De hecho, hay una muy bonita demostración aquí, que se basa precisamente en implementar una máquina de turing con TMP.

Seguramente el ejemplo más increíble que he visto es esto. Un raytracer. Sí, un raytracer. Puedo repetirlo cincuenta veces y todavía no se me vuelve a colocar la mandíbula en su sitio.
Fue tras ver esta biblioteca cuando decidí cual sería el objetivo de mi siguiente gran proyecto: Un render 3D en tiempo de compilación. Hace tiempo ya implementé un render 3D completamente desde cero, y no estaría mal repetir, utilizando algo tan bizarro como es TMP. No, no me he pinchado nada raro, en realidad ya tengo bastante cara la idea general de la implementación. Otra cosa es sacarle tiempo. Sería un buen proyecto de fin de carrera….

Ya veremos si no muero en el intento.