26

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
nickeb96
  • 789
  • 1
  • 10
  • 16

7 Answers7

36

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...);
    }
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • For `(std::cout << sep << args, ...);` it says "expression not permitted as operand of fold expression". However it suggests I put parentheses around part of it like `((std::cout << sep << args), ...);` but that skips printing the first argument. Is there a version of this line that will work? – nickeb96 May 02 '17 at 17:25
  • @nickeb96 Yep, forgot the parentheses. I'm not sure what you mean about the rest, it doesn't skip the first argument. – Barry May 02 '17 at 17:32
  • 1
    Ah, I got the second part working. It prints a leading `sep` however. Is there a straight forward way to have `sep` only in between each argument? Or does that require a more complex solution like recursion? – nickeb96 May 02 '17 at 17:45
  • @nickeb96 If you want `sep` printed *after* each argument (no leading `sep`) then just move `sep` after `args`: `((std::cout << args << sep), ...);` [Demo](https://wandbox.org/permlink/NCu14w0YkfyId5GR) – monkey0506 Jun 14 '18 at 06:25
  • @nickeb96 I know the question is two years old, but I just discovered fold-expression. And operator ?: with extra variable helps here: `unsigned i = 0; ( ( std::cout << args << ( (++i – Adrian May 11 '20 at 16:51
15

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;
}

live wandbox example


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), 
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
11

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
3

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;
}
dodomorandi
  • 1,143
  • 1
  • 11
  • 18
  • I'm not an expert on lambdas but could you, hypothetically, make the lambda into a function and put it right above the print function? Or would that be more complicated and/or generate worse assembly? – nickeb96 May 02 '17 at 17:37
  • 1
    @nickeb96 If we start from the idea we want to optimize this example (just because I have the two _unoptimized_ parameters _sep_ and _end_ and I am not sure how I have to handle them), then we can easily obtain the same assembly (according to GCC 7.1) using a function instead of a lambda. Look [at this example](https://godbolt.org/g/TGoYLT): the two versions can be compiled defining or not USE_LAMBDA, and you can see that the result is the same. However, I am not sure I replied to your doubts, because I used _sep_ and _end_ as literals. – dodomorandi May 02 '17 at 20:09
2

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
Gerard097
  • 815
  • 4
  • 12
1

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...);
}
eferion
  • 872
  • 9
  • 14
1

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.

Meloman
  • 104
  • 1
  • 7