C++ 中的元编程并在 D

发布于 2024-12-03 06:50:35 字数 131 浏览 2 评论 0原文

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

我没有使用 D 的经验,但我很好奇,当涉及到模板元编程时,在 D 中可以做什么而在 C++ 中不能做什么?

The template mechanism in C++ only accidentally became useful for template metaprogramming. On the other hand, D's was designed specifically to facilitate this. 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

当爱已成负担 2024-12-10 06:50:35

对 D 中的模板元编程有帮助的两个最重要的事情是模板约束和静态 if - 理论上 C++ 可以添加这两个内容,并且这会给 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++ 中使用模板,除了容器和 中偶尔使用的函数,因为它们使用起来非常痛苦。它们会导致丑陋的错误,并且很难做任何花哨的事情。要完成任何稍微复杂的事情,您都需要非常熟练地使用模板和模板元编程。不过,有了 D 中的模板,它非常简单,我一直在使用它们。这些错误更容易理解和处理(尽管它们仍然比非模板化函数通常出现的错误更糟糕),而且我不必弄清楚如何通过花哨的元编程强制语言执行我想要的操作。

C++ 没有理由不能获得 D 所拥有的大部分能力(如果它们能够解决这些问题,C++ 概念将会有所帮助),但直到它们添加了类似于模板约束和 static if 的基本条件编译结构。 /code> 到 C++,C++ 模板在易用性和功能方面无法与 D 模板进行比较。

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.

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:

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. If the result of the condition in the template constraint is false, then the template won't compile. 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. For instance, there's another overload of find which is

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. There's no way to do that sort of thing cleanly in 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. Boost is a prime example of this. It does some amazing stuff, but it's incredibly complicated.

static if improves the situation even further. Just like with template constraints, any condition which can be evaluated at compile time can be used with it. e.g.

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. 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

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

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++. 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. e.g.

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. That simple improvement makes even basic template specializations much more powerful (even without template constraints or 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. 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. 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.

鸩远一方 2024-12-10 06:50:35

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

编译器输出

是的!这实际上是编译器生成的......它是“程序”,而且确实是一个丰富多彩的程序。

编辑

源似乎已重新上线。

I believe nothing is better qualified to show the incredible power (TM) of the D template system than this renderer I found years ago:

The compiler output

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.

那些过往 2024-12-10 06:50:35

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

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

另请注意,D 具有编译时函数求值 (CTFE),它与 ​​C++1x 的 constexpr 类似,但更通用,因为可以对可在运行时求值的大量且不断增长的函数子集进行求值编译时未修改。这对于编译时代码生成很有用,生成的代码可以使用 字符串 mixins。

The best examples of D metaprogramming are D standard library modules that make heavy use of it vs. C++ Boost and STL modules. Check out D's std.range, std.algorithm, std.functional and std.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.

The best way to learn D metaprogramming, IMHO, is by these kinds of examples. 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). I then used what I learned and contributed the std.parallelism module.

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. This is useful for compile-time code generation, and the generated code can be compiled using string mixins.

走野 2024-12-10 06:50:35

在 D 中,您可以轻松地对模板参数施加静态约束并根据带有 static if 的实际模板参数。
可以通过使用模板专门化和其他技巧(请参阅 boost)来使用 C++ 来模拟简单的情况,但它是一个 PITA 并且非常有限,因为编译器不会公开有关类型的许多详细信息。

C++ 确实无法做到的一件事是复杂的编译时代码生成。

Well in D you can easily impose static constraints on template parameters and write code depending on the actual template argument with 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.

One thing C++ really just can't do is sophisticated compile time code generation.

醉酒的小男人 2024-12-10 06:50:35

下面是一段 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);
}

Here's a piece of D code that does a custom-made map() which returns its results by reference.

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.

Some important features to note are the following:

  • The templates are variadic: map() could take any number of arguments.

  • 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. My point isn't that this is impossible in C++, but that certainly isn't as compact and clean.


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);
}
不…忘初心 2024-12-10 06:50:35

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

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

我的结论是:非常灵活,非常强大,并且普通人也可以使用,但是当涉及到更高级的编译时元编程内容时,参考编译器仍然存在一些问题。

I wrote up my experiences with D's templates, string mixins, and template mixins: 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.

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.

浅忆 2024-12-10 06:50:35

字符串操作,甚至字符串解析。

这是一个 MP 库,它基于字符串中定义的语法(或多或少)生成递归体面的解析器巴纳夫。我已经很多年没有碰过它了,但它曾经有用过。

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. I haven't touched it in years but it used to work.

一抹微笑 2024-12-10 06:50:35

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

in D you can check the size of a type and the available methods on it and decide which implementation you want to use

this is used for example in the core.atomic module

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);
}
霊感 2024-12-10 06:50:35

为了反驳 D 光线追踪帖子,这里有一个 C++ 编译时光线追踪器 (metatrace):

< img src="https://i.sstatic.net/lX8c8.png" alt="在此处输入图像描述">

(顺便说一下,它主要使用 C++2003 元编程;使用新的 constexpr)

Just to counter the D ray tracing post, here is a C++ compile time ray tracer (metatrace):

enter image description here

(by the way, it uses mostly C++2003 metaprogramming; it would be more readable with the new constexprs)

成熟的代价 2024-12-10 06:50:35

在 D 中的模板元编程中,您可以做一些在 C++ 中无法做的事情。最重要的是,您可以进行模板元编程,而无需那么痛苦!

There are quiet a few things you can do in template metaprogramming in D that you cannot do in C++. The most important thing is that you can do template metaprogramming WITHOUT SO MUCH OF A PAIN!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文