使用折叠表达式打印所有带有换行符的可变参数

Using fold expressions to print all variadic arguments with newlines inbetween

C++17 折叠表达式的经典示例是打印所有参数:

template<typename ... Args>
void print(Args ... args)
{
    (cout << ... << args);
}

示例:

print("Hello", 12, 234.3, complex<float>{12.3f, 32.8f});

输出:

Hello12234.3(12.3,32.8)

我想在我的输出中添加换行符。但是,我找不到一个好的方法,到目前为止我找到的最好的方法:

template<typename ... Args>
void print(Args ... args)
{
    (cout << ... << ((std::ostringstream{} << args << "\n").str()));
}

但这不是零开销,因为它为每个参数构造了一个临时 ostringstream

以下版本也不起作用:

(cout << ... << " " << args);

error: expression not permitted as operand of fold expression

(cout << ... << (" " << args));

error: invalid operands to binary expression 

我明白为什么最后两个版本不起作用。 这个问题有没有更优雅的解决方案,使用折叠表达式?

更新:T.C.下面的评论提供了更好的解决方案:

template<typename ... Args>
void print(Args ... args)
{
    ((cout << args << '\n'), ...);
}

您可以在 逗号运算符:

上使用 折叠表达式
template<typename ... Args>
void print(Args ... args)
{
    ([](const auto& x){ cout << x << "\n"; }(args), ...);
}

用法:

int main()
{
    print("a", 1, 1000);
}

a

1

1000

(注意:这也会打印尾随换行符。)


解释:

  • [](const auto& x){ cout << x << "\n"; } 是给定 x 打印 x'\n'.

  • 的 lambda
  • [](const auto& x){ cout << x << "\n"; }(args) 立即调用带有 args 的 lambda。

  • ([](const auto& x){ cout << x << "\n"; }(args), ...) 逗号运算符 上的折叠表达式,按以下方式展开:

    // (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>)
    

repeat 接受一个函数对象 f,return 接受一个新的函数对象。 return 值在其每个参数上运行 f。它在每个参数上 "repeats" f

template<class F>
auto repeat( F&& f ) {
  return [f=std::forward<F>(f)](auto&&...args)mutable{
    ( void(f(args)), ... );
  };
}

使用:

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

这使用了折叠表达式,但只是间接使用。老实说,你可以用 C++14 写这个(只是 repeat 的主体会更丑)。

我们也可以编写一个与 << 一起工作的流媒体来做到这一点 "more inline" 并直接使用折叠表达式:

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

然后我们这样使用它:

(std::cout << ... << ostreamer([&](auto&& os){ os << " " << args;}));

ostreamer 接受一个函数对象。它 return 是一个重载 << 的对象,这样当您向它传递左侧的 ostream 时,它会调用带有 ostream 的函数对象。

没有创建临时流。

Live examples.