The classic example for C++17 fold expressions is printing all arguments:
template<typename ... Args>
void print(Args ... args)
{
(cout << ... << args);
}
Example:
print("Hello", 12, 234.3, complex<float>{12.3f, 32.8f});
Output:
Hello12234.3(12.3,32.8)
I'd like to add newlines to my output. However, I can't find a good way to do that, the best I've found so far:
template<typename ... Args>
void print(Args ... args)
{
(cout << ... << ((std::ostringstream{} << args << "\n").str()));
}
This however isn't zero-overhead, as it constructs a temporary ostringstream
for each argument.
The following versions don't work either:
(cout << ... << " " << args);
error: expression not permitted as operand of fold expression
And
(cout << ... << (" " << args));
error: invalid operands to binary expression
I understand why the last two versions don't work. Is there a more elegant solution to this problem, using fold expressions?
repeat
takes a function object f
, and return a new function object. The return value runs f on each of its args. It "repeats" f
on each of its args.
template<class F>
auto repeat( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args)mutable{
( void(f(args)), ... );
};
}
Use:
repeat
( [](auto&&x){ std::cout << x << "\n"; } )
( args... );
This uses fold expressions, but only indirectly. And honestly, you could have written this in C++14 (just the body of repeat
would be uglier).
We could also write a streamer that works with <<
to do it "more inline" and use fold expressions directly:
template<class F>
struct ostreamer_t {
F f;
friend std::ostream& operator<<( std::ostream& os, ostreamer_t&& self ) {
std::move(self).f(os);
return os;
}
};
template<class F>
ostreamer_t<F> ostreamer( F&& f ) { return {std::forward<F>(f)}; }
then we use it like this:
(std::cout << ... << ostreamer([&](auto&& os){ os << " " << args;}));
ostreamer
takes a function object. It returns an object that overloads <<
such that when you pass it an ostream on the left, it invokes the function object with the ostream.
No temporary stream is created.
Live examples.
Update: T.C.'s comment below provided a better solution:
template<typename ... Args>
void print(Args ... args)
{
((cout << args << '\n'), ...);
}
You can use a fold expression over the comma operator:
template<typename ... Args>
void print(Args ... args)
{
([](const auto& x){ cout << x << "\n"; }(args), ...);
}
Usage:
int main()
{
print("a", 1, 1000);
}
a
1
1000
(Note: this prints a trailing newline as well.)
live wandbox example
assembly comparison on godbolt
Explanation:
[](const auto& x){ cout << x << "\n"; }
is a lambda that given x
prints x
and '\n'
.
[](const auto& x){ cout << x << "\n"; }(args)
immediately invokes the lambda with args
.
([](const auto& x){ cout << x << "\n"; }(args), ...)
is a fold expression over the comma operator that expands in the following way:
// (pseudocode)
[](const auto& x){ cout << x << "\n"; }(args<0>),
[](const auto& x){ cout << x << "\n"; }(args<1>),
[](const auto& x){ cout << x << "\n"; }(args<2>),
// ...
[](const auto& x){ cout << x << "\n"; }(args<N>)
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