Anuncios

Bienvenidos sean a este post, hoy veremos una de las etapas de TMP.

Anuncios

Habitualmente, si tenemos la posibilidad de conocer los valores de entrada y salida de una tarea al momento de compilarlo, podemos usar metaprogramacion para la computacion durante la compilacion y esto nos ahorrara tiempo de ejecucion y consumo de memoria. Lo cual resulta especialmente util cuando trabajamos en proyectos que utilizan intensamente el CPU. Para entender este concepto,, vamos a analizar un ejemplo:

#include <iostream>

int f1(const int n)
{
        return (n<=1) ? 1 : n * f1(n - 1);
}

constexpr int f2(const int n)
{
        return (n<=1) ? 1 : n * f2(n - 1);
}

int main()
{
        int a1 = f1(10);
        int a2 = f2(10);
        const int a3 = f2(10);
        std::cout << "a1: " << a1 << "\ta2: " << a2;
        std::cout << "\ta3: " << a3 << std::endl;
        return 0;
}
Anuncios
Anuncios

Este es un codigo para calcular el factoreo de un numero. Para ello usaremos una funcion recursiva donde recibira un valor y devuelve 1 si el valor ingresado es menor o igual a 1. En caso contrario, devuelve la multiplicacion del valor recibido por la funcion con el valor anterior menos uno. Esto lo hara mientras no se cumpla la condicion anterior. La siguiente funcion hace exactamente lo mismo pero mediante el constexpr se trabaja al momento de compilacion o ejecucion dependiendo como se lo utilice, sobre constexpr hablamos en este post, pero eso lo veremos en el main. Mencionando al main, pasemos a hablar sobre este. Primero definiremos tres variables que almacenaran el resultado de las dos funciones anteriores. En el caso de a1 y a2 se ejecutaran en el momento de ejecucion, perdon por la redundancia, como dijimos anteriormente esta se adapta al momento de ser utilizado. En cambio, la tercer variable se ejecutara al momento de compilacion, para finalmente mostrar los tres valores obtenidos. Compilemos y veamos como es la salida:

$ ./fact
a1: 3628800     a2: 3628800     a3: 3628800
$
Anuncios

Con nuestro codigo funcionando, pasemos a convertirlo a metaprogramacion. Para ello tomaremos el codigo anterior y lo modificaremos de la siguiente manera:

#include <iostream>

template <int n>
struct fact
{
        const static int valor = n * fact<n - 1>::valor;
};

template<>
struct fact<0>
{
        const static int valor = 1;
};

int main()
{
        std::cout << "fact<0>=" << fact<0>::valor << std::endl;
        std::cout << "fact<1>=" << fact<1>::valor << std::endl;
        std::cout << "fact<10>=" << fact<10>::valor << std::endl;
        return 0;
}
Anuncios
Anuncios

Primero definiremos un template donde manejara solo el tipo int. En este estableceremos un struct que se encargara del factoreo. Este contendra una variable estatica que almacenara la recursion de multiplicarse el argumento recibido por la struct pero le pasaremos un valor menor al anterior y usara el valor actual en la variable. Es exactamente lo mismo que vimos en el codigo anterior que inclusive funcionara con el valor de 1, pero que sucedera con el 0? Para ello, aplicamos la especializacion que sigue a la struct anterior. En este, le indicamos que solo trabajara cuando reciba el valor de 0 y asignara el valor de 1 a la variable. En el main, mostraremos a valor dentro de fact y le pasaremos tres valores representativos como son 0, 1 y 10. Los dos primeros porque devuelven un valor en comun y el ultimo porque sera el verdadero factoreo. Compilemos y veamos como es la salida:

$ ./fact
fact<0>=1
fact<1>=1
fact<10>=3628800
$
Anuncios

Como pueden ver funciono perfectamente y tenemos la recursion propia en la variable del struct que nos permite obtener el valor pero esto tiene un par de inconvenientes, modifiquemos al main de la siguiente manera:

int main()
{
        int m = 5;
        std::cout << "fact<0>=" << fact<0>::valor << std::endl;
        std::cout << "fact<1>=" << fact<1>::valor << std::endl;
        std::cout << "fact<10>=" << fact<10>::valor << std::endl;
        std::cout << "fact<m>=" << fact<m>::valor << std::endl;
        return 0;
}
Anuncios

Agregamos una variable con un valor, y al final mostraremos el valor de usar al struct con la variable anterior, compilemos y veamos como es la salida:

$ g++ fact.cpp -o fact
fact.cpp: In function ‘int main()’:
fact.cpp:21:42: error: the value of ‘m’ is not usable in a constant expression
   21 |         std::cout << "fact<m>=" << fact<m>::valor << std::endl;
      |                                          ^
fact.cpp:17:13: note: ‘int m’ is not const
   17 |         int m = 5;
      |             ^
fact.cpp:21:42: note: in template argument for type ‘int’
   21 |         std::cout << "fact<m>=" << fact<m>::valor << std::endl;
      |                                          ^
$
Anuncios

Falla porque nosotros debemos usar valores constantes para los argumentos non-type. Para poder solucionarlo, debemos modificar a la linea de la variable de la siguiente manera:

const int m = 5;
Anuncios

El resto del codigo sigue siendo el mismo, compilemos y veamos como es la salida ahora:

$ ./fact
fact<0>=1
fact<1>=1
fact<10>=3628800
fact<m>=120
$
Anuncios

Como pueden ver funciono perfectamente y ocurrio lo que comentamos cuando hablamos de los argumentos non-type, sobre este tema hablamos en este post, y antes de finalizar vamos a comparar las funciones constexpr (FC) con TMP:

  • Tiempo de computacion: FC se ejecuta al momento de compilacion o ejecucion, dependiendo del uso. TMP solo es en ejecucion.
  • Listas de argumentos: FC toma solo valores pero TMP puede tomar valores y parametros de tipos
  • Estructura de control: FC puede usar recursion, condiciones y bucles pero TMP usa solo recursion
Anuncios

En resumen, hoy hemos visto a la etapa de computacion en la compilacion, que es, como se aplica, y unos ejemplos practicos para verlo en accion. Espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios
pp258

Donatión

It’s for site maintenance, thanks!

$1.50