Metaprogramming in C ++ and D

The C ++ template engine accidentally became useful for metaprogramming templates. On the other hand, D was designed specifically to facilitate this. And, apparently, this is even easier to understand (or so I heard).

I have no experience with D, but I am curious what you can do in D, and you cannot in C ++, when it comes to metaprogramming templates?

+64
c ++ metaprogramming d d2
04 Sep '11 at 15:39
source share
10 answers

The two biggest things that help metaprogramming templates in D are template limitations and static if - both of which C ++ can theoretically add and which will be very useful.

Template constraints allow you to set a condition for a template that must be true in order for the template to be created. For example, this is the signature of one of the std.algorithm.find 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 created, the type R must be an input range, as defined by std.range.isInputRange (therefore isInputRange!R must be true ), and this predicate must be a binary function that compiles with these arguments and returns a type, which is implicitly converted to bool . If the result of the condition in the template is false , then the template will not compile. This not only protects you from the unpleasant template errors that you get in C ++ when templates will not compile with their given arguments, but it also makes it possible for you to overload templates based on the limitations of their templates. For example, there is another find overload, 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 accepts exactly the same arguments, but its limitation is different. Thus, different types work with different overloads of the same templated function, and the best find implementation can be used for each type. In C ++ there is no way to do something clean. With a little familiarity with the functions and templates used in your typical template constraint, the template constraints in D are pretty easy to read, whereas you need very complicated metaprogramming of templates in C ++ to even try something like this that your average programmer doesn't to be able to understand, not to mention doing on their own. Boost is a prime example of this. It does some amazing things, but it is incredibly difficult.

static if improves the situation even more. As with the limitations of the template, you can use any condition with it that can be evaluated at compile time. eg.

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

Which branch compiles depending on which condition is first evaluated as true . Thus, in a template, you can specialize fragments of your implementation based on the types with which the template was created, or on the basis of anything else that can be evaluated at compile time. For example, core.time uses

 static if(is(typeof(clock_gettime))) 

to compile the code differently depending on whether the system clock_gettime or not (if clock_gettime exists, it uses it, otherwise it uses gettimeofday ).

Probably the most striking example I've seen where D improves on templates is the problem that my team at work ran into with C ++. We needed to create an instance of the template in different ways depending on whether the type that it was specified was obtained from a specific base class or not. We ended up using a solution based on this stack overflow question . It works, but it's quite complicated to just check if one type is derived from another.

In D, however, all you have to do is use the : operator. eg.

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

If T implicitly converted to U (as it would be if T were obtained from U ), then func will compile, and if T implicitly converted to U , then it will not. This simple improvement makes even the basic template specializations much more powerful (even without template restrictions or static if ).

Personally, I rarely use templates in C ++ except containers, and a random function in <algorithm> , because they are so sick to use. They lead to ugly mistakes and it is very difficult to do something interesting. To make something even a little complicated, you need to be very experienced with templates and template metaprogramming. With templates in D, it's so simple that I use them all the time. Errors are much easier to understand and handle (although they are still worse than errors, usually with non-template functions), and I do not need to figure out how to get the language to do what I want with fantastic metaprogramming.

There is no reason C ++ could not get most of these capabilities that D (C ++ concepts would help if they ever sorted them), but until they add basic conditional compilation with constructs similar to template constraints, and static if for C ++ C ++ templates simply cannot compare with D-templates in terms of ease of use and power.

+68
Sep 05 2018-11-11T00:
source share

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

The compiler output

Yes! Actually, this is what the compiler generates ... it is really a “program” and quite bright.

Edit

It seems that the source has returned to the network.

+39
Sep 05 2018-11-11T00:
source share

The best examples of D metaprogramming are standard D library modules, which use it heavily against C ++ Boost and STL modules. Check out D std.range , std.algorithm , std.functional and std.parallelism . None of them would be easy to implement in C ++, at least with the help of the clean, expressive API that D-modules have.

The best way to learn D metaprogramming, IMHO, is just such examples. I basically found out by reading the std.algorithm and std.range code, which were written by Andrei Alexandrescu (the metaprogramming guru of C ++ templates, who became actively involved in D). Then I used what I learned and contributed to the std.parallelism module.

Also note that D has a function for computing a time function (CTFE), which is similar to C ++ 1x constexpr , but much more general in the sense that a large and growing subset of functions that can be evaluated at runtime can be evaluated without changing compilation time. This is useful for generating compilation code, and the generated code can be compiled using string mixins .

+27
Sep 04 2018-11-11T00:
source share

Well in D, you can easily put static constraints on template parameters and write code depending on the actual argument of the template with static if .
You can simulate this for simple cases with C ++, using specialized specialization and other tricks (see Boost), but this is PITA and a very limited reason why the compiler does not provide a lot of details about types.

One thing that C ++ simply cannot do is the complicated generation of compile-time code.

+14
Sep 04 '11 at 17:52
source share

Here is a snippet of D code that executes a custom map() that 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 saves the result back to the original array.

Listed below are some important features:

  • Templates are variables: map() can take any number of arguments.

  • The code is (relatively) short ! The Mapper structure, which is the main logic, is only 15 lines long, and yet it can do so much with so little. My point is not that this is not possible in C ++, but it is certainly not so 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 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); } 
+11
Sep 05 '11 at 3:16
source share

I wrote my experiences with D-patterns, line mixes and mixins patterns: http://david.rothlis.net/d/templates/

This should give you an idea of ​​what is possible in D - I don’t think that in C ++ you can access the identifier as a string, convert that string at compile time, and generate code from a managed string.

My conclusion: an extremely flexible, extremely powerful and useful simple mortal, but the reference compiler is still somewhat erroneous when it comes to more complex compilation metaprograms.

+9
Sep 07 2018-11-11T00:
source share

String processing, even string parsing.

This is an MP library that generates recursive decent parsers based on grammars defined in strings using (more or less) BNF. I did not touch him for years, but he worked.

+8
Sep 05 2018-11-11T00:
source share

in D you can check the type size 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); } 
+6
04 Sep '11 at 15:50
source share

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

enter image description here

(by the way, it uses mainly C ++ 2003 metaprogramming, it will be more readable with the new constexpr s)

+3
Dec 16 '11 at 12:27
source share

There are a few things you can do in metaprogramming templates in D that you cannot do in C ++. Most importantly, you can do metaprogramming templates WITHOUT SO MUCH PAIN!

+1
Jul 24 2018-12-12T00:
source share



All Articles