简体   繁体   English

C++ 和 D 中的元编程

[英]Metaprogramming in C++ and in D

The template mechanism in C++ only accidentally became useful for template metaprogramming. C++ 中的模板机制只是偶然地对模板元编程有用。 On the other hand, D's was designed specifically to facilitate this.另一方面,D's 是专门为促进这一点而设计的。 And apparently it's even easier to understand (or so I've heard).显然它更容易理解(或者我听说过)。

I've no experience with D, but I'm curious, what is it that you can do in D and you cannot in C++, when it comes to template metaprogramming?我没有使用 D 的经验,但我很好奇,在模板元编程方面,您可以在 D 中做什么而在 C++ 中不能做什么?

The two biggest things that help template metaprogramming in D are template constraints and static if - both of which C++ could theoretically add and which would benefit it greatly.在 D 中帮助模板元编程的两个最重要的事情是模板约束和static if - C++ 理论上可以添加这两者,并且会从中受益匪浅。

Template constraints allow you to put a condition on a template that must be true for the template to be able to be instantiated.模板约束允许您在模板上放置一个条件,该条件必须为真,模板才能被实例化。 For instance, this is the signature of one of std.algorithm.find 's overloads:例如,这是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))

In order for this templated function to be able to be instantiated, the type R must be an input range as defined by std.range.isInputRange (so isInputRange!R must be true ), and the given predicate needs to be a binary function which compiles with the given arguments and returns a type which is implicitly convertible to bool .为了能够实例化这个模板化函数,类型R必须是由std.range.isInputRange定义的输入范围(所以isInputRange!R必须是true ),并且给定的谓词需要是一个二元函数使用给定的参数进行编译并返回一个可隐式转换为bool的类型。 If the result of the condition in the template constraint is false , then the template won't compile.如果模板约束条件的结果为false ,则模板将不会编译。 Not only does this protect you from the nasty template errors that you get in C++ when templates won't compile with their given arguments, but it makes it so that you can overload templates based on their template constraints.当模板无法使用给定的参数编译时,这不仅可以保护您免受 C++ 中令人讨厌的模板错误的影响,而且还可以根据模板约束重载模板。 For instance, there's another overload of find which is例如, 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)

It takes exactly the same arguments, but its constraint is different.它采用完全相同的参数,但其约束不同。 So, different types work with different overloads of the same templated function, and the best implementation of find can be used for each type.因此,不同类型使用相同模板化函数的不同重载,并且可以对每种类型使用find的最佳实现。 There's no way to do that sort of thing cleanly in C++.没有办法在 C++ 中干净地做那种事情。 With a bit of familiarity with the functions and templates used in your typical template constraint, template constraints in D are fairly easy to read, whereas you need some very complicated template metaprogramming in C++ to even attempt something like this, which your average programmer is not going to be able to understand, let alone actually do on their own.稍微熟悉典型模板约束中使用的函数和模板后,D 中的模板约束相当容易阅读,而您需要在 C++ 中进行一些非常复杂的模板元编程才能尝试这样的事情,而您的普通程序员不会反正能看懂,更不用说自己动手了。 Boost is a prime example of this. Boost 就是一个典型的例子。 It does some amazing stuff, but it's incredibly complicated.它做了一些了不起的事情,但它非常复杂。

static if improves the situation even further. static if进一步改善情况。 Just like with template constraints, any condition which can be evaluated at compile time can be used with it.就像模板约束一样,可以在编译时评估的任何条件都可以与它一起使用。 eg例如

static if(isIntegral!T)
{
    //...
}
else static if(isFloatingPoint!T)
{
    //...
}
else static if(isSomeString!T)
{
    //...
}
else static if(isDynamicArray!T)
{
    //...
}
else
{
    //...
}

Which branch is compiled in depends on which condition first evaluates to true .编译哪个分支取决于哪个条件首先评估为true So, within a template, you can specialize pieces of its implementation based on the types that the template was instantiated with - or based on anything else which can be evaluated at compile time.因此,在模板中,您可以根据模板实例化的类型来专门化其实现的部分 - 或者基于可以在编译时评估的任何其他内容。 For instance, core.time uses例如, core.time使用

static if(is(typeof(clock_gettime)))

to compile code differently based on whether the system provides clock_gettime or not (if clock_gettime is there, it uses it, otherwise it uses gettimeofday ).根据系统是否提供clock_gettime以不同方式编译代码(如果存在clock_gettime则使用它,否则使用gettimeofday )。

