48 hour game jam

H. S. Teoh hsteoh at quickfur.ath.cx
Mon Oct 15 11:00:14 PDT 2012


On Mon, Oct 15, 2012 at 07:19:42PM +0200, so wrote:
> On Monday, 15 October 2012 at 16:37:08 UTC, Peter Alexander wrote:
> >...
> >- Simple(r) templates
> 
> I keep seeing things like this and probably i am failing to understand
> it.  This is a vast understatement for D templates. Yes, easier to
> use, i agree.  But C++ templates are stone age comparing to D and i
> don't see this mentioned enough where it matters most. [...]
[...]

One of the major advances of D templates over C++ is its potent
combination with other D features like aliases/enums, static if,
compile-time introspection, and signature constraints. For example,
consider this:

	// This template checks if T satisfies certain properties, like
	// having a .NodeType type, parseAtom and parsePrimary methods,
	// etc.
	template isExprType(T) {
		static if (is(T.NodeType) &&
			is(isNodeType!(T.NodeType) &&
			is(T.parseAtom(Lexer.init)) &&
			is(T.parsePrimary(Lexer.init)) && ...)
		{
			enum isExprType = true;
		}
		else
			enum isExprType = false;
	}

	// This template checks that T is a valid node type. That is,
	// the previous template uses this one to enforce T.NodeType
	// being a type that satisfies certain properties.
	template isNodeType(T) {
		static if (is(T.precedence : int))
			enum isNodeType = true;
		else
			enum isNodeType = false;
	}

	// By using the above template as a signature constraint, we're
	// free to make use of properties we assumed about T, such as
	// parametrizing the return type on .NodeType as defined in T
	T.NodeType parseExpr(T)(Lexer lex)
		if (isExprType!T)
	{
		...
		// Or calling functions we verified to exist in T
		auto node1 = T.parseAtom(lex);
		auto node2 = T.parseAtom(lex);
		...
		// Or using properties of T.NodeType that we verified
		// exists, even though the template itself is
		// independent of how T.NodeType is even defined by T!
		if (node1.precedence < node2.precedence)
			...
	}

This is a ducktyping system where any type that satisfies isExprType
will work with parseExpr. This isn't just C++'s "conform to conventions
described in the missing accompanying documentation or get 10 pages of
template errors". This is *compile-time* verification that any type T
you instantiate parseExpr with, conforms to the requirements defined by
isExprType. And furthermore, even T.NodeType itself has been checked to
have certain properties -- such as a .precedence property that is
convertible to int (which means it can be an actual int field, or a
@property function that returns an int, or something that returns an
object convertible to int, etc.).

This allows you to implement an expression parser that is completely
independent of the concrete types of the expression being parsed.  AND
this is done without requiring 100 template parameters that specify
every configurable parameter: you just specify them in the type used to
instantiate parseExpr.

Better yet, if you designed the isExprType template correctly, you can
even have a derived class of an expression node that stores a value, and
specifies parsing functions that computes the value on the expression
on-the-fly. So instantiating parseExpr with the base class gives you an
expression tree, and instantiating it with the derived class computes
the value of the expression at parse-time. In the latter case, even the
return type is the correct derived class so you don't even need to
down-cast a base class reference to get at the value. (I actually have
code that does this. You cannot imagine the sense of power when code
like this can be written _cleanly_, without bending over backwards and
running a 100-meter dash with your left leg tied to your neck.)

Now try doing this in C++. It is in all likelihood plain impossible, or
so extremely painful that it's not worth the suffering to implement.


T

-- 
Lottery: tax on the stupid. -- Slashdotter


More information about the Digitalmars-d mailing list