在C ++和D中进行元编程

C ++中的模板机制只是偶然地对模板元编程有用。 另一方面,D的devise是为了方便这个。 显然这更容易理解(或者我听说过)。

我对D没有经验,但是我很好奇,在D中你能做什么,在模板元编程中你不能用C ++呢?

在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)) 

为了使这个模板函数能够被实例化,typesR必须是由std.range.isInputRange定义的input范围(因此isInputRange!R必须是true ),并且给定的谓词需要是一个二元函数,用给定的参数编译并返回一个可以隐式转换为bool的types。 如果模板约束条件的结果为false ,那么模板将不会编译。 这不仅可以保护你免受模板不能用给定参数编译的C ++模板的错误,而且可以根据模板约束来重载模板。 比如说,还有另外一个超载的问题

 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) 

它采用完全相同的论点,但是它的约束是不同的。 因此,不同的types可以使用同一个模板化函数的不同重载,并且可以为每个types使用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 。 因此,在一个模板中,可以根据模板实例化的types,或者基于编译时可以评估的其他任何东西,来专门化它的实现部分。 例如, core.time使用

 static if(is(typeof(clock_gettime))) 

根据系统是否提供clock_gettime (如果clock_gettime在那里,它使用它,否则它使用gettimeofday )来不同地编译代码。

可能是我看到D改进模板的最明显的例子是我的团队在C ++中遇到的一个问题。 我们需要基于给定的types是否从特定的基类派生而不同地实例化一个模板。 我们最终使用了一个基于这个堆栈溢出问题的解决scheme。 它可以工作,但是testing一个types是否从另一个types派生是相当复杂的。

然而在D中,你所要做的只是使用:操作符。 例如

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

如果T可以隐式转换为U (如果T是从U派生的),那么func将被编译,而如果T不能隐式转换为U ,那么它就不会。 简单的改进使得即使是基本的模板专业化也更加强大(即使没有模板约束或static if )。

就个人而言,我很less在C ++中使用模板,除了容器和<algorithm>的偶然函数外,因为它们使用起来非常麻烦。 他们导致丑陋的错误,很难做任何事情。 要做任何事情,甚至有点复杂,你需要非常熟练的模板和模板元编程。 D中的模板虽然非常简单,但我总是使用它们。 这些错误更容易理解和处理(尽pipe它们比通常使用非模板函数的错误还要糟糕),而且我不必弄清楚如何用花式元编程来强制语言做我想做的事情。

没有任何理由说C ++不能获得D所具有的许多能力(C ++概念在解决这些问题时会起到帮助作用),但是直到他们添加了基本条件编译,其结构类似于模板约束,而static if对C ++,C ++在易用性和function方面,模板将不能与D模板进行比较。

我相信没有什么比这个我多年前发现的渲染器更能展现D模板系统的令人难以置信的力量(TM):

编译器输出

是! 这实际上是由编译器产生的……这是“程序”,确实是相当丰富多彩的。

编辑

这个消息来源似乎已经恢复在线了。

D元编程的最好的例子是D标准库模块,大量使用它与C ++ Boost和STL模块。 查看D的std.range , std.algorithm , std.functional和std.parallelism 。 这些都不容易在C ++中实现,至less使用D模块所具有的干净,富有performance力的API。

学习D元编程,恕我直言,最好的方法就是通过这些例子。 我主要是通过阅读代码std.algorithm和std.range,这是Andrei Alexandrescu(C ++模板元编程大师,已经深入D)编写的。 然后我使用了我学到的东西,并贡献了std.parallelism模块。

还要注意D具有编译时function评估(CTFE),它类似于C ++ 1x的constexpr但更具有一般性,因为可以在运行时评估的大型且不断增长的function子集可以在编译时不加修改地进行评估。 这对于编译时代码的生成非常有用,并且可以使用stringmixin编译生成的代码。

那么在D中,您可以轻松地对模板参数施加静态约束,并根据实际的模板参数使用static if编写代码。
通过使用模板特化和其他技巧(参见boost),可以用C ++模拟简单案例,但这是一个PITA,而且非常有限,因为编译器没有公开有关types的许多细节。

C ++真正做不到的一件事是编译时代码的复杂性。

这是一段D代码,它执行一个定制的map()它通过引用返回结果

它创build两个长度为4的数组, 每个相应的元素对映射到具有最小值的元素,并将其乘以50,然后将结果存储回原始数组中

一些重要的function要注意以下几点:

  • 模板是可变的: map()可以接受任意数量的参数。

  • 代码是(相对)短 ! 作为核心逻辑的Mapper结构只有15条线路,然而它却能做得如此之less。 我的观点并不是在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的模板,string mixins和模板mixin写下了我的经验: http : //david.rothlis.net/d/templates/

它应该给你一个D中可能的东西的味道 – 我不认为在C ++中你可以作为一个string来访问一个标识符,在编译时转换这个string,并从被操纵的string中生成代码。

我的结论是:非常灵活,function非常强大,可以被凡人使用,但是当涉及到更高级的编译时元编程的时候,参考编译器仍然有些问题。

string操作,甚至stringparsing。

这是一个MP库 ,基于使用(或多或less)BNF在string中定义的语法生成recursion正确的parsing器。 我好几年没有碰过它,但它曾经工作过。

在D中,您可以检查types的大小和可用的方法,并决定要使用哪个实现

例如在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光线跟踪post,这里是一个C ++编译时间光线跟踪器( metatrace ):

在这里输入图像说明

(顺便说一句,它主要使用C ++ 2003元编程;新的constexpr会更易读)

在D的模板元编程中你可以做一些你不能用C ++来做的事。 最重要的是,你可以做模板元编程没有这么多痛苦!