Metaprogramación en C++ con macros
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 🙂
hola, soy Evelyn de Chile, sabes que en la universidad me dieron una tarea con respecto a las macros, ¿cual es la diferencia entre usar macros y funciones?, nos dieron un ejemplo en el cual no retorna los mismos valores cuando se usan prefijos y sufijos , –, sabes porque retornan valores distintos en algunas ocasiones?
Gracias por tu ayuda
Hola Evelyn,
La diferencia entre macros y funciones es que las macros actúan en el preprocesado del fichero a compilar, es decir, antes de empezar a compilar el programa incluso antes de validar si un programa es o no correcto. Si usas un compilador como gcc/g puedes con el atributo «-E» ver el efecto que causan las macros, sin compilar el programa.
Por ejemplo un error tÃpico del uso de macros:
#define SUMA(a,b) a b
cuando compilas una expresión como SUMA(5,3), se genera en tiempo de preproceso la expresión 5 3 que luego se compila y finalmente se ejecutará. Pero escribir las macros asà puede ocasionar efectos no deseados, por ejemplo:
printf(«%d»,SUMA(4,6)*10)
uno esperarÃa si SUMA fuese una función, que primero se sumase los valores 4 y 6 y luego se multiplicase por 10, el resultado «(4 6)*10 = 100». Pero las macros, a diferencia de las funciones, se expanden no se ejecutan. Esto es lo que hace el preprocesador:
printf(«%d»,SUMA(4,6)*10) -> printf(«%d»,4 6*10)
el resultado es 64 y no 100, si SUMA hubiera sido una función y no una macro se hubiera ejecutado correctamente, por el contrario la macro se ha limitado a coger los parámetros y expandir su contenido. Espero que asà veas la diferencia entre una macro y una función.
Por cierto, si se hubiera escrito la macro como se recomienda hacerlo, todo hubiera funcionado bien:
#define SUMA(a,b) ((a) (b))
En definitiva, las macros expanden código mientras que las funciones ejecutan código.
Respecto a los prefijos y sufijos que comentas, no se muy bien a qué te refieres, si esto de las macros no te lo aclara coméntalo con un ejemplo y algo más de detalle.
Sé que el ejemplo es solo para fines ilustrativos pero no tendrÃa mas sentido en ese case sobrecargar el operador + en lugar de tener una funcion para n elementos que van a ser sumados.
Asi solo tienes una funcion para «sumar» dos objetos que no necesariamente son enteros (claro que definiendo que significa sumar para esa clase)
Hola Alberto,
Sà claro, el ejemplo tiene como fin ser ilustrativo, nada más. SerÃa una aberración usar esto tal cual para «sumar».
Yo lo uso únicamente para generalizar el número de parámetros de las plantillas, ya que estas generalizan bien el tipo pero no en número. Por ejmplo, para escribir wrappers de métodos y funciones las plantillas son ideales ya que por matching y especialización uno puede generar automáticamente código especÃfico para una funcion dada de tipos desconocidos pero con número concreto de parámetros. Para generalizar el número en este caso sólo hay, hasta donde yo se, trucos con el preprocesador como este.
hola ante todo saludos!
no soy programador cuando vi por accidente tu blog me intereso el tema y busque informacion del lenguaje c y esas cosas desde mi punto de vista » aunque es buena la idea de hacerlo como lo planteas» tu solucion presenta varios problemas practicos entre ellos debes declarar previmente los posibles casos que aplican y no le das cierto grado de independencia al programa en mi caso empece por tratar de evitar hacer un funcion como lo hace en el archivo adicional .h y con un simple #define suma(a, b) a + b «en el caso de querer sumar»y aplicarlo como una funcion cualquiera ya puedes sumar y asi para (a + b + c …..) y asi con multiplicaciones y varias cosas que hice para demostrarmelo incluso complicando un poco el asunto puede utilizar asignacion dinamica para hacerlo o utilizar otras definiciones para realizar un macro realmente complejo aunque muy funcional. Con lo cual se confirma lo que dices de la dificutad de mantenerlo. lo otro es que de esta forma te evitas tener que ver en otro archivo una funcion super larga que no comprenderias a la larga! si quieres hacerlo modular lo pueder hacer en otro archivo pero ahorrando linea de codigo…
Hola,
No tengo ni idea de como hacer una macro de este estilo SPP_ENUM_D(N,item), puedes poner el código de esta librerà SPP accesible?
gracias
He cambiado el link, hacia unos dos años de la entrada y no estaba actualizado
http://svn.pplux.com/SLB/trunk/include/SLB/SPP.hpp
Hola, muy bueno el bolg 😉
QuerÃa plantearte una duda. Es posible que una macro genere nombres de funcion diferentes según los parámetros que le pasemos a la macro?
He de hacer un ejercicio donde una macro genere 100 funciones tal que asÃ: imprimir_0_1(); imprimir_0_2()… hasta imprimir_9_9(); y la macro es NUM(x,y), pero claro he probado varias formas y no me deja que hacerlo…
A ver si es posible.
Un saludo y gracias 😉
Hola @Fran,
Yo creo que si haces algo como en la macro suma poner el nombre de la función como int suma##N :
#define NAME(X) suma_##X()
int NAME(5)
{
//
}
int NAME(3)
{
//
}
hola! es posible tener una variable auxiliar dentro de la macro?
saludos,Lara
Hola @Lara, una variable auxiliar? me parece que a nivel de macro nope, otra cosa es que la variable auxiliar la quieras en C y entonces sà la puedas generar.