Skip to main content

Raymii.org Logo (IEC resistor symbol) logo

Quis custodiet ipsos custodes?
Home | About | All pages | RSS Feed | Gopher

C++ variadic template recursive example

Published: 08-06-2019 | Author: Remy van Elst | Text only version of this article


Table of Contents


In this article I'll show you how to use a variadic template in C++. Variadic templates allow you to have a template with a variable number of arguments, also called a parameter pack. Unpacking that pack is more difficult than it should be, so we use a recursive template to iterate over all the parameters one by one. I've also included an example in Python to compare to.

If you like this article, consider sponsoring me by trying out a Digital Ocean VPS. With this link you'll get $100 credit for 60 days). (referral link)

Variadic templates

Variadic templates allow you to have a template with a variable number of arguments, also called a parameter pack. They were introduced in C++ 11, before that you had to use va_ macros with the ellipsis (...) operator, which is not type-safe and quite complex.

My use case was to have a template that allows an arbitrary number of arguments, of a few different types which will all be processed one by one sequentially. If you're looking to get all of the template pack and to something with it at once, this guide isn't for you. That requires either an initializer list or a tuple.

This is a great article on variadic templates with more examples. Wikipedia also has a page with some examples. This is also a good introduction.

Quoting Kevin from the last linked article:

When it comes to handling variadic functions, you can't think in the standard iterative C++ style. You need to write such functions recursively, with a base case, and a recursive case that reduces, eventually, into a base case. This implies a separate function for each case.

It took me a while to find out how to use the argument pack. At the end of this article is a comparison to Python, which is what I was used to before going into C++. There, you can use Foo(*args) or Foo(**kwargs) and a for loop. In C++ that for loop isn't easily possible.

Quoting davmac from lobste.rs who has an explanation on why this is not as easy as I would hope:

C++ for loops were traditionally a run-time loop and so they don't apply to parameter packs which are a compile-time construct only. While a for loop can now be evaluated at compile time (in a constexpr function) their syntax and semantics are unchanged in that case, i.e. they still apply to constructs that are (or at least which can also be) run-time. Allow for loops to iterate through parameter packs would require new syntax and semantics in a language that many feel is already getting too complex. That said I suspect you could do this with something more like a regular loop with C++2x features (or possibly even C++17, I haven't been keeping up to well) e.g. with template lambdas.

Unpacking the pack to a std::tuple is possible, but tricky to use afterwards. By using the ellipsis (...) operator in the correct place (left or right of a parameter name) we can control what happens.

Placing the ellipsis to the left of the parameter name declares an parameter pack. You use this in the template declaration, like so:

template <typename First, typename... Args>
void Foo(First first, Args... args) { }

Placing the ellipsis to the right of the parameter will cause the whole expression that precedes the ellipsis to be repeated for every subsequent argument unpacked from the argument pack. In our example, it is used in the variadic function to call the base function:

 Foo(args...);

The below Foo() example is recursive. The template function with First and Args... calls the template with Arg, which performs the actual action we want. Both functions have the same name, thus being overloaded. There also is a function (not template) which takes no arguments, but that is up to you if you need that. The functions could be named different (Base(Arg) and Other(First, Args...) e.g.).

The First argument is required to get the 'One or more' behaviour. If you would omit it, Foo(Args...) would accept zero or more parameters.

void Foo() example

Tested with CLion in C++ 11 mode.

// non template function to call with zero arguments
void Foo() {
    std::cout << " ";
}

// base template with 1 argument (which will be called from the variadic one).
template <typename Arg>
void Foo(Arg arg) {
    //std::cout << __PRETTY_FUNCTION__ << "\n";
   std::cout << arg << " ";
}

// variadic template with one or more arguments.
// ellipsis (...) operator to the left of the parameter name declares a parameter pack,
// allowing you to declare zero or more parameters (of different types).
template <typename First, typename... Args>
void Foo(First first, Args... args) {
    //std::cout << __PRETTY_FUNCTION__ << "\n";
    Foo(first);
    Foo(args...);
    //  ellipsis (...) operator to the right of the parameter name will cause
    //  the whole expression that precedes the ellipsis to be repeated for every
    //  subsequent argument unpacked from the argument pack, with the expressions
    //  separated by commas.
}

int main() {
    std::string one = "One";
    const char* two = "Two";
    float three = 3.3333333333;

    Foo(); // non template
    std::cout << std::endl;

    Foo(one); // base template
    std::cout << std::endl;

    Foo(one, two); // variadic argument template
    std::cout << std::endl;

    Foo(one, two, three);  // variadic argument template
    std::cout << std::endl;

    Foo(1, 2, three, 4, 5.7, 6/2, "lalala");  // variadic argument template
    return 0
}

Example output:

One 
One Two 
One Two 3.33333 
1 2 3.33333 4 5.7 3 lalala 

PRETTY_FUNCTION

__PRETTY_FUNCTION__

contains the name of the current function as a string, and for C++ functions (classes, namespaces, templates and overload) it contains the pretty name of the function including the signature of the function. It is a gcc extension that is mostly the same as

__FUNCTION__

or

__func__

By placing

std::cout << __PRETTY_FUNCTION__ << "\n"

at the top of the function, you can get an overview of what is called and when. Consider the following example:

template <typename Arg>
void Foo(Arg arg) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

template <typename First, typename... Args>
void Foo(First first, Args... args) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
    Foo(first);
    Foo(args...);
}

int main() {
    std::string one = "one";
    const char* two = "two";
    Foo(one); // base template
    std::cout << std::endl;
    Foo(one, two); // variadic argument template
    std::cout << std::endl;
}

Will output:

void Foo(Arg) [with Arg = std::__cxx11::basic_string<char>]

void Foo(First, Args ...) [with First = std::__cxx11::basic_string<char>; Args = {const char*}]
void Foo(Arg) [with Arg = std::__cxx11::basic_string<char>]
void Foo(Arg) [with Arg = const char*]

The first line is the base template. After the newline, the variadic template is called and that calls the base template twice.

Python

In Python preceding a method parameter with an asterisk (*args) defines it as a variable non-keyword list of arguments. Preceding with two asterisks (**kwargs) defines the parameter as a keyworded list of arguments. The parameters can be named anything as long as the asterisks are there, but convention says to use *args and **kwargs.

A small example of the above Foo() method in Python. Newline printing is omited by appending the comma (,) to the print() function.

#!/usr/bin/python

def Foo(first, *argv):
    print(first),
    print(" "),
    for arg in argv:
        print(arg),
        print(" "),
    print("")


bla = "Hello"
Foo('one')
Foo('one', 'two')
Foo('Remy', 2, 2.4, bla)

Output:

$ python test.py
one
one   two
Remy   2   2.4   Hello

An example using keyworded args (**kwargs):

#!/usr/bin/python
def Foo2(**kwargs):
    if kwargs:
        for key, value in kwargs.iteritems():
            print("%s: %s, ") % (key,value),
    print("")

bla = "Hello"
Foo2(first='one')
Foo2(first='one', second='two')
Foo2(first='one', second='two', three=3, var=bla)

Output:

first: one,
second: two,  first: one,
var: Hello,  second: two,  three: 3,  first: one,
Tags: c++ , cpp , development , linux , python , snippets , software , templates , variadic