Метапрограммирование в 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; Это было бы более читаемым с новым
constexprs)

