Pregunta

yo descubrí metaprogramación de plantillas hace más de 5 años y disfruté muchísimo leyendo Diseño moderno en C++ pero nunca encontré la oportunidad de usarlo en la vida real.

Tener ¿Alguna vez usaste esta técnica en código real?

Colaboradores de Aumentar No es necesario aplicar ;o)

¿Fue útil?

Solución

Una vez utilicé metaprogramación de plantillas en C++ para implementar una técnica llamada "perturbación simbólica" para lidiar con entradas degeneradas en algoritmos geométricos.Al representar expresiones aritméticas como plantillas anidadas (es decir,básicamente escribiendo los árboles de análisis a mano) pude pasar todo el análisis de expresiones al procesador de plantillas.

Hacer este tipo de cosas con plantillas es más eficiente que, digamos, escribir árboles de expresión usando objetos y realizar el análisis en tiempo de ejecución.Es más rápido porque el árbol de expresiones modificado (perturbado) está disponible para el optimizador al mismo nivel que el resto de su código, por lo que obtiene todos los beneficios de la optimización, tanto dentro de sus expresiones como también (cuando sea posible) entre sus expresiones y el código circundante.

Por supuesto, podría lograr lo mismo implementando un pequeño DSL (lenguaje específico de dominio) para sus expresiones y pegando el código C++ traducido en su programa habitual.Eso le brindaría los mismos beneficios de optimización y también sería más legible, pero la desventaja es que debe mantener un analizador.

Otros consejos

He encontrado políticas, descritas en Diseño moderno de C++, realmente útiles en dos situaciones:

  1. Cuando desarrollo un componente, espero que se reutilice, pero de una manera ligeramente diferente.La sugerencia de Alexandrescu de usar una política para reflejar un diseño se ajusta muy bien aquí: me ayuda a obtener preguntas más allá de las preguntas como: "Podría hacer esto con un hilo de fondo, pero ¿qué pasa si alguien más tarde quiere hacerlo en rodajas de tiempo?" Está bien, solo escribo mi clase para aceptar una Concurrencipolicy e implementar la que necesito en este momento.Entonces al menos sé que la persona que viene detrás de mí puede redactar e implementar una nueva póliza cuando la necesite, sin tener que reelaborar totalmente mi diseño.Advertencia:A veces tengo que controlarme o esto puede salirse de control. Recuerda el YAGNI ¡principio!

  2. Cuando intento refactorizar varios bloques de código similares en uno.Por lo general, el código se copiará, pegará y modificará ligeramente porque, de lo contrario, habría tenido demasiada lógica if/else, o porque los tipos involucrados eran demasiado diferentes.He descubierto que las políticas a menudo permiten una versión limpia y única, donde la lógica tradicional o la herencia múltiple no lo harían.

Lo he usado en los bucles internos del código de gráficos de un juego, donde quieres cierto nivel de abstracción y modularidad pero no puedes pagar el costo de sucursales o llamadas virtuales.En general, fue una solución mejor que la proliferación de funciones de casos especiales escritas a mano.

La metaprogramación de plantillas y las plantillas de expresión se están volviendo más populares en la comunidad científica como métodos de optimización que descargan parte del esfuerzo computacional en el compilador manteniendo cierta abstracción.El código resultante es más grande y menos legible, pero he usado estas técnicas para acelerar las bibliotecas de álgebra lineal y los métodos de cuadratura en las bibliotecas FEM.

Para lecturas específicas de la aplicación, Todd Veldhuizen es un gran nombre en esta área.Un libro popular es C++ y computación numérica orientada a objetos para científicos e ingenieros por Daoqi Yang.

La metaprogramación de plantillas es una técnica maravillosa y poderosa al escribir C++ bibliotecas.Lo he usado algunas veces en soluciones personalizadas, pero generalmente una solución C++ de estilo antiguo menos elegante es más fácil de obtener mediante la revisión del código y más fácil de mantener para otros usuarios.

Sin embargo, he aprovechado mucho la metaprogramación de plantillas al escribir componentes/bibliotecas reutilizables.No me refiero a nada tan grande, algunas de las cosas de Boost, solo componentes más pequeños que se reutilizarán con frecuencia.

Utilicé TMP para un sistema singleton donde el usuario podía especificar qué tipo de singleton deseaba.La interfaz era muy básica.Debajo estaba impulsado por TMP pesado.

template< typename T >
T& singleton();

template< typename T >
T& zombie_singleton();

template< typename T >
T& phoenix_singleton();

Otro uso exitoso fue simplificar nuestra capa IPC.Está construido con el estilo clásico de OO.Cada mensaje debe derivar de una clase base abstracta y anular algunos métodos de serialización.Nada demasiado extremo, pero genera una gran cantidad de código repetitivo.

