I would like to do
template<typename... ArgTypes> void print(ArgTypes... Args)
{
print(Args)...;
}
And have it be equivalent to this quite bulky recursive chain:
template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args)
{
print(t);
print(Args...);
}
followed by explicit single-parameter specializations for every type I'd like to print.
The "problem" with the recursive implementation is that a lot of redundant code is generated, because each recursive step results in a new function of N-1
arguments, whereas the code I'd like to have would only generate code for a single N
-arg print
function, and have at most N
specialized print
functions.
To access variadic arguments, we must include the <stdarg. h> header.
Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.
A variadic function allows you to accept any arbitrary number of arguments in a function.
C++17 fold expression
(f(args), ...);
If you call something that might return an object with overloaded comma operator use:
((void)f(args), ...);
Pre-C++17 solution
The typical approach here is to use a dumb list-initializer and do the expansion inside it:
{ print(Args)... }
Order of evaluation is guaranteed left-to-right in curly initialisers.
But print
returns void
so we need to work around that. Let's make it an int then.
{ (print(Args), 0)... }
This won't work as a statement directly, though. We need to give it a type.
using expand_type = int[];
expand_type{ (print(Args), 0)... };
This works as long as there is always one element in the Args
pack. Zero-sized arrays are not valid, but we can work around that by making it always have at least one element.
expand_type{ 0, (print(Args), 0)... };
We can make this pattern reusable with a macro.
namespace so {
using expand_type = int[];
}
#define SO_EXPAND_SIDE_EFFECTS(PATTERN) ::so::expand_type{ 0, ((PATTERN), 0)... }
// usage
SO_EXPAND_SIDE_EFFECTS(print(Args));
However, making this reusable requires a bit more attention to some details. We don't want overloaded comma operators to be used here. Comma cannot be overloaded with one of the arguments void
, so let's take advantage of that.
#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
::so::expand_type{ 0, ((PATTERN), void(), 0)... }
If you are paranoid afraid of the compiler allocating large arrays of zeros for naught, you can use some other type that can be list-initialised like that but stores nothing.
namespace so {
struct expand_type {
template <typename... T>
expand_type(T&&...) {}
};
}
C++17 fold expression:
(f(args), ...);
Keep simple things simple ;-)
If you call something that might return an object with overloaded comma operator use:
((void)f(args), ...);
You can use even more simple and readable approach
template<typename... ArgTypes> void print(ArgTypes... Args)
{
for (const auto& arg : {Args...})
{
print(arg);
}
}
I have played with both variants on compile explorer and both gcc and clang with O3 or O2 produce exactly the same code but my variant is obviously cleaner.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With