Metaprogramación en C++ y en D


El mecanismo de plantillas en C++ solo se volvió útil accidentalmente para la metaprogramación de plantillas. Por otro lado, D's fue diseñado específicamente para facilitar esto. Y aparentemente es aún más fácil de entender (o eso he oído).

No tengo experiencia con D, pero tengo curiosidad, ¿qué es lo que puedes hacer en D y no en C++, cuando se trata de metaprogramación de plantillas?

Author: Faisal Vali, 2011-09-04

10 answers

Las dos cosas más importantes que ayudan a la metaprogramación de plantillas en D son las restricciones de plantilla y static if, ambos de los cuales C++ podría agregar teóricamente y que lo beneficiarían enormemente.

Las restricciones de plantilla le permiten poner una condición en una plantilla que debe ser verdadera para que la plantilla pueda ser instanciada. Por ejemplo, esta es la firma de una de las sobrecargas de std.algorithm.find:

R find(alias pred = "a == b", R, E)(R haystack, E needle)
    if (isInputRange!R &&
        is(typeof(binaryFun!pred(haystack.front, needle)) : bool))

Para que esta función templada pueda ser instanciada, el tipo R debe ser un rango de entrada definido por std.range.isInputRange (así que isInputRange!R debe ser true), y el predicado dado debe ser una función binaria que compila con los argumentos dados y devuelve un tipo que es implícitamente convertible a bool. Si el resultado de la condición en la restricción de plantilla es false, entonces la plantilla no se compilará. Esto no solo lo protege de los desagradables errores de plantilla que obtiene en C++ cuando las plantillas no se compilan con sus argumentos dados, sino que lo hace para que pueda sobrecargar plantillas en función de sus restricciones de plantilla. Por ejemplo, hay otra sobrecarga de find que es

R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
if (isForwardRange!R1 && isForwardRange!R2
        && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)
        && !isRandomAccessRange!R1)

Toma exactamente los mismos argumentos, pero su restricción es diferente. Por lo tanto, diferentes tipos funcionan con diferentes sobrecargas de la misma función templada, y la mejor implementación de find se puede usar para cada tipo. No hay manera de hacer ese tipo de cosas limpiamente en C++. Con un poco de familiaridad con las funciones y plantillas utilizadas en su plantilla típica constraint, template constraints en D son bastante fáciles de leer, mientras que necesitas una metaprogramación de plantillas muy complicada en C++ para intentar algo como esto, que tu programador promedio no va a ser capaz de entender, y mucho menos realmente hacerlo por su cuenta. Boost es un buen ejemplo de esto. Hace algunas cosas increíbles, pero es increíblemente complicado.

static if mejora aún más la situación. Al igual que con las restricciones de plantilla, cualquier condición que puede ser evaluado en tiempo de compilación se puede utilizar con él. por ejemplo,

static if(isIntegral!T)
{
    //...
}
else static if(isFloatingPoint!T)
{
    //...
}
else static if(isSomeString!T)
{
    //...
}
else static if(isDynamicArray!T)
{
    //...
}
else
{
    //...
}

En qué rama se compila depende de qué condición se evalúa primero a true. Por lo tanto, dentro de una plantilla, puede especializar partes de su implementación en función de los tipos con los que se creó la plantilla, o en función de cualquier otra cosa que pueda evaluarse en tiempo de compilación. Por ejemplo, core.time utiliza

static if(is(typeof(clock_gettime)))

Para compilar el código de manera diferente en función de si el sistema proporciona clock_gettime o no (si clock_gettime está allí, lo usa, de lo contrario usa gettimeofday).

Probablemente el ejemplo más duro que he visto donde D mejora las plantillas es con un problema que mi equipo en el trabajo se encontró en C++. Necesitábamos instanciar una plantilla de manera diferente en función de si el tipo que se le dio se derivaba de una clase base en particular o no. Terminamos usando una solución basada en esta pregunta de desbordamiento de pila. Funciona, pero es bastante complicado solo para probar si un tipo se deriva de otro.

En D, sin embargo, todo lo que tienes que hacer es usar el operador :. por ejemplo,

