Метапрограммирование в C ++ и в D - PullRequest
64 голосов
/ 04 сентября 2011

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

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

Ответы [ 10 ]

68 голосов
/ 05 сентября 2011

Две самые важные вещи, которые помогают метапрограммированию шаблонов в 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 ++, чтобы даже попытаться сделать что-то подобное, чего не делает обычный программист будет в состоянии понять, не говоря уже о том, чтобы на самом деле сделать самостоятельно. Повышение является ярким примером этого. Это делает удивительные вещи, но это невероятно сложно.

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

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

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

40 голосов
/ 05 сентября 2011

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

The compiler output

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

Редактировать

Источник, похоже, снова в сети.

27 голосов
/ 04 сентября 2011

Лучшими примерами метапрограммирования D являются модули стандартной библиотеки D, которые интенсивно используют его по сравнению с модулями C ++ Boost и STL. Проверьте D's std.range , std.algorithm , std.functional и std.parallelism . Ни один из них не может быть легко реализован в C ++, по крайней мере с чистым, выразительным API, который есть у модулей D.

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

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

14 голосов
/ 04 сентября 2011

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

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

11 голосов
/ 05 сентября 2011

Вот фрагмент кода D, который выполняет заказное map(), которое возвращает результаты по ссылке .

Создает два массива длины 4, maps каждая соответствующая пара элементов соответствует элементу с минимальным значением и умножает его на 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);
}
9 голосов
/ 07 сентября 2011

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

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

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

8 голосов
/ 05 сентября 2011

Манипулирование строками, даже синтаксический анализ строк.

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

6 голосов
/ 04 сентября 2011

в 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);
}
3 голосов
/ 16 декабря 2011

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

enter image description here

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

1 голос
/ 24 июля 2012

В метапрограммировании шаблонов в D есть несколько вещей, которые вы не можете сделать в C ++.Самое главное, что вы можете выполнять метапрограммирование шаблонов БЕЗ ТАКОГО БОЛЬШЕГО!

...