2016-05-01 14 views
3

私は、C++、特にC++ 11の組み込み機能を最大限に活用しようとしている機械学習ライブラリを構築しています。私はTransformationsと呼ばれる入力の変更を行うさまざまなクラスを持っています。今度は、パイプラインを構築して、それらを連鎖していきたい(そして最終的には、クラスファイヤーや回帰機のような機械学習アルゴリズムをチェーンの最後に持つ)。左辺値と右辺値を持つVariadicテンプレートクラスコンストラクタ

バリデーションテンプレートパラメータを持つクラスは、このユースケースには完全にマッチしていると思います。そのことは、コンストラクタでrvalueとlvaluesの両方を受け入れることです。

右値の場合は移動したいが、左辺値の場合は参照を保持したい(ただしこれは100%確かではないいくつかのスコープにバインドされ、関数の結果としてパイプラインを返すことは爆発するだろうが、このライブラリのpurporsesのためにこれはちょうど文書化することができる)。

これは、クラスのようになります。

template <class... Ts> 
class Pipeline { 
}; 

template <class T, class... Ts> 
class Pipeline<T, Ts...> { 
public: 
    Pipeline(T?? transformation, Ts ??... following) : Pipeline<Ts...>(following...), _transformation(???) {} 
... 
} 

_transformationは、初期化リストでstd::moveにするかどうか、どのようなコンストラクタでタイプTTsする必要があり、参照するかどうかはわかりません。

編集:左辺値の場合、パイプラインは変換を変更できるため、左辺値の場合は非constにする必要があります。

+1

典型的な標準ライブラリアプローチはすべてをコピーすることです。参照セマンティクスを必要とするユーザーは 'reference_wrapper'を使用できます。 –

答えて

2

が(であることに注意あなたが何ができるかの例です。 Tのコードは変換であり、パイプラインはSです)。

#include<tuple> 
#include<iostream> 

struct T { 
    T(int i): v{i} { } 
    T(const T &t) { v = t.v; std::cout << "cpy ctor" <<std::endl; } 
    T(T &&t) { v = t.v; std::cout << "move ctor" <<std::endl; } 
    void operator()(int i) { std::cout << "operator(): " << (v+i) << std::endl; } 
    int v; 
}; 

template<typename... T> 
struct S { 
    static constexpr std::size_t N = sizeof...(T); 

    template<typename... U> 
    S(U&&... args): tup{std::forward<U>(args)...} { } 

    void operator()(int i) { 
     unpack(i, std::make_index_sequence<N>{}); 
    } 

private: 
    template<std::size_t... I> 
    void unpack(int i, std::index_sequence<I...>) { 
     exec(i, std::get<I>(tup)...); 
    } 

    template<typename U, typename... O> 
    void exec(int i, U &&u, O&&... o) { 
     u(i); 
     exec(i, o...); 
    } 

    void exec(int) { } 

    std::tuple<T...> tup; 
}; 

int main() { 
    T t{40}; 
    S<T, T> s{t, T{0}}; 
    s(2); 
} 

基本的な考え方は、転送参照を使用することです。これはコンストラクタに独自のパラメータパックを与えることによってのみ可能です。

上記の例では、rvalue参照は移動され、lvalue参照はコピーされます。さもなければ、呼び出し元は参照されたオブジェクトの存続期間を担当していて、それはかなりエラーを起こしやすいでしょう。コメントで述べたように、必要に応じてstd::refを提出することができます。
とにかく実際のタイプとその値があるので、コンストラクタのポリシーを変更することができます。

継承を避けるために、私はtupleを使用して、後で使用するためにトランスフォームをパックしました。 operator()が呼び出されると、その参照が取り出されます。
Sのコンストラクタをsfinaeのビットで拡張して、パラメータパック(TU)が同じであることを確認します。そのためには、std::is_sameという一般化されたバージョンを使用することができます(必要に応じて実装可能な場合はhereを参照してください)。
明らかに、この例は最小限です。実際のコードでは複数の変換を使用できますが、タイプS<T, T>からタイプS<T1, T2, TAndSoOn>に切り替える必要があります。

