Reputation: 10122
As part of a logging library, I would like to be able to iterate a parameter pack, writing each value to a stream. My first attempt doesn't compile however. The first error is "error C2144: syntax error : 'int' should be preceded by '}'".
#include <sstream>
#include <ostream>
#include <iomanip>
#include <fstream>
template <typename ...Args>
std::ostream & Write(std::ostream & o, std::initializer_list<Args...> list)
{
size_t size = list.size();
if(list.size() > 0)
{
for(size_t i = 0; i < (size - 1); i++)
o << list[i] << ", ";
o << list[i];
}
return o;
}
template<typename ...Args>
std::ostream & Write(std::ostream & o, Args...)
{
return Write(o, { Args... });
}
int main(int argc, wchar_t * argv[])
{
std::ostringstream o;
Write(o, 1, "Hello", 2, "World", 3, 1.4857);
// o should contain the string of characters "1, Hello, 2, World, 3, 1.4857"
return 0;
}
How do I iterate each item in ... and send it to the stream?
Upvotes: 6
Views: 14827
Reputation: 97565
It's possible to write a helper function to iterate over a parameter pack:
// Invoke each of the functions `f` in sequence
template<typename... F>
void invoke_all(F&&... f) {
std::initializer_list<bool>{(f(), false)...};
}
Which in your case, can be used as:
template<typename... Args>
std::ostream& Write(std::ostream& os, Args&&... args) {
auto joiner = std::experimental::ostream_joiner(os, ", ");
invoke_all([&]() {
*joiner++ = std::forward<Args>(args);
}...);
return os;
}
Upvotes: 3
Reputation: 137301
Recursion is one option:
template<typename Arg>
std::ostream & Write(std::ostream & o, Arg&& arg) {
return o << std::forward<Arg>(arg);
}
template<typename Arg, typename ...Args>
std::ostream & Write(std::ostream & o, Arg&& arg, Args&&... args)
{
o << std::forward<Arg>(arg) << ", ";
return Write(o, std::forward<Args>(args)...);
}
Demo.
Alternatively, the pack expansion trick still works, with a little tweak - you need to special-case the first item in the list:
template<typename Arg, typename ...Args>
std::ostream & Write(std::ostream & o, Arg&& arg, Args&&... args)
{
o << std::forward<Arg>(arg);
using expander = int[];
(void) expander{ (o << ", " << std::forward<Args>(args), void(), 0)... };
return o;
}
Demo.
Upvotes: 12
Reputation: 60979
If an extra comma at the end is okay, use
template<typename... Args>
std::ostream& Write(std::ostream& o, Args&&... args)
{
std::initializer_list<bool> { o << std::forward<Args>(args) << ", "... };
return o;
}
Otherwise you will need recursion:
template <typename A>
std::ostream& Write(std::ostream& o, A&& a)
{ return o << std::forward<A>(a); }
template <typename A, typename... Args>
std::ostream& Write(std::ostream& o, A&& a, Args&&... args)
{
return Write(o << std::forward<A>(a) << ", ", std::forward<Args>(args)...);
}
Upvotes: 2
Reputation: 71899
Here's a utility I like to use:
#define VARIADIC_DETAIL_CAT2(a, b) a ## b
#define VARIADIC_DETAIL_CAT(a, b) VARIADIC_DETAIL_CAT2(a, b)
#define VARIADIC_EXPAND(...) \
int VARIADIC_DETAIL_CAT(libutil_expando, __COUNTER__) [] = { 0, \
((__VA_ARGS__), 0)... \
} \
/**/
With this, you can write:
template<typename... Args>
std::ostream & Write(std::ostream& o, Args&&... args)
{
VARIADIC_EXPAND(o << std::forward<Args>(args));
}
Getting correct separators is trickier.
Upvotes: 2