Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

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 C++. Then, we'll do it the various ways the D programming language makes possible.

C++ Solutions

The Overload 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.

C++ Variadic Templates

C++11 supports variadic templates:

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.

D Programming Language Solutions

The D Look Ma No Templates Solution

The current best practice, e.g. as espoused by the standard library, favours the variadic template solution. However, if the template solution is not practical, D provides a runtime solution utilizing the TypeInfo system (An example is shown below). There is a lexical similarity with the non-template variadic functions provided by C and C++ (va_list), however the solution D provides is typesafe whereas the aforementioned alternative is not.

import core.vararg;
import core.stdc.stdio;
/*
This function was part of D1.0, but does not exist anymore, it has been
resurrected here to give a realistic example of how this feature could be used.
*/
void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr);
void print(...)
{
    void putc(dchar c)
    {
        fputc(c, stdout);
    }

    doFormat(&putc, _arguments, _argptr);
}

Translating the Variadic C++ Solution into D

Variadic templates in D enable a straightforward translation of the C++11 variadic solution:

void print()
{
}

void print(T, A...)(T t, A a)
{
    import std.stdio;
    writeln(t);
    print(a);
}

There are two overloads. 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 any remaining values. A... says the parameter is a sequence, and Implicit Function Template Instantiation will fill in A with all the types of any arguments supplied following t. So, print(7, 'a', 6.8) will fill in int for T, and a type sequence (char, double) for A. The parameter a is an lvalue sequence of any arguments supplied after t. See Compile-time Sequences for more information.

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 ifs, which provide for conditional compilation:

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

Sequences can be manipulated much like arrays. So a.length resolves to the number of elements in the sequence a. a[0] gives the first element in the sequence. a[1 .. $] creates a new sequence from any remaining elements in the original sequence.

The Foreach Solution

But since sequences can be manipulated like arrays, we can use a foreach statement to iterate over the sequence's elements:

void print(A...)(A a)
{
    foreach(t; a)
        writeln(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.