nota:La cabecera SPP.h se puede encontrar permanentemente en este repositorio de subversion:

http://svn.pplux.com/SLB/trunk/include/SLB/SPP.hpp

En este post vamos a hablar un poco de metaprogramación y después comentaremos un poco de su uso con templates(plantillas) de C++ . La metaprogramación se define como codificar programas que a su vez codifican oros programas (de ahí el «meta»), y aunque suena un poco extraño, y se podría entender cómo si en matrix las máquinas estuvieran programando programas… en realidad el uso práctico de la metaprogramación es ahorrar tiempo, mucho tiempo.

El problema me surgió cuando vi que para ofrecer cierta funcionalidad tenía que reescribir un código muchas veces con pocas variaciones. Esto es un problema triple, primero es aburrido, segundo es propenso a cometer errores en la versión 15 de 40 y no descubrirlos hasta que sea demasiado tarde, y tercero pensar en cambiar algo en las 40 versiones es una pesadilla. Veamos un caso totalmente ficticio.

int suma()
{
    return 0;
}

int suma(int i1)
{
    return i1;
}

int suma(int i1, int i2)
{
    return i1+i2;
}

int suma(int i1, int i2, int i3)
{
    return i1+i2+i3;
}

Esto es una funcion suma sobrecargada, es una serie de funciones muy tontas pero que alguien tiene que escribir, y es fácil ver que hay un patrón repetitivo. ¿qué pasaría si nos piden que soportemos sumas de hasta 32 elementos de esta forma? Habría que escribir 32+1 una funciones de este estilo. y qué pasa si el día de mañana tiene que cambiar el cuerpo de la función? pues que habría que hacerlo para todas y cada una de las funciones escritas.

Definitivamente esta forma de programar es propensa a errores y engorrosa de mantener. Pero para esto hay solución, y lo llamaremos Metaprogramación con macros, veamos primero cómo quedaría y luego os explico dónde está el truco:

#include "SPP.h"

#define SUMA(N)                                                                      \
int suma(SPP_ENUM_D(N,int i))             /* int suma(int i1, int i2,..., int iN) */ \
{                                         /* {                                    */ \
    return 0 SPP_IF(N,+) SPP_ENUM(N,i,+); /*    return 0 + i1 + i2 + ... + iN;    */ \
}                                         /* }                                    */
SUMA(0)
SUMA(1)
SUMA(2)
SUMA(3)

En primer lugar incluimos la cabecera SPP.h que es la que contiene todas las macros usadas. Esta cabecera es la que hice para poder resolver estos problemas de reescribir mucho código. SPP viene de Simple PreProcessor, no es una idea original ya que la librería boost ya contempla una librería de este estilo mucho más potente (y mucho más compleja), de ahí lo de que esta sea Simple.

  • Ahora definimos una macro SUMA(N), que para dado un N debería escribir la funcion suma para N parámetros (incluido el N=0).
  • SPP_ENUM_D(N,item) enumera item desde 1 hasta N usando comas como
    separador. Es decir SPP_ENUM_D(5,int i) se expande a int i1, int i2, int i3,int i4, int i5.
  • SPP_IF(N,item) se expande a item sólo cuando N es distinto de 0
  • SPP_ENUM(N,item,sep) enumera item desde 1 hasta N usando sep como separador. SPP_ENUM_D(N,item) es en realidad lo mismo que SPP_ENUM(N,item, SPP_COMMA), esto es así debido a que en una macro no se puede escribir directamente una coma(‘,’) ya que confundiría el número de argumentos de la macro.

Con todo esto debería quedar claro cúal es el resultado de SUMA(0), SUMA(1),… pero para asegurarnos haremos la prueba con el compilador. Cuando invocamos gcc -E fichero el compilador sólo aplica la fase de preprocesador, lo que nos permite ver en qué se exapnden las macros. El resulado que produce es el siguiente:

int suma() { return 0 ; }
int suma(int i1) { return 0 + i1; }
int suma(int i1 , int i2) { return 0 + i1 + i2; }
int suma(int i1 , int i2 , int i3) { return 0 + i1 + i2 + i3; }

Por último, para acabar de generalizar, utilizaremos otra macro que iterará automáticamente por cada uno de las SUMA(0), SUMA(1),… etc:

#include "SPP.h"

#define SUMA(N)                                                                      \
int suma(SPP_ENUM_D(N,int i))             /* int suma(int i1, int i2,..., int iN) */ \
{                                         /* {                                    */ \
    return 0 SPP_IF(N,+) SPP_ENUM(N,i,+); /*    return 0 + i1 + i2 + ... + iN;    */ \
}                                         /* }                                    */

SPP_MAIN_REPEAT_Z(32,SUMA)                /* SUMA(0),SUMA(1),...SUMA(32)          */

SPP_MAIN_REPEAT_Z(N,macro) repite desde 0 hasta N llamadas a la macro macro con argumentos 0,1,2,3… es decir macro(0) macro(1) macro(2) … macro(N). El postfijo _Z viene de zero indicando así que la repetición comienza en 0 y no en 1 ya que existe la macro SPP_MAIN_REPEAT (sin _Z).

Obviamente el objetivo de la metaprogramación con macros no es hacer este tipo de funciones tan tontas. Su uso combinado con plantillas de C++ permite ahorrar mucho en tiempo y mantenimiento, por ejemplo cuando se aplica a patrones de functores o similares. Como el post ha quedado un poco largo dejaremos ejemplos y el cómo funciona para mañana 🙂