auto func(T : U)(T val) {...}

Si T es implícitamente convertible a U (como sería si T se derivara de U), entonces func compilará, mientras que si T no es implícitamente convertible a U, entonces no lo hará. Esa mejora simple de hace que incluso las especializaciones básicas de plantillas sean mucho más poderosas (incluso sin restricciones de plantilla o static if).

Personalmente, rara vez uso plantillas en C++ que no sea con contenedores y la función ocasional en <algorithm>, porque son muy difíciles de usar. Dan lugar a errores feos y son muy difíciles de hacer nada elegante con. Para hacer cualquier cosa, incluso un poco complicado, necesita ser muy hábil con las plantillas y la metaprogramación de plantillas. Sin embargo, con las plantillas en D, es tan fácil que las uso todo el tiempo. Los errores son mucho más fáciles de entender y tratar (aunque todavía son peores que los errores típicamente son con funciones no templadas), y no tengo que averiguar cómo forzar el lenguaje a hacer lo que quiero con metaprogramación de lujo.

No hay razón para que C++ no pueda obtener muchas de estas habilidades que tiene D (los conceptos de C++ ayudarían si alguna vez las solucionan), pero hasta que agreguen compilación condicional básica con construcciones similares a las restricciones de plantilla y static if a C++, las plantillas de C++ simplemente no podrán compararse con las plantillas de D en términos de facilidad de uso y energía.

 68
Author: Jonathan M Davis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:24:33

Creo que nada está mejor calificado para mostrar el increíble poder (TM) del sistema de plantillas D que este renderizador que encontré hace años:

La salida del compilador

¡Sí! Esto es realmente lo que genera el compilador ... es el "programa", y bastante colorido, de hecho.

Editar

La fuente parece estar de vuelta en línea.

 39
Author: bitmask,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-06 17:30:35

Los mejores ejemplos de metaprogramación D son los módulos de biblioteca estándar D que hacen un uso intensivo de ti frente a los módulos C++ Boost y STL. Echa un vistazo a D's std.intervalo, std.algoritmo, std.funcional y std.paralelismo . Ninguno de estos sería fácil de implementar en C++, al menos con el tipo de API limpia y expresiva que tienen los módulos D.

La mejor manera de aprender D metaprogramación, en mi humilde opinión, es por este tipo de ejemplos. Aprendí en gran parte leyendo el código a std.algoritmo y std.range, que fueron escritos por Andrei Alexandrescu (un gurú de la metaprogramación de plantillas C++ que se ha involucrado mucho con D). Luego usé lo que aprendí y contribuí con la ets.módulo de paralelismo.

También tenga en cuenta que D tiene evaluación de funciones en tiempo de compilación (CTFE) que es similar a C++1x constexpr pero mucho más general en que un subconjunto grande y creciente de funciones que pueden ser evaluadas en tiempo de ejecución pueden ser evaluadas sin modificar en tiempo de compilación. Esto es útil para la generación de código en tiempo de compilación, y el código generado se puede compilar usando string mixins.

 27
Author: dsimcha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-04 16:36:27

Bien, en D puede imponer fácilmente restricciones estáticas en los parámetros de la plantilla y escribir código dependiendo del argumento de la plantilla real con static if.
Es posible simular eso para casos simples con C++ usando especialización de plantillas y otros trucos (ver boost) pero es un PITA y muy limitado porque el compilador no expone muchos detalles sobre los tipos.

Una cosa que C++ realmente no puede hacer es la sofisticada generación de código en tiempo de compilación.

 14
Author: Trass3r,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-04 17:52:08

Aquí hay un fragmento de código D que hace un map() personalizado que devuelve sus resultados por referencia.

Crea dos arrays de longitud 4, asignacada par correspondiente de elementos al elemento con el valor mínimo, y lo multiplica por 50, y almacena el resultado de nuevo en el array original.

