C++和D中的元编程

Pau*_*nta 65 c++ d metaprogramming

C++中的模板机制只是偶然地对模板元编程有用.另一方面,D's专门设计用于促进这一点.而且显然它更容易理解(或者我听说过).

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

Jon*_*vis 68

在D中帮助模板元编程的两个最重要的事情是模板约束static if- 这两者在理论上可以添加C++,并且会对它有很大的好处.

模板约束允许您在模板上放置条件,该模板必须为true才能使模板能够实例化.例如,这是其中一个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))
Run Code Online (Sandbox Code Playgroud)

为了能够实例化这个模板化函数,类型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)
Run Code Online (Sandbox Code Playgroud)

它需要完全相同的参数,但它的约束是不同的.因此,不同类型使用相同模板化函数的不同重载,并且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
{
    //...
}
Run Code Online (Sandbox Code Playgroud)

编译哪个分支取决于首先评估的条件true.因此,在模板中,您可以根据模板实例化的类型或基于可在编译时评估的任何其他内容来专门化其实现的各个部分.例如,core.time用途

static if(is(typeof(clock_gettime)))
Run Code Online (Sandbox Code Playgroud)

根据系统是否提供来编译代码clock_gettime(如果clock_gettime有的话,它使用它,否则使用它gettimeofday).

可能是我看到D改进模板的最明显的例子是我的团队在C++中遇到的问题.我们需要根据给定的类型是否来自特定的基类来不同地实例化模板.我们最终使用了基于此堆栈溢出问题的解决方案.它可以工作,但仅仅测试一种类型是否来自另一种类型是相当复杂的.

但是,在D中,您所要做的就是使用:运算符.例如

auto func(T : U)(T val) {...}
Run Code Online (Sandbox Code Playgroud)

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

就个人而言,我很少使用C++中的模板,而不是使用容器和偶尔的函数<algorithm>,因为它们使用起来非常麻烦.它们会导致难看的错误,并且很难做任何奇特的事情.要做任何有点复杂的事情,你需要非常熟练的模板和模板元编程.使用D中的模板,我总是很容易使用它们.这些错误更易于理解和处理(尽管它们仍然比通常使用非模板化函数的错误更糟糕),而且我不必弄清楚如何通过花哨的元编程强制语言做我想做的事情.

没有理由认为C++不能获得D所具有的这些能力(C++概念如果能够解决这些问题会有所帮助),但是直到他们使用类似于模板约束和static ifC++的结构添加基本条件编译,C++模板只是在易用性和功率方面将无法与D模板进行比较.


bit*_*ask 40

我相信没有什么比我多年前发现的渲染器更能证明D模板系统令人难以置信的功效(TM):

编译器输出

是! 这实际上是由编译器生成的......它是"程序",确实非常多彩.

编辑

消息来源似乎重新上线了.

  • @Justin:恭喜完全忽略了这一点;)这很酷,所以它的推荐不仅仅是下面不那么酷但更有用的答案.问题是"我能用d ++做什么我不能做什么".输出rgb而不是程序远离你在c ++中可以做的更远,所以有你的答案. (4认同)

dsi*_*cha 27

D元编程的最佳示例是D标准库模块,它们大量使用它与C++ Boost和STL模块相比.查看D的std.range,std.algorithm,std.functionalstd.parallelism.这些都不容易在C++中实现,至少与D模块具有的那种干净,富有表现力的API有关.

学习D元编程的最佳方法是恕我直言,就是这些例子.我在很大程度上通过阅读std.algorithm和std.range的代码来学习,这些代码是由Andrei Alexandrescu编写的(一个C++模板元编程大师,他已经大量参与了D).然后我使用了我学到的东西并贡献了std.parallelism模块.

另请注意,D具有类似于C++ 1x的编译时功能评估(CTFE),constexpr但更为通用的是,可以在编译时未经修改地评估可在运行时评估的大型且不断增长的函数子集.这对于编译时代码生成很有用,并且可以使用字符串mixins编译生成的代码.


Tra*_*s3r 14

在D中,您可以轻松地对模板参数施加静态约束,并根据具有静态if的实际模板参数编写代码.
通过使用模板特化和其他技巧(参见boost)可以模拟C++的简单情况,但它是PITA并且非常有限,因为编译器不会暴露许多关于类型的细节.

C++真正做不到的一件事是复杂的编译时代码生成.


Meh*_*dad 11

这是一段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);
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*ger 9

我用D的模板,字符串mixins和模板混合编写了我的经验:http://david.rothlis.net/d/templates/

它应该让你了解D中可能的内容 - 我不认为在C++中你可以访问标识符作为字符串,在编译时转换该字符串,并从操作字符串生成代码.

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

  • 3天前发布的最新版本的dmd(D编译器)修复了我发现的两个错误之一.我已经更新了这篇文章. (2认同)

BCS*_*BCS 8

字符串操作,甚至是字符串解析.

这是一个MP库,它使用(或多或少)BNF在字符串中定义的语法生成递归的正确解析器.我多年没碰过它,但它曾经工作过.


rat*_*eak 6

在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);
}
Run Code Online (Sandbox Code Playgroud)

  • 在C++中,您也可以检查`sizeof`,尽管专业化可以更好地处理 (2认同)
  • 难道不会在运行时发生这种情况,造成开销吗?在D版本中,所有都发生在编译时.没有分支. (2认同)