www.digitalmars.com

D Programming Language 2.0


Last update Mon Dec 26 20:17:04 2011

Variadic Templates

The problem statement is simple: write a function that takes an arbitrary number of values of arbitrary types, and print those values out one per line in a manner appropriate to the type. For example, the code:

print(7, 'a', 6.8);

should output:

7
'a'
6.8

We'll explore how this can be done in standard C++, followed by doing it using the proposed variadic template C++ extension. Then, we'll do it the various ways the D programming language makes possible.

C++ Solutions

The Standard C++ Solution

The straightforward way to do this in standard C++ is to use a series of function templates, one for each number of arguments:

#include <iostream>
using namespace::std;

void print()
{
}

template<class T1> void print(T1 a1)
{
    cout << a1 << endl;
}

template<class T1, class T2> void print(T1 a1, T2 a2)
{
    cout << a1 << endl;
    cout << a2 << endl;
}

template<class T1, class T2, class T3> void print(T1 a1, T2 a2, T3 a3)
{
    cout << a1 << endl;
    cout << a2 << endl;
    cout << a3 << endl;
}

... etc ...

This poses significant problems:

One, the function implementor must decide in advance what the maximum number of arguments the function will have. The implementor will usually err on the side of excess, and ten, or even twenty overloads of print() will be written. Yet inevitably, some user somewhere will require just one more argument. So this solution is never quite thoroughly right.

Two, the logic of the function template body must be cut and paste duplicated, then carefully modified, for every one of those function templates. If the logic needs to be adjusted, all of those function templates must receive the same adjustment, which is tedious and error prone.

Three, as is typical for function overloads, there is no obvious visual connection between them, they stand independently. This makes it more difficult to understand the code, especially if the implementor isn't careful to place them and format them in a consistent style.

Four, it leads to source code bloat which slows down compilation.

The C++ Extension Solution

Douglas Gregor has proposed a variadic template scheme [1] for C++ that solves these problems. The result looks like:

void print()
{
}

template<class T, class... U> void print(T a1, U... an)
{
    cout << a1 << newline;
    print(an...);
}

It uses recursive function template instantiation to pick off the arguments one by one. A specialization with no arguments ends the recursion. It's a neat and tidy solution, but with one glaring problem: it's a proposed extension, which means it isn't part of the C++ standard, may not get into the C++ standard in its current form, may not get into the standard in any form, and even if it does, it may be many, many years before the feature is commonly implemented.

D Programming Language Solutions

The D Look Ma No Templates Solution

It is not practical to solve this problem in C++ without using templates. In D, one can because D supports typesafe variadic function parameters.

import std.stdio;

void print(...)
{
    foreach (arg; _arguments)
    {
	writefx(stdout, (&arg)[0 .. 1], _argptr, 1);
	auto size = arg.tsize();
	_argptr += ((size + size_t.sizeof - 1) & ~(size_t.sizeof - 1));
    }
}

It isn't elegant or the most efficient, but it does work, and it is neatly encapsulated into a single function. (It relies on the predefined parameters _argptr and _arguments which give a pointer to the values and their types, respectively.)

Translating the Variadic C++ Solution into D

Variadic templates in D enable a straightforward translation of the proposed C++ variadic syntax:

void print()()
{
}

void print(T, A...)(T t, A a)
{
    writefln(t);
    print(a);
}

There are two function templates. The first provides the degenerate case of no arguments, and a terminus for the recursion of the second. The second has two arguments: t for the first value and a for the rest of the values. A... says the parameter is a tuple, and implicit function template instantiation will fill in A with the list of all the types following t. So, print(7, 'a', 6.8) will fill in int for T, and a tuple (char, double) for A. The parameter a becomes an expression tuple of the arguments.

The function works by printing the first parameter t, and then recursively calling itself with the remaining arguments a. The recursion terminates when there are no longer any arguments by calling print()().

The Static If Solution

It would be nice to encapsulate all the logic into a single function. One way to do that is by using static if's, which provide for conditional compilation:

void print(A...)(A a)
{
    static if (a.length)
    {
	writefln(a[0]);
	static if (a.length > 1)
	    print(a[1 .. length]);
    }
}

Tuples can be manipulated much like arrays. So a.length resolves to the number of expressions in the tuple a. a[0] gives the first expression in the tuple. a[1 .. length] creates a new tuple by slicing the original tuple.

The Foreach Solution

But since tuples can be manipulated like arrays, we can use a foreach statement to 'loop' over the tuple's expressions:

void print(A...)(A a)
{
    foreach(t; a)
	writefln(t);
}

The end result is remarkably simple, self-contained, compact and efficient.

Acknowledgments

  1. Thanks to Andrei Alexandrescu for explaining to me how variadic templates need to work and why they are so important.
  2. Thanks to Douglas Gregor, Jaakko Jaervi, and Gary Powell for their inspirational work on C++ variadic templates.

References

  1. Variadic Templates N2080




Forums | Comments |  D  | Search | Downloads | Home