Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using fold expressions to print all variadic arguments with newlines inbetween

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?

like image 266
Thomas McGuire Avatar asked Mar 28 '17 12:03

Thomas McGuire


2 Answers

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.

like image 105
Yakk - Adam Nevraumont Avatar answered Oct 08 '22 11:10

Yakk - Adam Nevraumont


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>)
    
like image 37
Vittorio Romeo Avatar answered Oct 08 '22 12:10

Vittorio Romeo