Is it possible to insert extra operation in fold expression?

851 views Asked by At

In C++17, fold expression is available, so to print arguments, we could use

#define EOL '\n'

template<typename ...Args>
void output_argus(Args&&... args) 
{
    (cout << ... << args) << EOL;
}


int main()
{
    output_argus(1, "test", 5.6f);
}

having the output
1test5.6

What if I would like using the fold expression appending an extra character '\n' to each element to get the following results?

1
test
5.6

Is that even possible? If yes, how?

3

There are 3 answers

1
max66 On BEST ANSWER

What if I would like using the fold expression appending an extra character '\n' to each element to get the following results?

You can use the power of the comma operator

 ((std::cout << args << std::endl), ...);

or, as suggested by Quentin (thanks) and as you asked, you can simply use \n instead of std::endl (to avoid multiple flushing of the stream)

 ((std::cout << args << '\n'), ...); 
4
n. m. could be an AI On

I know that the comma operator is probably the easiest way to do that, but for completeness here's something I came up with, mainly because I wanted to show off my little generalisation of iomanip. The standard library iomanips are functions. There's an << overload that takes a function pointer. I extended that for arbitrary callable objects that take and return streams by reference.

template <class Stream, class Func>
auto operator << (Stream& s, Func f) -> 
        std::enable_if_t<std::is_same_v<decltype(f(s)), Stream&>, Stream&>
{
    return f(s);
}

With this little tool in our toolbox, it's easy to write a fold expression that does absolutely anything we want.

template<typename ...Args>
void output_args(Args&&... args)
{
     (std::cout << ... << [&](auto& x)->auto&{return x << args << '\n';});
}

This technique can be used in scenarios where we need to capture the value of the fold expression, rather than its side effects. The comma operator is less useful in such contexts.

0
Yakk - Adam Nevraumont On

This is @n.m.'s solution without the rude global greedy operator<<.

template<class Os>
struct chain_stream {
  Os& stream;
  template<class Rhs,
    std::enable_if_t<std::is_same_v<Os&, decltype(std::declval<Os&>() << std::declval<Rhs>())>, bool> =true
  >
  friend chain_stream<Os> const& operator<<( chain_stream<Os> const& os, Rhs&& rhs ) {
    os.stream << std::forward<Rhs>(rhs);
    return os;
  }
  // iomanipulator:
  friend chain_stream<Os> const& operator<<( chain_stream<Os> const& os, Os&(*rhs)(Os&) ) {
    os.stream << rhs;
    return os;
  }
  template<class Rhs,
    std::enable_if_t<
      std::is_same_v< std::result_of_t< Rhs&&(chain_stream const&) >, void >
      || std::is_same_v< std::result_of_t< Rhs&&(chain_stream const&) >, Os& >,
    bool> =true
  >
  friend chain_stream<Os> const& operator<<( chain_stream<Os> const& os, Rhs&& rhs ) {
    std::forward<Rhs>(rhs)( os );
    return os;
  }
};

now we can do:

 (chain_stream{std::cout} << ... << [&](auto& x){x << args << '\n';});

and it works.