La metaprogramación de plantillas ( TMP ) es una técnica de metaprogramación en la que un compilador utiliza las plantillas para generar código fuente temporal , que el compilador fusiona con el resto del código fuente y luego lo compila. La salida de estas plantillas puede incluir constantes en tiempo de compilación , estructuras de datos y funciones completas . El uso de plantillas se puede considerar como polimorfismo en tiempo de compilación . La técnica es utilizada por varios lenguajes, el más conocido es C ++ , pero también Curl , D y XL. .
La metaprogramación de plantillas fue, en cierto sentido, descubierta accidentalmente. [1] [2]
Algunos otros lenguajes admiten instalaciones de tiempo de compilación similares, si no más potentes (como macros Lisp ), pero están fuera del alcance de este artículo.
Componentes de la metaprogramación de plantillas
El uso de plantillas como técnica de metaprogramación requiere dos operaciones distintas: se debe definir una plantilla y se debe crear una instancia de una plantilla definida . La definición de la plantilla describe la forma genérica del código fuente generado, y la instanciación hace que se genere un conjunto específico de código fuente a partir de la forma genérica en la plantilla.
La metaprogramación de plantilla es Turing-completa , lo que significa que cualquier cálculo expresable por un programa de computadora puede ser calculado, de alguna forma, por un metaprograma de plantilla. [3]
Las plantillas son diferentes a las macros . Una macro es un fragmento de código que se ejecuta en tiempo de compilación y realiza una manipulación textual del código que se va a compilar (por ejemplo, macros C ++ ) o manipula el árbol de sintaxis abstracto que produce el compilador (por ejemplo, macros Rust o Lisp ). Las macros textuales son notablemente más independientes de la sintaxis del lenguaje que se manipula, ya que simplemente cambian el texto en memoria del código fuente justo antes de la compilación.
Los metaprogramas de plantilla no tienen variables mutables , es decir, ninguna variable puede cambiar el valor una vez que se ha inicializado, por lo tanto, la metaprogramación de plantilla puede verse como una forma de programación funcional . De hecho, muchas implementaciones de plantillas implementan el control de flujo solo a través de la recursividad , como se ve en el siguiente ejemplo.
Usando la metaprogramación de plantillas
Aunque la sintaxis de la metaprogramación de plantillas suele ser muy diferente del lenguaje de programación con el que se utiliza, tiene usos prácticos. Algunas razones comunes para usar plantillas son implementar programación genérica (evitando secciones de código que son similares excepto por algunas variaciones menores) o realizar una optimización automática en tiempo de compilación, como hacer algo una vez en tiempo de compilación en lugar de cada vez que se ejecuta el programa. por ejemplo, haciendo que el compilador desenrolle los bucles para eliminar los saltos y las disminuciones en el recuento de bucles cada vez que se ejecuta el programa.
Generación de clases en tiempo de compilación
Lo que significa exactamente "programar en tiempo de compilación" se puede ilustrar con un ejemplo de una función factorial, que en C ++ sin plantilla se puede escribir usando la recursividad de la siguiente manera:
unsigned int factorial ( unsigned int n ) { return n == 0 ? 1 : n * factorial ( n - 1 ); }// Ejemplos de uso: // factorial (0) produciría 1; // factorial (4) daría como resultado 24.
El código anterior se ejecutará en tiempo de ejecución para determinar el valor factorial de los literales 0 y 4. Al usar la metaprogramación de plantilla y la especialización de plantilla para proporcionar la condición final para la recursividad, los factoriales usados en el programa, ignorando cualquier factorial no usado, pueden ser calculado en tiempo de compilación por este código:
plantilla < unsigned int n > struct factorial { enum { value = n * factorial < n - 1 > :: value }; };plantilla <> estructura factorial < 0 > { enumeración { valor = 1 }; };// Ejemplos de uso: // factorial <0> :: valor produciría 1; // factorial <4> :: valor daría como resultado 24.
El código anterior calcula el valor factorial de los literales 0 y 4 en tiempo de compilación y usa los resultados como si fueran constantes precalculadas. Para poder usar plantillas de esta manera, el compilador debe conocer el valor de sus parámetros en el momento de la compilación, lo cual tiene la condición previa natural de que el factorial
En C ++ 11 y C ++ 20 , constexpr y consteval se introdujeron para permitir que el compilador ejecutara código. Usando constexpr y consteval, se puede usar la definición factorial recursiva habitual con la sintaxis sin plantilla. [4]
Optimización de código en tiempo de compilación
El ejemplo factorial anterior es un ejemplo de optimización de código en tiempo de compilación en el que todos los factoriales utilizados por el programa se compilan previamente y se inyectan como constantes numéricas en la compilación, lo que ahorra tanto la sobrecarga del tiempo de ejecución como la huella de memoria. Sin embargo, es una optimización relativamente menor.
Como otro ejemplo más significativo de desenrollado de bucles en tiempo de compilación , la metaprogramación de plantillas se puede utilizar para crear clases de vectores de longitud n (donde n se conoce en tiempo de compilación). La ventaja sobre un vector de longitud n más tradicional es que los bucles se pueden desenrollar, lo que da como resultado un código muy optimizado. Como ejemplo, considere el operador de suma. Una suma de vectores de longitud n podría escribirse como
template < int length > Vector < length > & Vector < length > :: operator + = ( const Vector < length > & rhs ) { for ( int i = 0 ; i < length ; ++ i ) value [ i ] + = dcha . valor [ i ]; devolver * esto ; }
Cuando el compilador crea una instancia de la plantilla de función definida anteriormente, se puede producir el siguiente código: [ cita requerida ]
plantilla <> Vector < 2 > & Vector < 2 > :: operador + = ( const Vector < 2 > & rhs ) { valor [ 0 ] + = rhs . valor [ 0 ]; valor [ 1 ] + = dcha . valor [ 1 ]; devolver * esto ; }
El optimizador del compilador debería poder desenrollar el for
ciclo porque el parámetro de plantilla length
es una constante en el momento de la compilación.
Sin embargo, tenga cuidado y sea precavido, ya que esto puede causar que el código se hinche, ya que se generará un código desenrollado por separado para cada 'N' (tamaño de vector) con el que cree una instancia.
Polimorfismo estático
El polimorfismo es una instalación de programación estándar común donde los objetos derivados se pueden usar como instancias de su objeto base, pero donde se invocarán los métodos de los objetos derivados, como en este código.
class Base { public : virtual void method () { std :: cout << "Base" ; } virtual ~ Base () {} };clase Derived : public Base { public : virtual void method () { std :: cout << "Derived" ; } };int main () { Base * pBase = new Derived ; pBase -> método (); // genera "Derivado" delete pBase ; return 0 ; }
donde todas las invocaciones de virtual
métodos serán las de la clase más derivada. Este comportamiento dinámicamente polimórfico se obtiene (normalmente) mediante la creación de tablas de búsqueda virtuales para clases con métodos virtuales, tablas que se recorren en tiempo de ejecución para identificar el método que se invocará. Por lo tanto, el polimorfismo en tiempo de ejecución implica necesariamente una sobrecarga de ejecución (aunque en las arquitecturas modernas la sobrecarga es pequeña).
Sin embargo, en muchos casos, el comportamiento polimórfico necesario es invariante y se puede determinar en el momento de la compilación. Luego, el patrón de plantilla curiosamente recurrente (CRTP) se puede usar para lograr un polimorfismo estático , que es una imitación del polimorfismo en el código de programación, pero que se resuelve en tiempo de compilación y, por lo tanto, elimina las búsquedas de tablas virtuales en tiempo de ejecución. Por ejemplo:
plantilla < clase derivada > estructura base { interfaz vacía () { // ... static_cast < derivada *> ( esto ) -> implementación (); // ... } }; estructura derivada : base < derivada > { implementación nula () { // ... } };
Aquí, la plantilla de la clase base aprovechará el hecho de que los cuerpos de las funciones miembro no se instancian hasta después de sus declaraciones, y usará miembros de la clase derivada dentro de sus propias funciones miembro, mediante el uso de a static_cast
, por lo tanto en la compilación generando un objeto composición con características polimórficas. Como ejemplo de uso en el mundo real, el CRTP se usa en la biblioteca de iteradores de Boost . [5]
Otro uso similar es el " truco de Barton-Nackman ", a veces denominado "expansión de plantilla restringida", donde la funcionalidad común se puede colocar en una clase base que no se usa como un contrato sino como un componente necesario para hacer cumplir el comportamiento conforme mientras se minimiza Redundancia de código.
Generación de tablas estáticas
El beneficio de las tablas estáticas es el reemplazo de cálculos "costosos" con una operación simple de indexación de arreglos (para ejemplos, vea la tabla de búsqueda ). En C ++, existe más de una forma de generar una tabla estática en tiempo de compilación. La siguiente lista muestra un ejemplo de cómo crear una tabla muy simple usando estructuras recursivas y plantillas variadas . La mesa tiene un tamaño de diez. Cada valor es el cuadrado del índice.
#include #include constexpr int TAMAÑO_TABLA = 10 ;/ ** * Plantilla variable para una estructura auxiliar recursiva. * / plantilla < int INDICE = 0 , int ... D > struct Helper : Helper < INDEX + 1 , D ..., INDEX * INDEX > { };/ ** * Especialización de la plantilla para finalizar la recursividad cuando el tamaño de la tabla alcanza TABLE_SIZE. * / plantilla < int ... D > struct Helper < TABLE_SIZE , D ... > { static constexpr std :: array < int , TABLE_SIZE > table = { D ... }; };constexpr std :: array < int , TABLE_SIZE > tabla = Ayudante <> :: tabla ;enum { CUATRO = tabla [ 2 ] // uso del tiempo de compilación };int main () { for ( int i = 0 ; i < TABLE_SIZE ; i ++ ) { std :: cout << tabla [ i ] << std :: endl ; // uso en tiempo de ejecución } std :: cout << "CUATRO:" << CUATRO << std :: endl ; }
La idea detrás de esto es que el struct Helper hereda recursivamente de una estructura con un argumento de plantilla más (en este ejemplo calculado como INDICE * INDEX) hasta que la especialización de la plantilla finaliza la recursividad en un tamaño de 10 elementos. La especialización simplemente usa la lista de argumentos de la variable como elementos para la matriz. El compilador producirá un código similar al siguiente (tomado de clang llamado con -Xclang -ast-print -fsyntax-only).
plantilla < int INDICE = 0 , int ... D > struct Helper : Helper < INDEX + 1 , D ..., INDEX * INDEX > { }; plantilla <> struct Helper < 0 , <>> : Helper < 0 + 1 , 0 * 0 > { }; plantilla <> struct Helper < 1 , < 0 >> : Helper < 1 + 1 , 0 , 1 * 1 > { }; plantilla <> struct Helper < 2 , < 0 , 1 >> : Helper < 2 + 1 , 0 , 1 , 2 * 2 > { }; plantilla <> struct Helper < 3 , < 0 , 1 , 4 >> : Helper < 3 + 1 , 0 , 1 , 4 , 3 * 3 > { }; plantilla <> struct Helper < 4 , < 0 , 1 , 4 , 9 >> : Helper < 4 + 1 , 0 , 1 , 4 , 9 , 4 * 4 > { }; plantilla <> struct Helper < 5 , < 0 , 1 , 4 , 9 , 16 >> : Helper < 5 + 1 , 0 , 1 , 4 , 9 , 16 , 5 * 5 > { }; plantilla <> struct Helper < 6 , < 0 , 1 , 4 , 9 , 16 , 25 >> : Helper < 6 + 1 , 0 , 1 , 4 , 9 , 16 , 25 , 6 * 6 > { }; template <> struct Helper < 7 , < 0 , 1 , 4 , 9 , 16 , 25 , 36 >> : Helper < 7 + 1 , 0 , 1 , 4 , 9 , 16 , 25 , 36 , 7 * 7 > { }; template <> struct Helper < 8 , < 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 >> : Helper < 8 + 1 , 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 8 * 8 > { }; template <> struct Helper < 9 , < 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 >> : Helper < 9 + 1 , 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 9 * 9 > { }; template <> struct Helper < 10 , < 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 >> { static constexpr std :: array < int , TABLE_SIZE > table = { 0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 }; };
Desde C ++ 17, esto se puede escribir de manera más legible como:
#include #include constexpr int TAMAÑO_TABLA = 10 ;constexpr std :: array < int , TABLE_SIZE > table = [] { // O: constexpr auto table std :: array < int , TABLE_SIZE > A = {}; para ( sin signo i = 0 ; i < TABLE_SIZE ; i ++ ) { A [ i ] = i * i ; } return A ; } ();enum { CUATRO = tabla [ 2 ] // uso del tiempo de compilación };int main () { for ( int i = 0 ; i < TABLE_SIZE ; i ++ ) { std :: cout << tabla [ i ] << std :: endl ; // uso en tiempo de ejecución } std :: cout << "CUATRO:" << CUATRO << std :: endl ; }
Para mostrar un ejemplo más sofisticado, el código de la siguiente lista se ha ampliado para tener un ayudante para el cálculo del valor (en preparación para cálculos más complicados), un desplazamiento específico de la tabla y un argumento de plantilla para el tipo de los valores de la tabla (por ejemplo, uint8_t, uint16_t, ...).
#include #include constexpr int TAMAÑO_TABLA = 20 ; constexpr int DESPLAZAMIENTO = 12 ;/ ** * Plantilla para calcular una sola entrada de tabla * / plantilla < typename VALUETYPE , VALUETYPE OFFSET , VALUETYPE INDEX > struct ValueHelper { static constexpr VALUETYPE value = OFFSET + INDEX * INDEX ; };/ ** * Plantilla variable para una estructura auxiliar recursiva. * / template < typename VALUETYPE , VALUETYPE OFFSET , int N = 0 , VALUETYPE ... D > struct Helper : Helper < VALUETYPE , OFFSET , N + 1 , D ..., ValueHelper < VALUETYPE , OFFSET , N > :: valor > { };/ ** * Especialización de la plantilla para finalizar la recursividad cuando el tamaño de la tabla alcanza TABLE_SIZE. * / template < typename VALUETYPE , VALUETYPE OFFSET , VALUETYPE ... D > struct Helper < VALUETYPE , OFFSET , TABLE_SIZE , D ... > { static constexpr std :: array < VALUETYPE , TABLE_SIZE > table = { D ... } ; };constexpr std :: matriz < uint16_t , TABLE_SIZE > tabla = Ayudante < uint16_t , OFFSET > :: tabla ;int main () { for ( int i = 0 ; i < TABLE_SIZE ; i ++ ) { std :: cout << tabla [ i ] << std :: endl ; } }
Que podría escribirse de la siguiente manera usando C ++ 17:
#include #include constexpr int TAMAÑO_TABLA = 20 ; constexpr int DESPLAZAMIENTO = 12 ;plantilla < typename VALUETYPE , VALUETYPE OFFSET > constexpr std :: array < VALUETYPE , TABLE_SIZE > table = [] { // O: constexpr auto table std :: array < VALUETYPE , TABLE_SIZE > A = {}; para ( sin signo i = 0 ; i < TABLE_SIZE ; i ++ ) { A [ i ] = OFFSET + i * i ; } return A ; } ();int main () { for ( int i = 0 ; i < TABLE_SIZE ; i ++ ) { std :: cout << tabla < uint16_t , OFFSET > [ i ] << std :: endl ; } }
Conceptos
El estándar C ++ 20 trajo a los programadores de C ++ una nueva herramienta para la programación de metaplantillas, conceptos. [6]
Los conceptos permiten a los programadores especificar requisitos para el tipo, para hacer posible la creación de instancias de la plantilla. El compilador busca una plantilla con el concepto que tenga los requisitos más altos.
Aquí hay un ejemplo del famoso problema de buzz de Fizz resuelto con la Meta Programación de Plantillas.
#include // para una impresión bonita de tipos#include #include / ** * Tipo de representación de palabras para imprimir * / struct Fizz {}; struct Buzz {}; struct FizzBuzz {}; plantilla < size_t _N > struct número { constexpr estática size_t N = _N ; };/ ** * Conceptos usados para definir condiciones para especializaciones * / template < typename Any > concepto has_N = requiere { requiere Any :: N - Any :: N == 0 ; }; plantilla < nombre de tipo A > concepto fizz_c = has_N < A > && requiere { requiere A :: N % 3 == 0 ; }; plantilla < nombre de tipo A > concepto buzz_c = has_N < A > && requiere { requiere A :: N % 5 == 0 ;}; plantilla < nombre de tipo A > concepto fizzbuzz_c = fizz_c < A > && buzz_c < A > ;/ ** * Al especializar la estructura `res`, con los requisitos de conceptos, se realiza la instalación adecuada * / template < typename X > struct res ; plantilla < fizzbuzz_c X > struct res < X > { usando result = FizzBuzz ; }; plantilla < fizz_c X > struct res < X > { usando result = Fizz ; }; plantilla < buzz_c X > struct res < X > { usando result = Buzz ; }; plantilla < has_N X > struct res < X > { usando result = X ; };/ ** * Predeclaración del concentrador * / template < size_t cnt , typename ... Args > struct concatenator ;/ ** * Forma recursiva de concatenar los siguientes tipos * / template < size_t cnt , typename ... Args > struct concatenator < cnt , std :: tuple < Args ... >> { usando type = typename concatenator < cnt - 1 , std :: tupla < nombre de tipo res < número < cnt > > :: consecuencia , Args ... >> :: tipo ;};/ ** * Caso base * / template < typename ... Args > struct concatenator < 0 , std :: tuple < Args ... >> { using type = std :: tuple < Args ... > ;};/ ** * Captador de resultado final * / template < size_t Amount > usando fizz_buzz_full_template = typename concatenator < Amount - 1 , std :: tuple < typename res < number < Amount >> :: result >> :: type ;int main () { // imprimiendo el resultado con boost, por lo que es claro std :: cout << boost :: typeindex :: type_id < fizz_buzz_full_template < 100 >> (). pretty_name () << std :: endl ; / * Resultado: std :: tuple , número <2ul>, Fizz, número <4ul>, Buzz, Fizz, número <7ul>, número <8ul>, Fizz, Buzz, número <11ul>, Fizz , número <13ul>, número <14ul>, FizzBuzz, número <16ul>, número <17ul>, Fizz, número <19ul>, Buzz, Fizz, número <22ul>, número <23ul>, Fizz, Buzz, número < 26ul>, Fizz, número <28ul>, número <29ul>, FizzBuzz, número <31ul>, número <32ul>, Fizz, número <34ul>, Buzz, Fizz, número <37ul>, número <38ul>, Fizz, Buzz, número <41ul>, Fizz, número <43ul>, número <44ul>, FizzBuzz, número <46ul>, número <47ul>, Fizz, número <49ul>, Buzz, Fizz, número <52ul>, número <53ul >, Fizz, Buzz, número <56ul>, Fizz, número <58ul>, número <59ul>, FizzBuzz, número <61ul>, número <62ul>, Fizz, número <64ul>, Buzz, Fizz, número <67ul> , número <68ul>, Fizz, Buzz, número <71ul>, Fizz, número <73ul>, número <74ul>, FizzBuzz, número <76ul>, número <77ul>, Fizz, número <79ul>, Buzz, Fizz, número <82ul>, número <83ul>, Fizz, Buzz, número <86ul>, Fizz, número <88ul>, número <89ul>, FizzBuzz, número <91ul>, número <92ul>, Fizz, n úmero>umber <94ul>, Buzz, Fizz, número <97ul>, número <98ul>, Fizz, Buzz> * / }
Beneficios e inconvenientes de la metaprogramación de plantillas
- Compensación de tiempo de compilación versus tiempo de ejecución
- Si se utiliza una gran cantidad de metaprogramación de plantillas.
- Programación genérica
- La metaprogramación de plantillas permite al programador centrarse en la arquitectura y delegar en el compilador la generación de cualquier implementación requerida por el código del cliente. Por lo tanto, la metaprogramación de plantillas puede lograr código verdaderamente genérico, facilitando la minimización del código y una mejor capacidad de mantenimiento [ cita requerida ] .
- Legibilidad
- Con respecto a C ++, la sintaxis y los modismos de la metaprogramación de plantilla son esotéricos en comparación con la programación C ++ convencional, y los metaprogramas de plantilla pueden ser muy difíciles de entender. [7] [8]
Ver también
- El fallo de sustitución no es un error (SFINAE)
- Metaprogramación
- Preprocesador
- Polimorfismo paramétrico
- Plantillas de expresión
- Plantillas Variadic
- Ejecución de la función en tiempo de compilación
Referencias
- ^ Scott Meyers (12 de mayo de 2005). C ++ eficaz: 55 formas específicas de mejorar sus programas y diseños . Educación Pearson. ISBN 978-0-13-270206-5.
- ^ Ver Historia de TMP en Wikilibros
- ^ Veldhuizen, Todd L. "Las plantillas de C ++ son Turing Complete". CiteSeerX 10.1.1.14.3670 . Cite journal requiere
|journal=
( ayuda ) - ^ http://www.cprogramming.com/c++11/c++11-compile-time-processing-with-constexpr.html
- ^ http://www.boost.org/libs/iterator/doc/iterator_facade.html
- ^ https://en.cppreference.com/w/cpp/language/constraints
- ^ Czarnecki, K .; O'Donnell, J .; Striegnitz, J .; Taha, Walid Mohamed (2004). "Implementación de DSL en metaocaml, plantilla haskell y C ++" (PDF) . Universidad de Waterloo, Universidad de Glasgow, Centro de Investigación Julich, Universidad Rice.
La metaprogramación de plantillas de C ++ sufre una serie de limitaciones, incluidos problemas de portabilidad debido a las limitaciones del compilador (aunque esto ha mejorado significativamente en los últimos años), falta de soporte de depuración o IO durante la instanciación de plantillas, tiempos de compilación prolongados, errores largos e incomprensibles, mala legibilidad del código e informes de errores deficientes.
Cite journal requiere|journal=
( ayuda ) - ^ Sheard, Tim; Jones, Simon Peyton (2002). "Metaprogramación de plantillas para Haskell" (PDF) . ACM 1-58113-415-0 / 01/0009.
El provocativo artículo de Robinson identifica las plantillas de C ++ como un éxito importante, aunque accidental, del diseño del lenguaje C ++. A pesar de la naturaleza extremadamente barroca de la metaprogramación de plantillas, las plantillas se utilizan de formas fascinantes que se extienden más allá de los sueños más locos de los diseñadores de lenguajes. Quizás sorprendentemente, en vista del hecho de que las plantillas son programas funcionales, los programadores funcionales han tardado en capitalizar el éxito de C ++
Cite journal requiere|journal=
( ayuda )
- Eisenecker, Ulrich W. (2000). Programación generativa: métodos, herramientas y aplicaciones . Addison-Wesley. ISBN 0-201-30977-7.
- Alexandrescu, Andrei (2003). Diseño C ++ moderno: programación genérica y patrones de diseño aplicados . Addison-Wesley. ISBN 3-8266-1347-3.
- Abrahams, David ; Gurtovoy, Aleksey (enero de 2005). Metaprogramación de plantillas C ++: conceptos, herramientas y técnicas de Boost y más allá . Addison-Wesley. ISBN 0-321-22725-5.
- Vandevoorde, David ; Josuttis, Nicolai M. (2003). Plantillas C ++: la guía completa . Addison-Wesley. ISBN 0-201-73484-2.
- Clavel, Manuel (16 de octubre de 2000). Reflexión en la reescritura de la lógica: fundamentos metalogicos y aplicaciones de la metaprogramación . ISBN 1-57586-238-7.
enlaces externos
- "La biblioteca de metaprogramación Boost (Boost MPL)" .
- "La biblioteca del espíritu" . (construido usando plantilla-metaprogramación)
- "La biblioteca Boost Lambda" . (use algoritmos STL fácilmente)
- Veldhuizen, Todd (mayo de 1995). "Usando metaprogramas de plantilla C ++" . Informe C ++ . 7 (4): 36–43. Archivado desde el original el 4 de marzo de 2009.
- "Plantilla Haskell" . (metaprogramación segura en Haskell)
- Brillante, Walter . "Plantillas revisadas" .(plantilla de metaprogramación en el lenguaje de programación D )
- Koskinen, Johannes . "Metaprogramación en C ++" (PDF) .
- Attardi, Giuseppe; Cisternino, Antonio. "Soporte de reflexión mediante metaprogramación de plantillas" (PDF) .
- Burton, Michael C .; Griswold, William G .; McCulloch, Andrew D .; Huber, Gary A. "Estructuras de datos estáticos". CiteSeerX 10.1.1.14.5881 . Cite journal requiere
|journal=
( ayuda ) - Amjad, Zeeshan. "Plantilla Meta Programación y Teoría de Números" .
- Amjad, Zeeshan. "Plantilla de metaprogramación y teoría de números: parte 2" .
- "Una biblioteca para la programación estilo LISP en C ++" .