¿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.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s