La mayoría de los ejemplos hasta ahora han operado en valores (dígitos de computación de pi, el factorial de N o similar), y esos son más o menos ejemplos de libros de texto, pero en general no son muy útiles. Es difícil imaginar una situación en la que realmente necesite que el compilador compre el decimoséptimo dígito de pi. O lo codifica usted mismo o lo calcula en tiempo de ejecución.
Un ejemplo que podría ser más relevante para el mundo real podría ser la siguiente:
Digamos que tenemos una clase de matriz en donde el tamaño es un parámetro de plantilla (lo que esta sería declarar una matriz de 10 números enteros: array<int, 10>
)
Ahora es posible que deseemos concatenar dos matrices, y podemos usar un poco de metaprogramación para calcular el tamaño de la matriz resultante.
template <typename T, int lhs_size, int rhs_size>
array<T, lhs_size + rhs_size> concat(const array<T, lhs_size>& lhs, const array<T, rhs_size>& rhs){
array<T, lhs_size + rhs_size> result;
// copy values from lhs and rhs to result
return result;
}
Un ejemplo muy simple, pero al menos los tipos tienen algún tipo de relevancia en el mundo real. Esta función genera una matriz del tamaño correcto, lo hace en tiempo de compilación y con seguridad de tipo completo. Y está computando algo que no podríamos haber hecho fácilmente, ya sea codificando los valores (podríamos concatenar muchas matrices con diferentes tamaños), o en tiempo de ejecución (porque perderíamos la información del tipo)
Más comúnmente, sin embargo, tiende a utilizar metaprogramación para tipos, en lugar de valores.
Un buen ejemplo se puede encontrar en la biblioteca estándar. Cada tipo de contenedor define su propio tipo de iterador, pero los indicadores antiguos simples pueden también ser utilizados como iteradores. Técnicamente, se requiere un iterador para exponer una cantidad de miembros typedef, como value_type
, y los punteros obviamente no hacen eso. Así que usamos un poco de metaprogramación para decir "oh, pero si el tipo de iterador resulta ser un puntero, su value_type
debería usar esta definición".
Hay dos cosas que se deben tener en cuenta al respecto. La primera es que estamos manipulando tipos, no valores. No estamos diciendo "el factorial de N es tal y tal", sino que "el value_type
de un tipo T se define como ..."
Lo segundo es que se usa para facilitar la programación genérica. (Los iteradores no serían un concepto muy genérico si no funcionara con el ejemplo más simple, un puntero en una matriz. Por lo tanto, usamos un poco de metaprogramación para completar los detalles necesarios para que un puntero se considere válido iterador).
Este es un caso de uso bastante común para la metaprogramación. Claro, puede usarlo para una amplia gama de otros fines (las plantillas de expresión son otro ejemplo comúnmente utilizado, destinado a optimizar los cálculos caros, y Boost.Spirit es un ejemplo de ir por la borda y le permite definir su propio analizador en compilación- tiempo), pero probablemente el uso más común es suavizar estos pequeños baches y casos de esquina que de otro modo requerirían un manejo especial y hacer imposible la programación genérica.
No soy un programador de QT, pero parece una forma de metaprogramación. Sin embargo, cuando los programadores de C++ hablan de metaprogramación, generalmente se refieren específicamente a la metaprogramación de plantillas (que no tiene nada que ver con el sistema de metaobjetos de QT). Existen varios otros sabores: algunos lenguajes le permiten hacer metaprogramación en tiempo de ejecución (manipulando o extendiendo tipos mientras el programa se está ejecutando), y el enfoque de QT parece generar literalmente nuevos archivos de código para ser #incluidos. Pero todo es metaprogramación, en el sentido de que un metaprograma es simplemente un programa que genera o manipula un programa. – jalf
¿Está interesado en la metaprogramación general o solo en la metaprogramación de plantillas C++? –