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