Le agregamos algo de TMP y automatizamos la generación de todo el código para el caso simple de mensajes que contienen solo datos POD.Los mensajes TMP todavía usaban el backend OO pero reducen enormemente la cantidad de código repetitivo.El TMP también se utilizó para generar el mensaje visitante.Con el tiempo, todos nuestros mensajes migraron al método TMP.Era más fácil y con menos código construir una estructura POD simple solo para pasar mensajes y agregar las pocas (tal vez 3) líneas necesarias para que el TMP generara las clases que derivar un nuevo mensaje para enviar una clase normal a través del IPC. estructura.

Utilizo metaprogramación de plantillas todo el tiempo, pero en D, no en C++.El metalenguaje de plantilla de C++ se diseñó originalmente para la parametrización de tipos simples y se convirtió en un metalenguaje completo de Turing casi por accidente.Se trata, por tanto, de una lona de Turing que sólo Andrei Alexandrescu, no los simples mortales, puede utilizar.

El sublenguaje de plantilla de D, por otro lado, en realidad fue diseñado para la metaprogramación más allá de la simple parametrización de tipos.Andrei Alexandrescu Parece que le encanta, pero otras personas pueden entender sus plantillas D.También es lo suficientemente poderoso como para que alguien haya escrito un trazador de rayos en tiempo de compilación en él como prueba de concepto.

Supongo que el metaprograma más útil/no trivial que he escrito en D fue una plantilla de función que, dado un tipo de estructura como parámetro de plantilla y una lista de nombres de encabezados de columna en un orden correspondiente a las declaraciones de variables en la estructura como tiempo de ejecución parámetro, leerá un archivo CSV y devolverá una matriz de estructuras, una para cada fila, con cada campo de estructura correspondiente a una columna.Todas las conversiones de tipos (cadena a flotante, int, etc.) se realizan automáticamente, según los tipos de campos de la plantilla.

Otra buena, que en su mayoría funciona, pero aún no maneja algunos casos adecuadamente, es una plantilla de función de copia profunda que maneja estructuras, clases y matrices correctamente.Utiliza solo reflexión/introspección en tiempo de compilación, de modo que puede trabajar con estructuras que, a diferencia de las clases completas, no tienen capacidades de reflexión/introspección en tiempo de ejecución en D porque se supone que son livianas.

La mayoría de los programadores que utilizan metaprogramación de plantillas la utilizan indirectamente, a través de bibliotecas como boost.Probablemente ni siquiera sepan lo que sucede detrás de escena, solo que hace que la sintaxis de ciertas operaciones sea mucho más fácil.

Lo he usado bastante con código DSP, especialmente FFT, buffers circulares de tamaño fijo, transformaciones hadamard y similares.

Para aquellos familiarizados con la biblioteca de plantillas de Oracle (OTL), impulsar::cualquiera y Loki biblioteca (la que se describe en Diseño moderno de C++) aquí está la prueba de concepto del código TMP que le permite almacenar una fila de otl_stream en vector<boost::any> contenedor y acceder a los datos por número de columna.Y 'Sí', lo voy a incorporar en el código de producción.

#include <iostream>
#include <vector>
#include <string>
#include <Loki/Typelist.h>
#include <Loki/TypeTraits.h>
#include <Loki/TypeManip.h>
#include <boost/any.hpp>
#define OTL_ORA10G_R2
#define OTL_ORA_UTF8
#include <otlv4.h>

using namespace Loki;

/* Auxiliary structs */
template <int T1, int T2>
struct IsIntTemplateEqualsTo{
    static const int value = ( T1 == T2 );
};

template <int T1>
struct ZeroIntTemplateWorkaround{
    static const int value = ( 0 == T1? 1 : T1 );
};


/* Wrapper class for data row */
template <class TList>
class T_DataRow;


template <>
class T_DataRow<NullType>{
protected:
    std::vector<boost::any> _data;
public:
    void Populate( otl_stream& ){};
};


/* Note the inheritance trick that enables to traverse Typelist */
template <class T, class U>
class T_DataRow< Typelist<T, U> >:public T_DataRow<U>{
public:
    void Populate( otl_stream& aInputStream ){
        T value;
        aInputStream >> value;
        boost::any anyValue = value;
        _data.push_back( anyValue );

        T_DataRow<U>::Populate( aInputStream );
    }

    template <int TIdx>
    /* return type */
    Select<
        IsIntTemplateEqualsTo<TIdx, 0>::value,
        typename T,
        typename TL::TypeAt<
            U,
            ZeroIntTemplateWorkaround<TIdx>::value - 1
        >::Result
    >::Result
    /* sig */
    GetValue(){
    /* body */
        return boost::any_cast<
            Select<
                IsIntTemplateEqualsTo<TIdx, 0>::value,
                typename T,
                typename TL::TypeAt<
                    U,
                    ZeroIntTemplateWorkaround<TIdx>::value - 1
                >::Result
            >::Result
        >( _data[ TIdx ] );
    }
};


