Reputation: 789
I don't understand why this doesn't work. Could someone who understands templates and variadic expression folding explain what is going on and give a solution that does work?
#include <iostream>
#include <string>
template <typename... Args>
void print(Args... args)
{
std::string sep = " ";
std::string end = "\n";
(std::cout << ... << sep << args) << end;
}
int main()
{
print(1, 2, 3);
}
It should print out each of the args with a space in between and a newline at the end. It works if you remove the sep <<
but then there is no space between each argument when it is printed.
Upvotes: 26
Views: 11555
Reputation: 825
Another approach is the next:
#include <iostream>
template<class U, class... T>
void printSpaced(const U& u, const T&... args)
{
using std::cout;
using std::endl;
((cout << u) << ... << (cout << ' ', args)) << endl;
}
This way you won't get leading/trailing space
Usage:
printSpaced(1, 2, "Hello", 4.5f); //Output 1 2 Hello 4.5 and no trailing space
Upvotes: 3
Reputation: 104
If you don't want leading/trailing sep
:
template <typename First, typename... Rest>
void print(First first, Rest... rest)
{
std::string sep = " ";
std::string end = "\n";
std::cout << first;
((std::cout << sep << rest), ...);
std::cout << end;
}
You need to make std::cout << end;
a separate instruction to handle case with one parameter.
Upvotes: 1
Reputation: 902
You can try something like this
template <typename... Args>
void print(Args... args)
{
bool first = true;
auto lambda = [&](auto param)
{
if( !first) std::cout << ',';
first= false;
return param;
};
((std::cout << lambda(args)), ...);
}
The lambda ensures separator are only inserted between two items.
On the other hand if you dont wanna use lambdas you can overload the template:
template<typename T>
void print(T item)
{
std::cout << item;
}
template<typename T, typename... Args>
void print(T item, Args... args)
{
print(item);
std::cout << ',';
print(args...);
}
Upvotes: 1
Reputation: 93384
This will work, but it will print a trailing space:
template <typename... Args>
void print(Args... args)
{
std::string sep = " ";
std::string end = "\n";
((std::cout << args << sep), ...) << end;
}
In this case, a fold over the comma operator is being performed, resulting in an expansion like:
// (pseudocode)
(std::cout << args<0> << sep),
(std::cout << args<1> << sep),
(std::cout << args<2> << sep),
...,
(std::cout << args<N> << sep),
Upvotes: 15
Reputation: 304112
The grammar for binary fold-expressions must be one of:
(pack op ... op init)
(init op ... op pack)
What you have is (std::cout << ... << sep << args)
, which doesn't fit either form. You need something like (cout << ... << pack)
, which is why removing sep
works.
Instead, you can either fold over a comma:
((std::cout << sep << args), ...);
or use recursion:
template <class A, class... Args>
void print(A arg, Args... args) {
std::cout << arg;
if constexpr (sizeof...(Args) > 0) {
std::cout << sep;
print(args...);
}
}
Upvotes: 36
Reputation: 1153
As answered by others, you are trying to use a wrong fold-expression format. You could use a lambda helper for your purpose in a very simple way:
template <typename... Args>
void print(Args&&... args)
{
std::string sep = " ";
std::string end = "\n";
auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
std::cout << sep;
return arg;
};
(std::cout << ... << streamSep(args)) << end;
}
This will follow the behaviour expected in the code you wrote. However, if you want to avoid the sep before the first argument, you could use the following:
template <typename Arg, typename... Args>
void print(Arg&& arg, Args&&... args)
{
std::string sep = " ";
std::string end = "\n";
auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
std::cout << sep;
return arg;
};
std::cout << arg;
(std::cout << ... << streamSep(args)) << end;
}
Upvotes: 3
Reputation: 275946
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.
Upvotes: 11