2017-11-29 6 views
2

私は、このデータの不変性を維持しながら、特定のデータに対して複数の操作を連鎖させるインターフェイスを公開するパイプ実装を作成しようとしています。ここでの注意点は、操作(明らかに純粋な関数である)がデータのタイプを変えることができることです(OCRツールを通過するOpenCVマットから作成されたパイプなど)。文字列出力)。つまり、私はstd :: experimental :: anyを利用してこの実装を行いました。次のようにexperimental :: anyなしでmorphタイプを持つパイプを作成する

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によって隠蔽されたデータに実際にアクセスしたいときはいつも明示的にany_castを使用する必要があるため、エラーが発生しやすいということです。つまり、減速機の機能はどのタイプを受け取るのかを推測しなければならず、現実に合わない場合は、ランタイムエラーです。つまり、不要な試行錯誤やコード内の何か、明示的な型キャストの場合。

私はこの 'pipe thingy'について2つの質問があります。

  1. これをエラーを起こしやすいようにすることはできますか?
  2. anyを使用せずに同じ効果を達成できますか?たぶん、いくつかのテンプレートクラス、何かがするだろう。

答えて

3

テンプレートは、確かに、のようなものanyの使用を削除することができます。使い方は

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

ワウ。ありがとう、これは美しい解決策です。 – StargazingTux

1

ので配管はに関するデータを格納ではない流れであるだろう

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

を。

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

Live example

これは、あなたがステートレスパイプチェーンを設定でき、タイプは(gen_pipe<In,Out>を使用して)、それを消去し、データの1つのチャンクがゼロまたは多くなっています(あなたが終端記号を追加した場合)、バッファや文字列に取り組むなど

Tのシンクは、TのソースこれはUのTからパイプがある以上1

を生成するためにそれらを可能にするTのシンクを消費する機能であるT.

を消費する機能であります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); 
    }); 
}); 

これはパイプです。それは範囲のソースと要素のシンクを取り、それらを接続します。

With more examples

+0

これは感謝の意を表します。おかげさまで – StargazingTux

+0

@StargazingTux固定されたライブのサンプルが追加されました。 – Yakk

関連する問題