Probably the most stark example that I've seen where D improves on templates is with a problem which my team at work ran into in C++.可能我见过的最明显的例子是 D 改进模板的一个问题是我的团队在 C++ 中遇到的问题。 We needed to instantiate a template differently based on whether the type it was given was derived from a particular base class or not.我们需要根据给定的类型是否从特定基类派生来以不同的方式实例化模板。 We ended up using a solution based on this stack overflow question .我们最终使用了基于此堆栈溢出问题的解决方案。 It works, but it's fairly complicated for just testing whether one type is derived from another.它有效,但仅测试一种类型是否源自另一种类型就相当复杂。

In D, however, all you have to do is use the : operator.但是,在 D 中,您所要做的就是使用:运算符。 eg例如

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

If T is implicitly convertible to U (as it would be if T were derived from U ), then func will compile, whereas if T isn't implicitly convertible to U , then it won't.如果T可隐式转换为U (就像T派生自U ),则func将编译,而如果T不可隐式转换为U ,则不会。 That simple improvement makes even basic template specializations much more powerful (even without template constraints or static if ).这种简单的改进甚至使基本模板特化变得更加强大(即使没有模板约束或static if )。

Personally, I rarely use templates in C++ other than with containers and the occasional function in <algorithm> , because they're so much of a pain to use.就我个人而言,除了容器和<algorithm>的偶尔函数之外,我很少在 C++ 中使用模板,因为它们使用起来非常痛苦。 They result in ugly errors and are very hard to do anything fancy with.它们会导致丑陋的错误,并且很难做任何花哨的事情。 To do anything even a little bit complicated, you need to be very skilled with templates and template metaprogramming.要做任何稍微复杂的事情,您都需要非常熟练地使用模板和模板元编程。 With templates in D though, it's so easy that I use them all the time.但是使用 D 中的模板,它非常容易,我一直使用它们。 The errors are much easier to understand and deal with (though they're still worse than errors typically are with non-templated functions), and I don't have to figure out how to force the language into doing what I want with fancy metaprogramming.这些错误更容易理解和处理(尽管它们仍然比非模板化函数的错误更糟糕),而且我不必弄清楚如何通过花哨的元编程来强制语言做我想做的事.

There's no reason that C++ couldn't gain much of these abilities that D has (C++ concepts would help if they ever get those sorted out), but until they add basic conditional compilation with constructs similar to template constraints and static if to C++, C++ templates just won't be able to compare with D templates in terms of ease of use and power. C++ 没有理由不能获得 D 拥有的这些能力中的很多(C++ 概念如果能够解决这些问题会有所帮助),但是直到他们添加具有类似于模板约束和static if构造的基本条件编译到 C++、C++模板只是在易用性和功能方面无法与 D 模板进行比较。

I believe nothing is better qualified to show the incredible power (TM) of the D template system than this renderer I found years ago:我相信没有什么比我多年前发现的这个渲染器更能展示 D 模板系统令人难以置信的力量 (TM) 了:

编译器输出

Yes!是的! This is actually what is generated by the compiler ... it is the "program", and quite a colourful one, indeed.这实际上是由编译器生成的……它是“程序”,确实是一个非常丰富多彩的程序。

Edit编辑

The source seems to be back online.来源似乎重新上线。

The best examples of D metaprogramming are D standard library modules that make heavy use of it vs. C++ Boost and STL modules. D 元编程的最佳示例是大量使用它的 D 标准库模块,而不是 C++ Boost 和 STL 模块。 Check out D's std.range , std.algorithm , std.functional and std.parallelism .查看 D 的std.rangestd.algorithmstd.functionalstd.parallelism None of these would be easy to implement in C++, at least with the kind of clean, expressive API that the D modules have.这些都不容易在 C++ 中实现,至少使用 D 模块具有的那种干净、富有表现力的 API。

The best way to learn D metaprogramming, IMHO, is by these kinds of examples.学习 D 元编程的最好方法,恕我直言,就是通过这些例子。 I learned largely by reading the code to std.algorithm and std.range, which were written by Andrei Alexandrescu (a C++ template metaprogramming guru who has become heavily involved with D).我主要是通过阅读 Std.algorithm 和 std.range 的代码来学习的,这些代码由 Andrei Alexandrescu(一位 C++ 模板元编程大师,与 D 密切相关)编写。 I then used what I learned and contributed the std.parallelism module.然后我使用了我学到的知识并贡献了 std.parallelism 模块。

Also note that D has compile time function evaluation (CTFE) which is similar to C++1x's constexpr but much more general in that a large and growing subset of functions that can be evaluated at runtime can be evaluated unmodified at compile time.另请注意,D 具有编译时函数求值 (CTFE),它类似于 C++1x 的constexpr但更通用,因为可以在运行时求值的大量且不断增长的函数子集可以在编译时不加修改地求值。 This is useful for compile-time code generation, and the generated code can be compiled using string mixins .这对于编译时代码生成很有用,生成的代码可以使用字符串 mixins进行编译。

Well in D you can easily impose static constraints on template parameters and write code depending on the actual template argument with static if .好吧,在 D 中,您可以轻松地对模板参数施加静态约束,并根据使用static if的实际模板参数编写代码。
It's possible to simulate that for simple cases with C++ by using template specialization and other tricks (see boost) but it's a PITA and very limited cause the compiler doesn't expose many details about types.可以通过使用模板特化和其他技巧(参见 boost)模拟 C++ 的简单情况,但它是一个 PITA 并且非常有限,因为编译器不会公开有关类型的许多细节。

One thing C++ really just can't do is sophisticated compile time code generation. C++ 真正不能做的一件事是复杂的编译时代码生成。

Here's a piece of D code that does a custom-made map() which returns its results by reference .这是一段 D 代码,它执行定制的map()它通过引用返回其结果

It creates two arrays of length 4, maps each corresponding pair of elements to the element with the minimum value, and multiplies it by 50, and stores the result back into the original array .它创建两个长度为 4 的数组,每对对应的元素映射到具有最小值的元素,并将其乘以 50,并将结果存储回原始数组

Some important features to note are the following:需要注意的一些重要功能如下:

  • The templates are variadic: map() could take any number of arguments.模板是可变参数的: map()可以接受任意数量的参数。

  • The code is (relatively) short !代码(相对)短 The Mapper structure, which is the core logic, is only 15 lines -- and yet it can do so much with so little.作为核心逻辑的Mapper结构只有 15 行——但它可以用这么少的东西做这么多。 My point isn't that this is impossible in C++, but that certainly isn't as compact and clean.我的观点并不是说这在 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);
}

