Метапрограммирование на C++ и в D



механизм шаблонов в C++ только случайно стал полезен для метапрограммирования шаблонов. С другой стороны, D был разработан специально для облегчения этого. И, по-видимому, это еще легче понять (или так я слышал).



У меня нет опыта работы С D, но мне любопытно, что вы можете сделать в D, и вы не можете в C++, когда речь заходит о метапрограммировании шаблонов?

13148   10  

10 ответов:

две самые большие вещи, которые помогают шаблонному метапрограммированию В D, - это ограничения шаблона и static if - оба из которых c++ теоретически может добавить и которые принесут ему большую пользу.

ограничения шаблона позволяют задать условие для шаблона, которое должно быть истинным для создания экземпляра шаблона. Например, это подпись одного из 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))

для того, чтобы эта функция использует, чтобы быть экземпляр, вида R должен быть входной диапазон, как определено std.range.isInputRange (т. isInputRange!R должно быть true), и данный предикат должен быть двоичной функцией, которая компилируется с заданными аргументами и возвращает тип, который неявно преобразуется в bool. Если результатом условия в ограничении шаблона является false, то шаблон не компилируется. Это не только защищает вас от неприятных ошибок шаблонов, которые вы получаете в C++, когда шаблоны не будут компилироваться с их помощью даны аргументы, но это делает его так, что вы можете перегружать шаблоны на основе их ограничений шаблона. Например, есть еще одна перегрузка find что это

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)

он принимает точно такие же аргументы, но его ограничение отличается. Итак, разные типы работают с разными перегрузками одной и той же шаблонной функции, и лучшая реализация find может использоваться для каждого типа. В C++нет способа сделать это чисто. С небольшой фамильярностью с функциями и шаблонами, используемыми в вашем типичном ограничении шаблона, ограничения шаблона в D довольно легко читаются, тогда как вам нужно очень сложное метапрограммирование шаблона в C++, чтобы даже попытаться сделать что-то подобное, что ваш средний программист не сможет понять, не говоря уже о том, чтобы сделать самостоятельно. Boost является ярким примером этого. Это делает некоторые удивительные вещи, но это невероятно сложно.

static if улучшает ситуацию еще больше. Так же, как и с ограничениями шаблона, с ним можно использовать любое условие, которое может быть оценено во время компиляции. например,

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

какая ветвь компилируется в зависимости от того, какое условие сначала оценивается в true. Таким образом, в шаблоне вы можете специализировать части его реализации на основе типов, с которыми был создан шаблон, или на основе чего - либо еще, что может быть оценено во время компиляции. Например, core.time использует

static if(is(typeof(clock_gettime)))

для компиляции код по-разному зависит от того, предоставляет ли система clock_gettime или нет (если clock_gettime есть, он использует его, в противном случае он использует gettimeofday).

вероятно, самый яркий пример, который я видел, где D улучшает шаблоны, - это проблема, с которой столкнулась моя команда на работе в C++. Нам нужно было создать экземпляр шаблона по-разному в зависимости от того, был ли данный тип получен из определенного базового класса или нет. В итоге мы использовали решение, основанное на это переполнение стека вопрос. Это работает, но это довольно сложно для простого тестирования, является ли один тип производным от другого.

в D, однако, все, что вам нужно сделать, это использовать : оператора. например,

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

если T неявно преобразуется в U (как было бы, если бы T были получены U), то func будет компилироваться, тогда как если T не может быть неявно преобразован в U, значит, не будет. это простые улучшения делает даже базовые специализации шаблонов гораздо более мощными (даже без ограничений шаблона или static if).

лично я редко использую шаблоны в C++, кроме как с контейнерами и случайной функцией в <algorithm>, потому что они так много боли, чтобы использовать. Они приводят к уродливым ошибкам и очень трудно сделать что-нибудь необычное. Чтобы сделать что-нибудь даже немного сложное, вам нужно быть очень опытным с шаблонами и шаблонным метапрограммированием. Шаблоны в D хотя, это так просто, что я использую их все время. Ошибки гораздо легче понять и справиться с ними (хотя они все еще хуже, чем ошибки, как правило, с не шаблонными функциями), и мне не нужно выяснять, как заставить язык делать то, что я хочу с помощью причудливого метапрограммирования.

