Raymii.org
Quis custodiet ipsos custodes?Home | About | All pages | Cluster Status | RSS Feed
C++ variadic template recursive example
Published: 08-06-2019 | Author: Remy van Elst | Text only version of this article
❗ This post is over five years old. It may no longer be up to date. Opinions may have changed.
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.
Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:
I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!
Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.
You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!
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 abase
case, and arecursive
case that reduces, eventually, into abase
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