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