12

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?

Thomas McGuire
  • 5,308
  • 26
  • 45

2 Answers2

19

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.)


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>)
    
Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 9
    Why lambda? `((cout << args << '\n'), ...)` – T.C. Mar 28 '17 at 20:22
  • @T.C. ah, nice - this is even simpler than I expected. I used a lambda because my thought process was *"converting `for_each_argument(f, args...)` into a fold expression over the comma operator"*, but that was highly unnecessary. – Vittorio Romeo Mar 28 '17 at 20:30
  • Interesting solution, thanks! Why is this only possible in C++17, I thought C++11 also allows expanding over a comma? But why is this specific case not possible in C++11? – Thomas McGuire Mar 30 '17 at 11:16
  • 1
    This is a *fold expression* over the comma operator. C++11 allows a similar technique (`for_each_argument`) by (ab)using contexts where an expansion can take place. [I gave a talk about that at CppCon 2015](https://www.youtube.com/watch?v=2l83JlqkzBk) that explains how it works. – Vittorio Romeo Mar 30 '17 at 12:37
  • Is there a way to print without trailing newline? – user3882729 May 02 '22 at 01:51
8

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.

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