What you really want to do is:
std::string sep = " ";
std::string end = "\n";
(std::cout << ... << (sep << args)) << end;
because you want (sep << args)
to be left-folded with std::cout
. This doesn't work, because sep << args
doesn't know it is being streamed to std::cout
or streamed at all; <<
is only streaming if the left hand side is a stream.
In short, the problem is that sep << args
doesn't understand it is streaming.
Your other problem is not enough lambda.
We can fix this.
template<class F>
struct ostreamer_t {
F f;
friend std::ostream& operator<<(std::ostream& os, ostreamer_t&& self ) {
self.f(os);
return os;
}
template<class T>
friend auto operator<<(ostreamer_t self, T&& t) {
auto f = [g = std::move(self.f), &t](auto&& os)mutable {
std::move(g)(os);
os << t;
};
return ostreamer_t<decltype(f)>{std::move(f)};
}
};
struct do_nothing_t {
template<class...Args>
void operator()(Args&&...)const {}
};
const ostreamer_t<do_nothing_t> ostreamer{{}};
template <typename... Args>
void print(Args... args)
{
std::string sep = " ";
std::string end = "\n";
(std::cout << ... << (ostreamer << sep << args)) << end;
}
live example. (I also used a literal for sep
to ensure I work with rvalues).
ostreamer
captures references to things it is <<
'd, then dumps them when in turn it is <<
to an ostream
.
This entire process should be transparent to the compiler, so a decent optimizer should evaporate everything involved.