int main(int argc, char* argv[])
{
    db.rlogon( "AMONRAWMS/WMS@amohpadb.world" ); // connect to Oracle
    std::cout<<"Connected to oracle DB"<<std::endl;
    otl_stream o( 1, "select * from blockstatuslist", db );

    T_DataRow< TYPELIST_3( int, int, std::string )> c;
    c.Populate( o );
    typedef enum{ rcnum, id, name } e_fields; 
    /* After declaring enum you can actually acess columns by name */
    std::cout << c.GetValue<rcnum>() << std::endl;
    std::cout << c.GetValue<id>() << std::endl;
    std::cout << c.GetValue<name>() << std::endl;
    return 0;
};

Para aquellos que no están familiarizados con las bibliotecas mencionadas.

El problema con el contenedor otl_stream de OTL es que uno puede acceder a los datos de las columnas solo en orden secuencial declarando variables del tipo apropiado y aplicando el operator >> al objeto otl_stream de la siguiente manera:

otl_stream o( 1, "select * from blockstatuslist", db );
int rcnum; 
int id;
std::string name;
o >> rcnum >> id >> name; 

No siempre es conveniente.La solución es escribir alguna clase contenedora y completarla con datos de otl_stream.El deseo es poder declarar la lista de tipos de columnas y luego:

  • tomar el tipo T de la columna
  • declarar variable de ese tipo
  • aplicar olt_stream::operator >>(T&)
  • almacenar el resultado (en el vector de boost::any)
  • tome el tipo de la siguiente columna y repita hasta que se procesen todas las columnas

Puedes hacer todo esto con la ayuda de Loki. Typelist estructura, especialización de plantillas y herencia.

Con la ayuda de las construcciones de la biblioteca de Loki, también puedes generar un montón de funciones GetValue que devuelven valores del tipo apropiado, deduciéndolos del número de columna (en realidad, el número de tipo en Typelist).

No, no lo he usado en el código de producción.

¿Por qué?

  1. Tenemos que admitir más de 6 plataformas con nativo plataforma compiladores.Es bastante difícil usar STL en este entorno y mucho menos las técnicas de plantilla modernas.
  2. Los desarrolladores ya no parecen mantener el ritmo de los avances de C++.Usamos C ++ cuando tenemos que hacerlo.Tenemos código heredado con diseños heredados.El nuevo código se realiza en otra cosa, por ejemplo, Java, JavaScript, Flash.

Casi 8 meses después de hacer esto, finalmente usé algo de TMP, uso un Lista de tipos de interfaces para implementar QueryInterface en una clase base.

Lo uso con boost::statechart para máquinas de estados grandes.

Sí, lo hice, principalmente para hacer algunas cosas que se parecen a escribir con pato cuando estaba empaquetando una API heredada en una interfaz C++ más moderna.

No hagas eso.La razón detrás de esto es la siguiente:Por la naturaleza de la metaprogramación de plantillas, si alguna parte de su lógica se realiza en tiempo de compilación, cada lógica de la que depende también debe realizarse en tiempo de compilación.Una vez que lo inicies, haz una parte de tu lógica en el momento de la compilación, no hay retorno.La bola de nieve seguirá rodando y no habrá forma de detenerla.

Por ejemplo, no puedes iterar sobre los elementos de un boost::tuple<>, porque solo puedes acceder a ellos en tiempo de compilación.Debe utilizar metaprogramación de plantillas para lograr lo que hubiera sido fácil y directo en C++, y esto siempre Esto sucede cuando los usuarios de C++ no tienen el cuidado suficiente de no mover demasiadas cosas al tiempo de compilación.A veces es difícil ver cuándo un determinado uso de la lógica de tiempo de compilación se volvería problemático y, a veces, los programadores están ansiosos por probar lo que han leído en Alexandrescu.En cualquier caso, en mi opinión, es una muy mala idea.

Muchos programadores no usan mucho las plantillas debido al pobre soporte del compilador hasta hace poco.Sin embargo, si bien las plantillas han tenido muchos problemas en el pasado, los compiladores más nuevos tienen un soporte mucho mejor.Escribo código que tiene que funcionar con GCC en Mac y Linux, así como con Microsoft Visual C++ y es sólo con GCC 4 y VC++ 2005 que estos compiladores han soportado realmente bien el estándar.

La programación genérica mediante plantillas no es algo que necesite todo el tiempo, pero definitivamente es un código útil para tener en su caja de herramientas.

Las clases contenedoras de ejemplo obvias pero las plantillas también son útiles para muchas otras cosas.Dos ejemplos de mi propio trabajo son:

  • Punteros inteligentes (p. ej.Conteo de referencias, copia en escritura, etc.)
  • Clases de apoyo en matemáticas como Matrices, vectores, splines, etc.que necesitan admitir una variedad de tipos de datos y seguir siendo eficientes.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top