I wrote up my experiences with D's templates, string mixins, and template mixins: http://david.rothlis.net/d/templates/我写了我在 D 的模板、字符串混合和模板混合方面的经验: http : //david.rothlis.net/d/templates/

It should give you a flavour of what is possible in D -- I don't think that in C++ you can access an identifier as a string, transform that string at compile time, and generate code from the manipulated string.它应该让您了解 D 中的可能性——我不认为在 C++ 中您可以将标识符作为字符串访问,在编译时转换该字符串,并从操作的字符串生成代码。

My conclusion: Extremely flexible, extremely powerful, and usable by mere mortals, but the reference compiler is still somewhat buggy when it comes to the more advanced compile-time metaprogramming stuff.我的结论:非常灵活,非常强大,普通人也可以使用,但是当涉及到更高级的编译时元编程内容时,参考编译器仍然有些错误。

String manipulation, even string parsing.字符串操作,甚至字符串解析。

This is a MP library that generates recursive decent parsers based on grammars defined in strings using (more or less) BNF. 这是一个 MP 库,它基于使用(或多或少)BNF 的字符串中定义的语法生成递归体面的解析器。 I haven't touched it in years but it used to work.我已经好几年没碰它了,但它曾经奏效。

in D you can check the size of a type and the available methods on it and decide which implementation you want to use在 D 中,您可以检查类型的大小及其上可用的方法,并决定要使用哪种实现

this is used for example in the core.atomic module这用于例如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);
}

Just to counter the D ray tracing post, here is a C++ compile time ray tracer ( metatrace ):只是为了对抗 D 射线追踪帖子,这里是一个 C++ 编译时射线追踪器( metatrace ):

在此处输入图片说明

(by the way, it uses mostly C++2003 metaprogramming; it would be more readable with the new constexpr s) (顺便说一下,它主要使用 C++2003 元编程;使用新的constexpr会更易读)

There are quiet a few things you can do in template metaprogramming in D that you cannot do in C++.在 D 中的模板元编程中,您可以做一些在 C++ 中无法做的事情。 The most important thing is that you can do template metaprogramming WITHOUT SO MUCH OF A PAIN!最重要的是,您可以轻松地进行模板元编程!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM