How to call a function on all variadic template args?
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.
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), ...);