sábado, 24 de diciembre de 2011

El riesgo de la meta-progamación en C++


El riesgo de abusar de los templates es este: una compilación excesivamente costosa, casi inviable con muchos ordenadores. En la captura se muestra un ejemplo de corbasim donde se recorre varias veces durante la compilación una lista de tipos de 200 elementos. A su vez, cada iteración sobre esos 200 elementos conlleva iteraciones sobre otras listas de tipos derivadas. El resultado es una compilación que llega a provocar paginación, al consumir toda la memoria disponible (y más).

La versión de GCC que utilizo es la 4.4.3. Es probable que en versiones posteriores mejore algo la compilación, pero aún así a día de hoy resulta inviable una compilación de más de media hora para simples herramientas de test. toca optimizar la compilación para generar código eficiente en un tiempo razonable.

domingo, 11 de diciembre de 2011

Boost.Fusion: introspección en tiempo de compilación para estructuras en C++

Una de las librerías que más me gusta de las Boost C++ Libraries es la Boost.Fusion. No voy a entrar en detalle en lo que ofrece esta librería; básicamente permite el manejo de tuplas de elementos de distintos tipos.

Lo bueno de las tuplas que ofrece esta librería son las meta-funciones que ofrecen introspección en tiempo de compilación sobre estas tuplas. El disponer de esta introspección permite escribir código genérico sea cual sea el número y tipo de los elementos de estas tuplas.

La característica que más me gusta de esta librería es que permite poder adaptar estructuras convencionales de C++ para ser utilizadas como lo que realmente son: tuplas de elementos de distintos tipos. De esta forma, podemos utilizar las meta-funciones de esta librería sobre estructuras y así escribir código genérico. Creo que el poder adaptar estructuras para poder tener cierta introspección -siempre en tiempo de compilación- cubre una de las carencias principales del lenguaje C++.

Esta característica la he usado en varios proyectos. Uno de ellos es CORBASIM, donde se utiliza para generar automáticamente -en tiempo de compilación- formularios para introducir datos. También se utiliza para serializar estas estructuras en formato JSON.

Lo malo de esta característica, es que para poder utilizarla hay que definir dos veces la estructura: una -la propia estructura-, y otra con la macro BOOST_FUSION_ADAPT_STRUCT. Es decir, escribir dos veces la misma información semántica. En CORBASIM esto no supone ningún problema, ya que todo el código es generado a partir de ficheros IDL. Pero en otros ámbitos donde no hay un DSL ni código generado de por medio, esta duplicidad es un poco incómoda. La falta de esta introspección de por sí en el lenguaje es -en mi opinión- la mayor de sus carencias.

Para intentar cubrir esta carencia estaría bien disponer de una herramienta que realice esta adaptación de forma automática, generando código C++ adicional a partir de código C++. Esto no sería muy difícil; bastaría recorrer el AST del código en el que se definen las estructuras que queremos adaptar. El AST se puede recorrer por ejemplo con un plugin de GCC, solo hay que echarle tiempo. En una prueba rápida, he conseguido esbozar esta herramienta utilizando Dehydra.

Aunque Dehydra permite recorrer el AST mediante código JavaScript con el fin de implementar validadores de código C++, también se podría utilizar para generar automáticamente este código adicional. El código JavaScript del esbozo que he realizado con Dehydra para generar este código es tan sencillo como el que sigue:

function getHeader(name)
{
    return name.replace('::','_').toUpperCase() + '_ADAPTED';
}

function process_type(type)
{
    if (type.kind == "struct" && !type.typedef 
            && type.members.length > 0)
    {
        header = getHeader(type.name);
        print('#ifndef ' + header);
        print('#define ' + header);

        print('// Generated code for ' + type.name + 
                ' defined in ' + type.loc);
        // Uso de Boost.Fusion
        print('BOOST_FUSION_ADAPT_STRUCT (');
        print('\t' + type.name + ',');
        for each(let m in type.members)
        {
            print('\t(' + m.type.name + ', ' + m.shortName+')');
        }
        print(')');
        print('');

        print('#endif // ' + header);
        print('');
    }
}


Esta herramienta me parece interesante para poder generalizar parte del trabajo realizado en CORBASIM, haciéndolo independiente de CORBA. Extenderé la idea :)