The D programming language (which is evolved from C++) also has templates. [1] The templates in C++ have been used for many, many purposes beyond typesafe generic functions and classes. (For an extreme example, see the Boost MPL Library: http://www.boost.org/libs/mpl/doc/index.html.) The templates in D were rethought from the ground up by looking at what people actually used templates for, and then making many of those things easier. [2]
Let's look at the examples from the previous writeup, re-written in D:
T1 min(T1, T2) (T1 a, T2 b) {
return ( a < b ) ? a : b;
}
And here's the stack example in D:
class stack(T) {
private:
// Use a built-in dynamic array.
T[] m_data;
public:
// Don't need a default constructor;
// D initializes members by default.
// Don't need a destructor; D is garbage collected.
int count() { return m_data.length; }
T pop() {
T temp = m_data[length-1];
// Array slicing
m_data = m_data[0..length-1];
return temp;
}
T top() { return m_data[length-1]; }
void push(T newdata) {
// In-place array concatenation operator
m_data ~= newdata;
}
}
Using this class is much the same:
stack!(int) st1;
stack!(char[]) st2;
st2.push("foo");
st1.push(456);
So those are simple enough. They are mostly equivalent to the C++ templates above. Aside from the streamlined template syntax (which is optional: you can still say "template" if you like), they do not really show off any new template features.
So, let's talk about static if using an example from the spec:
template INT(int SIZE) {
static if (SIZE == 32)
alias int INT;
else static if (SIZE == 16)
alias short INT;
else
static assert(false, "Invalid size!");
}
INT!(32) a; // a is an int
INT!(16) b; // b is a short
INT!(17) c; // error, static assert trips
Integer arguments to templates are nothing new, but static if is. Because D does not have a preprocesser, conditional compilation must be handled some other way. static if therefore replaces #if and #endif, and it has the very, very useful ability to access template arguments, which the preprocesser cannot do in C++. The static assert is analogous to #error in C++. If the expression in parends is false, it halts compilation.
static if can also be used to check whether a template argument is a given type:
template Foo(T) {
static if (is(T == int)) {
// do something useful ...
} else static if (is(T == real)) {
// do something else ...
} else static assert(false, "Invalid type!");
}
The "is" expression is actually astoundingly powerful, and can be used for many other purposes:
import std.stdio;
template RetType(alias Fn) {
static if (is(typeof(Fn) RET == function)) {
alias RET RetType;
} else static assert(false);
}
char[] foo() { return "monkey!"; }
void main() {
writefln("foo's return type is: %s", typeid(RetType!(foo)));
}
static if also vastly simplifies some of the more complicated template metaprogramming tricks, making them look much more like a normal recursive function. This compile-time factorial example is from the spec: [2]
template factorial(int n) {
static if (n == 1)
const int factorial = 1;
else
const int factorial = n * factorial!(n-1);
}
String literals are also valid template parameters. Someone has even written a compile-time regular expression engine using D's templates. [2] (I am aware that there is such a thing for C++. However the D one simply operates on string literals, while the C++ one does some strange, fancy operator overloading stuff.)
Sources:
- http://www.digitalmars.com/d/template.html - The most up-to-date docs on D's templates.
- http://www.digitalmars.com/d/templates-revisited.html - A white paper on re-working C++'s templates.
ariels says re Template: All the examples you show with "static" are easily performed using C++ templates (using partial template specialization and typedefs).
/msg ariels
I am aware that static if does not really do anything that partial template specialization can't already achieve. The point is that (for example) the factorial template is done in one template and six lines what otherwise needs multiple templates and partial specialization to achieve. D's templates can be used by people who haven't studied the many tricks needed to leverage C++'s templates to these effects. They also scale better: These trivial examples may not show it, but this way of doing templates is more manageable in more complicated situations.
The point is making this other, second programming language look and behave much more like the first one it is built on top of.
I also did not enumerate all of the features of D templates. How easy is this in C++?
import some_c_library;
void foo() { }
void bar() { }
// Alias parameters can accept any symbol that can be
// evaluated at compile-time.
template wrap_func(alias Fn) {
// Wrap a D function with an "extern(C)" interface.
extern(C) void func() {
Fn();
}
}
void main() {
// Some C function that takes a function pointer as an argument.
c_function_with_callback(&wrap_func!(foo).func);
c_function_with_callback(&wrap_func!(bar).func);
}
I humbly request that you examine the second-to-last example above, again. The one where I derive the return type of a function in five lines and one template. Can C++ really do that anywhere near as elegantly? Without resorting to that almost glorious kludge of the quasi-variadic template?
So yes! C++ can do it, too. But D does it so much better.
D is hardly perfect, of course. The garbage collector requires some special care when writing real-time applications, for instance. (Calling "new" happens in potentially unbound time.) Protection attributes are currently a little broken. (Whoops!) Requiring reference semantics on class instances means trivial classes can have more overhead than a C++ coder might be used to. (Structs still have value semantics. Interfacing to C would be impossible if they didn't. However, they don't get constructors or destructors. If they did, this might as well be C++.)
But templates are one thing D nailed.