上記の例を実行すると分かりますが、コピーと移動のコンストラクタは、Sを構築するときに正しく呼び出されます。 operator()はタプルをアンパックして参照を扱うので、この場合は余分なコピーはありません。

+0

基本的にタプルから多くのアイデアを取り入れたので、これはうれしいことです。継承でそれを行う利点の1つは、再帰を使用し、アキュムレータ変数を使用しないようにすることです。たとえば、次のようにします。 'fit_transform(const Matrix&input){return Pipeline :: fit_transform(_transformation.fit_transform(input)); } '、 ここで、fit_transformはフィッティングによっていくつかの学習を実行し、入力データを変換し、それをパイプラインの次の変換に渡します。これはタプルで可能ですか? –

+0

また、私が間違っていない場合、呼び出し元が 'std :: reference_wraper'を渡した場合、左辺値を取って参照する唯一の方法はありますか?この機能を発信者に透過的に与える方法はありませんか?ありがとう! –

+0

@FedericoAllocatiあなたの要件をサポートするために、いくつかの構造体ですべてを行うことができます。型の再帰または参照の格納のいずれかです。 – skypjack

1

私はこれが

#include "iostream" 
#include "string" 

template <class... Ts> 
class Pipeline { 
}; 

template <class T, class... Ts> 
class Pipeline<T&&, Ts...>: Pipeline<Ts...> { 
    T _transformation; 
public: 
    Pipeline(T&& transformation, Ts... following) : Pipeline<Ts...>(std::forward<Ts>(following)...), _transformation(std::move(transformation)) { 
     std::cout << "rvalue " << _transformation << " " << transformation << std::endl; 
    } 
}; 

template <class T, class... Ts> 
class Pipeline<T&, Ts...>: Pipeline<Ts...> { 
    T& _transformation; 
public: 
    Pipeline(T& transformation, Ts... following) : Pipeline<Ts...>(std::forward<Ts>(following)...), _transformation(transformation) { 
     std::cout << "lvalue " << _transformation << " " << transformation << std::endl; 
    } 
}; 

int main() { 
    std::string param1 = "param1"; 
    std::string param2 = "param2"; 
    std::string param3 = "param3"; 
    Pipeline<std::string&, std::string&&> p(param1, param2 + param3); 
} 

をあなたの要件を満たしている場合、それは出力わからない:

ここ
rvalue param2param3 
lvalue param1 param1 

Live Demo

+0

ええ、これは何かですが、(パイプラインで与えられた残りの機能のために)コードがほとんど重複しないようにホッピングしていました。私が別の答えを得なければ、私はあなたの印をつけるでしょう! –

0

あなたはこの線に沿って何か行うことができます:

template <class... Ts> 
class Pipeline { 
}; 

template <class T, class... Ts> 
class Pipeline<T, Ts...> { 
public: 
    template<class U, class... Us> 
    Pipeline(U&& transformation, Us&&... following) : Pipeline<Ts...>(std::forward<Us>(following)...), _transformation(std::forward<U>(transformation)) {} 
private: 
    TransformationWrapper<T> _transformation; 
} 

template<class T> 
class TransformationWrapper { 
public: 
    TransformationWrapper(T& t) : _reference(t) {} 
    TransformationWrapper(T&& t) : _ptr(new T(std::move(t))) {} 
    ~TransformationWrapper() { delete _ptr; } 

    T& get() { if (_ptr==nullptr) return _reference.get() else return *_ptr; } 

private: 
    std::reference_wrapper<T> _reference; 
    T* _ptr=nullptr; 
} 

をしかし、それは変換にすべてのget()のためにあなたのブランチの費用がかかります。

関連する問題