Algunas características importantes a tener en cuenta son las siguientes:

  • Las plantillas son variádicas: map() podría tomar cualquier número de argumento.

  • El código es (relativamente) corto ! La estructura Mapper, que es la lógica central, es de solo 15 líneas yet y sin embargo puede hacer mucho con tan poco. Mi punto no es que esto sea imposible en C++, pero ciertamente no es tan compacto y limpio.


import std.metastrings, std.typetuple, std.range, std.stdio;

void main() {
    auto arr1 = [1, 10, 5, 6], arr2 = [3, 9, 80, 4];

    foreach (ref m; map!min(arr1, arr2)[1 .. 3])
        m *= 50;

    writeln(arr1, arr2); // Voila! You get:  [1, 10, 250, 6][3, 450, 80, 4]
}

auto ref min(T...)(ref T values) {
    auto p = &values[0];
    foreach (i, v; values)
        if (v < *p)
            p = &values[i];
    return *p;
}

Mapper!(F, T) map(alias F, T...)(T args) { return Mapper!(F, T)(args); }

struct Mapper(alias F, T...) {
    T src;  // It's a tuple!

    @property bool empty() { return src[0].empty; }

    @property auto ref front() {
        immutable sources = FormatIota!(q{src[%s].front}, T.length);
        return mixin(Format!(q{F(%s)}, sources));
    }

    void popFront() { foreach (i, x; src) { src[i].popFront(); } }

    auto opSlice(size_t a, size_t b) {
        immutable sliced = FormatIota!(q{src[%s][a .. b]}, T.length);
        return mixin(Format!(q{map!F(%s)}, sliced));
    }
}


// All this does is go through the numbers [0, len),
// and return string 'f' formatted with each integer, all joined with commas
template FormatIota(string f, int len, int i = 0) {
    static if (i + 1 < len)
        enum FormatIota = Format!(f, i) ~ ", " ~ FormatIota!(f, len, i + 1);
    else
        enum FormatIota = Format!(f, i);
}
 11
Author: Mehrdad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-05 03:16:39

Escribí mis experiencias con las plantillas de D, los mixins de cadenas y los mixins de plantillas: http://david.rothlis.net/d/templates /

Debería darte una idea de lo que es posible en D't No creo que en C++ puedas acceder a un identificador como una cadena, transformar esa cadena en tiempo de compilación y generar código a partir de la cadena manipulada.

Mi conclusión: Extremadamente flexible, extremadamente potente y utilizable por meros mortales, pero el compilador de referencia todavía es algo buggy cuando se trata de las cosas más avanzadas de metaprogramación en tiempo de compilación.

 9
Author: David Röthlisberger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-07 09:20:53

Manipulación de cadenas, incluso análisis de cadenas.

Esta es una biblioteca MP que genera analizadores decentes recursivos basados en gramáticas definidas en cadenas usando (más o menos) BNF. No lo he tocado en años, pero solía funcionar.

 8
Author: BCS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-05 16:13:27

En D puede comprobar el tamaño de un tipo y los métodos disponibles en él y decidir qué implementación desea utilizar

Esto se usa por ejemplo en el core.atomic módulo

bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, const V2 writeThis ){
    static if(T.sizeof == byte.sizeof){
       //do 1 byte CaS
    }else static if(T.sizeof == short.sizeof){
       //do 2 byte CaS
    }else static if( T.sizeof == int.sizeof ){
       //do 4 byte CaS
    }else static if( T.sizeof == long.sizeof ){
       //do 8 byte CaS
    }else static assert(false);
}
 6
Author: ratchet freak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-04 15:50:25

Solo para contrarrestar el post de trazado de rayos D, aquí hay un trazador de rayos en tiempo de compilación de C++ (metatrace):

introduzca la descripción de la imagen aquí

(por cierto, utiliza principalmente metaprogramación C++2003; sería más legible con los nuevos constexpr s)

 3
Author: Sebastian Mach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-12 19:59:38

Hay algunas cosas tranquilas que puede hacer en la metaprogramación de plantillas en D que no puede hacer en C++. Lo más importante es que usted puede hacer plantilla metaprogramación SIN TANTO DOLOR!

 1
Author: Ralph Tandetzky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-24 17:01:47