创建一个可以在没有实验的情况下变形的管道::任何



我正在尝试创建一个管道实现,该实现将使接口在某些给定数据上链接多个操作,同时维持上述数据的不变性。这里的警告是,操作(显然是纯函数)应该能够变形数据的类型(例如。字符串输出)。也就是说,我进行了使用STD ::实验:: Any的实现。

class Pipe {
    any data;
public:
    Pipe (any data) : data(data) {}
    any unpack () {
        return data;
    }
    Pipe operator| (function<any(any)> reducer) {
        return Pipe(reducer(this->unpack()));
    }
    Pipe operator|| (any & destination) {
        destination = unpack();
        return *this;
    }
    static Pipe from (any data) {
        return Pipe(data);
    }
};

因此,该管道的API如下:

Pipe::from(some_data) | do_something | do_something_else || unpacked

好吧,它有效。问题在于它容易出错,因为您必须随时随地使用明确的any_cast实际访问any隐藏的数据。也就是说,任何还原器函数都必须猜测它将收到的类型,如果那不匹配现实,那是一个运行时错误,即代码中的许多不必要的尝试或其他内容,以及这些其他行用于明确的打字。

所以我有两个问题的"管道"。

  1. 是否可以使此较小的错误?
  2. 可以不使用any实现相同的效果吗?也许有些模板类,任何事情都会做。

模板确实可以删除any的使用,例如:

template <typename T>
class Pipe {
    T data;
public:
    Pipe (T data) : data(data) {}
    const T& unpack () const { return data; }
    T& unpack () { return data; }
    template <typename F>
    auto operator| (F reducer) -> decltype(reducer(std::declval<T>()))
    {
        return Pipe<decltype(reducer(data))>(reducer(data));
    }
    Pipe operator|| (T& destination) const
    {
        destination = unpack();
        return *this;
    }
};
template <typename T>
Pipe<T> MakePipe(T data) {
    return Pipe<T>(data);
}

用法将为

MakePipe(some_data) | do_something | do_something_else || unpacked;

,因此管道与存储数据有关。

namespace plumbing {
  // tags to identify plumping components:
  struct pipe_tag {};
  struct src_tag{};
  struct sink_tag{};
  // this lets us tag function objects.  To handle function pointers,
  // we need a specialization.
  template<class F, class Tag>
  struct tagged_f:F,Tag{
    using F::F;
    tagged_f(F&& f):F(std::move(f)){}
  };
  template<class Tag, class F>
  tagged_f<F,Tag> tag(F f){ return {std::move(f)}; }
  // detect various tags:
  template<class F> using is_pipe = std::is_base_of<pipe_tag, F>;
  template<class F> using is_src = std::is_base_of<src_tag, F>;
  template<class F> using is_sink = std::is_base_of<sink_tag, F>;
  // type erased versions of the plumbing components
  // useful to store the end result of a plumbing job:
  template<class T>
  using gen_sink = tagged_f<std::function<void(T)>, sink_tag>;
  template<class T>
  using gen_src = tagged_f<std::function<void(gen_sink<T>)>, src_tag>;
  template<class In, class Out>
  using gen_pipe = tagged_f<std::function<void(gen_src<In>, gen_sink<Out>)>, pipe_tag>;
  // SFINAE helper using the is_X templates above:    
  template<class T, template<class...>class Test>
  using check=std::enable_if_t< Test<T>{}, bool >;
  // really simple source, pipe and sink helpers:
  template<class T>
  auto simple_src( T in ){
    return tag<src_tag>([=](auto&& sink){ sink(in); });
  }
  template<class F>
  auto simple_pipe( F f ){
    return tag<pipe_tag>( [=](auto&& src, auto&& sink){
      src([&](auto&& in){sink(f(in));});         
    });
  }
  template<class V>
  auto vector_sink(V& v){
    return tag<sink_tag>([&](auto&&t){ v.push_back(decltype(t)(t)); });
  }
  // operator| implementations:
  // Src|Pipe is a Src:
  template<class Src, class Pipe,
    check<Src, is_src> =true,
    check<Pipe, is_pipe> =true
  >
  auto operator|( Src src, Pipe pipe ){
    return tag<src_tag>([=](auto&& sink){
      pipe( src, sink );
    });
  }
  // Pipe|Sink is a Sink:
  template<class Pipe, class Sink,
    check<Sink, is_sink> =true,
    check<Pipe, is_pipe> =true
  >
  auto operator|( Pipe pipe, Sink sink ){
    return tag<sink_tag>([=](auto&& t){
      pipe( simple_src(t), sink );
    });
  }
  // Pipe|Pipe is a Pipe:
  template<class PipeA, class PipeB,
    check<PipeA, is_pipe> =true,
    check<PipeB, is_pipe> =true
  >
  auto operator|( PipeA a, PipeB b ){
    return tag<pipe_tag>([=](auto&& src, auto&& sink){
      b( src|a, sink );
    });
  }
  // Src|Sink is a callable object on 0 arguments:
  template<class Src, class Sink,
    check<Src, is_src> =true,
    check<Sink, is_sink> =true
  >
  auto operator|( Src src, Sink sink ){
    return [=]{ src(sink); };
  }
}

现场示例。

这使您可以设置一个无状态的管道链,键入它(使用gen_pipe<In,Out>),具有1块的数据变为零或多个,缓冲和在字符串上工作(如果添加终止符号),等等。

。 。

t的水槽是一种消耗t。

的函数

t的来源是一种消耗t的水槽的函数。这允许它们产生超过1。

从t到u的管道是一个函数,它既采用t源,又有u的源。

数据不存储在管道中,而是流过它们。

using namespace plumbing;
std::vector<int> v;
// build pipe, don't run it:
auto code = simple_src(7)|simple_pipe([](auto x){return x*2;})|vector_sink(v);
// run pipe:
code(); // v contains {14}
// print:
for (auto x:v)
    std::cout << x << "n";

这些管道是未构图的,并在连接它们时检查类型是否匹配。

这样您就可以有这样的水槽:

auto print_sink = tag<sink_tag>( [](auto&& x){ std::cout << x; } );

同样的水槽可以连接到多种不同类型的数据。

auto hello_world_src = tag<src_tag>( [](auto&& sink) {
  for (char c : "hello world")
    sink(c);
});

然后

(hello_world_src|print_sink)();

打印"hello world"。同时

(simple_src(3.14)|print_sink)();

打印3.14

我们甚至可以幻想:

auto foreach_src = [](auto&& c){
  return tag<src_tag>([c=decltype(c)(c)](auto&& sink) {
    for( auto&& x:c )
      sink(decltype(x)(x));
  });
};

这是一个范围并在容器元素上返回源的函数。

auto foreach_pipe = tag<pipe_tag>([foreach_src]( auto&& src, auto&& sink ) {
  src( [&](auto&& c) {
    foreach_src(c)(sink);
  } );
});

这是管道。它需要一个范围的来源和一个元素的水槽,并将它们连接起来。

有更多示例

相关内容

最新更新