Метапрограммирование в C++ и D
Шаблонный механизм в C++ только случайно оказался полезным для метапрограммирования шаблонов. С другой стороны, D был разработан специально, чтобы облегчить это. И, по-видимому, это даже легче понять (или так я слышал).
У меня нет опыта работы С D, но мне интересно, что вы можете сделать в D и не можете в C++, когда речь заходит о метапрограммировании шаблонов?
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++, кроме контейнеров и случайной функции в
Нет никаких причин, по которым C++ не мог бы получить большую часть этих способностей, которыми обладает D (концепции C++ помогут, если они когда-нибудь разберутся с ними), но до тех пор, пока они не добавят базовую условную компиляцию с конструкциями, подобными ограничениям шаблона и<algorithm>
, потому что они очень болезненны в использовании. Они приводят к уродливым ошибкам и очень трудно сделать что-нибудь необычное. Чтобы сделать что-нибудь даже немного сложное, вам нужно быть очень опытным с шаблонами и шаблонным метапрограммированием. С шаблонами в D, однако, это так просто, что я использую их все время. Ошибки гораздо легче понять и справиться с ними (хотя они все еще хуже, чем ошибки, с которыми обычно сталкиваются не шаблонные функции), и мне не нужно выяснять, как заставить язык делать то, что я хочу, с помощью причудливого метапрограммирования.static if
В C++, шаблоны C++ просто не смогут сравниться с шаблонами D с точки зрения простоты использования и удобства использования. сила.
Я считаю, что нет ничего лучше, чтобы показать невероятную силу (TM) системы шаблонов D, чем Этот визуализатор, который я нашел много лет назад:
Да! Это фактически то, что генерируется компилятором ... это" программа", причем довольно красочная.
Edit
Источник, кажется, снова в сети.
Лучшими примерами D-метапрограммирования являются модули библиотеки D standard, которые интенсивно используют it по сравнению с модулями C++ Boost и STL. Проверьте d'S std.диапазон, ЗППП.алгоритм, ЗППП.функциональные и СТД.параллелизм . Ни один из них не был бы легко реализован в C++, по крайней мере, с таким чистым, выразительным API, который есть у модулей D.
Лучший способ научиться D-метапрограммированию, ИМХО, заключается в таких примерах. Я учился в основном, читая код для ЗППП.алгоритм и std.range, которые были написаны Андреем Александреску (гуру метапрограммирования шаблонов C++, который стал активно заниматься D). Затем я использовал то, что узнал, и внес свой вклад в ЗППП.модуль параллелизма.Также обратите внимание, что у D есть функция оценки времени компиляции (CTFE), которая похожа на C++1x
constexpr
, но гораздо более общая в том, что большое и растущее подмножество функций, которые могут быть оценены во время выполнения, могут быть оценены немодифицированными во время компиляции. Это полезно для генерации кода во время компиляции, и сгенерированный код может быть скомпилирован с помощью string mixins .
Ну в D вы можете легко наложить статическиеограничения на параметры шаблона и написать код в зависимости от фактического аргумента шаблона сстатическим if .
Это можно смоделировать для простых случаев с 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); }
Просто для того, чтобы противостоять посту трассировки D-лучей, вот c++ compile time ray tracer (metatrace):
(Кстати, он использует в основном метапрограммирование C++2003; Это было бы более читаемым с новым
constexpr
s)