是否有更好的方法将流中的功能链接在一起?

时间:2018-12-07 14:58:25

标签: c++ stream

我有一系列函数,这些函数将流作为输入并将转换写入输出流。现在界面看起来像这样:

void phase1(std::istream& in, std::ostream& out);
void phase2(std::istream& in, std::ostream& out);

std::istream data = get_initial_data();
std::stringstream first_result;
phase1(data, first_result);
std::stringstream second_result;
phase2(first_result, second_result);

是否有一种更简单/更自然的方式来链接这些呼叫而无需使用Boost(抱歉)?

1 个答案:

答案 0 :(得分:3)

我想你想做:

(phase1 | phase2 | phase3)( in, out );

所有胶水都在您身边。还有,

auto first_part = phase1|phase2;
auto second_part = phase3|phase4;
(first_part | second_part)( in, out );

也应该起作用。

namespace stream {
  template<class F=std::function<void(std::istream&, std::ostream&)>>
  struct pipe {
    F f;
    void operator()( std::istream& in, std::ostream& out ) const {
      f(in,out);
    }
    template<class O,
      std::enable_if_t< !std::is_same<O, F>{} && std::is_convertible<O, F>{}, bool> = true
    >
    pipe ( pipe <O> o ):
      f(std::move(o.f))
    {}
    pipe (F fin):
      f(std::move(fin))
    {}
  };
  template<class F>
  pipe (F)->pipe <F>;

  template<class First, class Second>
  auto operator|( pipe <First> first, pipe <Second> second )
  {
    return pipe {[=](auto& in, auto& out){
      std::stringstream intermediate;
      first( in, intermediate );
      second( intermediate, out );
    }};
  }
}

现在您可以这样做:

std::istream data = get_initial_data();
( pipe {phase1} | pipe {phase2} )( data, out );

我们可以将其扩展到源和接收器,从而将内容粘贴到输入中,但这通常需要连续传递样式来处理生命周期问题。

您还可以使用pipe <>以类型擦除的方式处理任何流管道对象。

Live example

如果您要获取和接收,则如下所示:

namespace stream {
  template<class Sig, class F=std::function<Sig>>
  struct operation;
  template<class R, class...Unused, class F>
  struct operation<R(Unused...), F>
  {
    F f;
    static_assert(
        std::is_convertible< std::result_of_t< F const&(Unused...) >, R >{}
    );
    template<class...Args>
    R operator()( Args&&...args ) const {
      return static_cast<R>(f(std::forward<Args>(args)...));
    }
    template<class O,
      std::enable_if_t< !std::is_same<O, F>{} && std::is_convertible<O, F>{}, bool> = true
    >
    operation ( operation<R(Unused...), O> o ):
      f(std::move(o.f))
    {}
    operation (F fin):
      f(std::move(fin))
    {}
  };
  template<class F=std::function<void(std::istream&, std::ostream&)>>
  struct pipe:operation<void(std::istream&, std::ostream&), F> {
    using operation<void(std::istream&, std::ostream&), F>::operation;
  };
  template<class F>
  pipe (F)->pipe <F>;

  template<class First, class Second>
  auto operator|( pipe <First> first, pipe <Second> second )
  {
    return pipe {[=](auto& in, auto& out){
      std::stringstream intermediate;
      first( in, intermediate );
      second( intermediate, out );
    }};
  }

  template<class F=std::function< void(std::function< void(std::ostream&)>) >>
  struct source:operation<void(std::function< void(std::istream&)>), F> {
      using operation<void(std::function< void(std::istream&)>), F>::operation;
  };
  template<class F>
  source(F)->source<F>;
  template<class F=std::function< void(std::function< void(std::ostream&)>) >>
  struct sink:operation<void(std::function< void(std::ostream&)>), F> {
      using operation<void(std::function< void(std::ostream&)>), F>::operation;
  };
  template<class F>
  sink(F)->sink<F>;

  template<class First, class Second>
  auto operator|( source<First> src, pipe<Second> p ) {
      return source{[=]( auto&& f ){
          src([&](auto&& in){
              std::stringstream ss;
              p( in, ss );
              f( ss );
          });
      }};
  }
  template<class First, class Second>
  auto operator|( pipe<First> p, sink<Second> snk ) {
      return sink{[=]( auto&& f ){
          snk([&](auto&& out){
              std::stringstream ss;
              f(ss);
              p(ss, out);
          });
      }};
  }
  void copy_f( std::istream& is, std::ostream& os ) {
      char c;
      while (is.get(c)) {
          os << c;
      }
  }
  inline pipe copy{copy_f};
  template<class First, class Second>
  void operator|( source<First> src, sink<Second> snk ) {
      src([&](auto&& in){
          snk([&](auto&& out){
              copy( in, out );
          });
      });
  }
}

您可以执行以下操作:

using namespace stream;
auto src = source{[](auto&& f){
    std::stringstream ss;
    ss << "Hello world\n";
    f(ss);
}};
auto snk = sink{[](auto&& f){
    f(std::cout);
}};
src|copy|copy|copy|snk;

Live example

source是一个功能对象,它依次接受一个功能对象,并将一个istream&传递给该对象。

sink是一个功能对象,它依次接受一个功能对象,并将ostream&传递给该对象。

此双功能语法处理令人讨厌的生命周期问题,并允许您在客户端流用户对流进行填充之前/之后进行清理。

here是一个更疯狂的版本,它支持直接到流/从流的直接管道传输。