нет причин, по которым C++ не мог бы получить большую часть этих способностей, которые имеет D (концепции C++ помогут, если они когда-нибудь разберутся), но пока они не добавят основные условные компиляция с конструкциями, подобными ограничениям шаблона и static if для C++ шаблоны C++ просто не смогут сравниться с шаблонами D с точки зрения простоты использования и мощности.

Я считаю, что нет ничего лучше квалифицированного, чтобы показать невероятную мощность (TM) системы шаблонов D, чем этот рендерер Я нашел много лет назад:

The compiler output

да! Это на самом деле то, что генерируется компилятор ... это и есть "программа", причем довольно красочная.

Edit

источник, кажется, снова в сети.

лучшие примеры метапрограммирования D-это стандартные библиотечные модули D, которые интенсивно используют его в сравнении с модулями C++ Boost и STL. Проверить Д std.диапазон,std.алгоритм,std.функциональный и std.параллелизм. Ни один из них не будет легко реализовать в C++, по крайней мере, с помощью чистого, выразительного API, который имеют модули D.

лучший способ изучить D-метапрограммирование, ИМХО, заключается в таких примерах. Я учился в основном, читая код для std.алгоритм и СТД.диапазон, который был написан Андреем Александреску (гуру метапрограммирования шаблона C++, который стал активно участвовать в D). Затем я использовал то, что узнал, и внес свой вклад в ЗППП.модуль параллелизма.

также обратите внимание, что D имеет оценку функции времени компиляции (CTFE), которая похожа на C++1x constexpr но гораздо более общим в том, что большое и растущее подмножество функций, которые могут быть оценены во время выполнения могут быть оценены неизмененный во время компиляции. Это полезно для генерации кода во время компиляции, и сгенерированный код может быть скомпилирован с помощью строка mixins.

Ну В D вы можете легко наложить статику ограничения на параметры шаблона и написать код в зависимости от фактического аргумента шаблона с статический если.
Можно смоделировать это для простых случаев с C++, используя специализацию шаблона и другие трюки (см. boost), но это Пита и очень ограниченная причина, по которой компилятор не раскрывает много деталей о типах.

одна вещь, которую C++ действительно просто не может сделать, это сложный код времени компиляции поколение.

вот кусок кода D, который делает заказ map(), который возвращает свои результаты по ссылке.

Он создает два массива длины 4, карты каждая соответствующая пара элементов соответствует элементу с минимальным значением и умножает его на 50, а сохраняет результат обратно в исходный массив.

некоторые важные особенности, чтобы отметить следующее:

  • шаблоны с переменным числом аргументов: map() может принимать любое количество аргументов.

  • код (условно) короче! Элемент Mapper структура, которая является основной логикой, составляет всего 15 строк-и все же она может сделать так много с таким малым. Я не хочу сказать, что это невозможно в C++, но это, конечно, не так компактно и чисто.


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);
}

Я написал свой опыт работы с шаблонами D, строковыми миксинами и шаблонными миксинами:http://david.rothlis.net/d/templates/

Это должно дать вам представление о том, что возможно в D -- Я не думаю, что в C++ вы можете получить доступ к идентификатору в виде строки, преобразовать эту строку во время компиляции и сгенерировать код из манипулируемой строки.

мой вывод: чрезвычайно гибкий, чрезвычайно мощный и пригодный для использования простыми смертными, но ссылка компилятор все еще несколько глючит, когда речь заходит о более продвинутом метапрограммировании во время компиляции.

строковые манипуляции, даже разбор строк.

это библиотека MP который генерирует рекурсивные приличные Парсеры на основе грамматик, определенных в строках с использованием (более или менее) BNF. Я не прикасался к нему в течение многих лет, но он работал.

В D вы можете проверить размер типа и доступные методы на нем и решить, какую реализацию вы хотите использовать

это используется, например, в core.atomic модуль

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);
}

для того, чтобы противостоять Д трассировки лучей поста, вот на C++ время компиляции трассировщика лучей (metatrace):

enter image description here

(кстати, он использует в основном метапрограммирование C++2003; Это было бы более читаемым с новым constexpr s)

есть несколько тихих вещей, которые вы можете сделать в шаблоне метапрограммирования в D, что вы не можете сделать в C++. Самое главное, что вы можете сделать шаблон метапрограммирования без такой большой боли!

Comments

    Ничего не найдено.