2016-05-01 95 views
3

我正在構建一個機器學習庫,試圖從C++的內置功能中獲得最大收益,特別是C++ 11。我有各種各樣的類執行輸入修改,稱爲Transformations。現在我想要構建它們的一個管道,將它們一個接一個地鏈接起來(並且最終在鏈的最後有一個機器學習算法,如分類器或迴歸器)。左值和右值的可變參數模板類構造函數

我認爲一個具有可變參數模板參數的類是這個用例的完美匹配。關鍵是我想在構造函數中接受rvalue和lvalues。

在rvalue的情況下,我想移動它,並在左值的情況下,我想保留對它的引用(儘管我仍然不是100%確定這一點,因爲它可能是一個引用綁定到某個作用域,並且作爲函數結果返回管道將炸燬;但是對於這個庫的聲明,這可以被記錄)。

這將是類:

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

其基本思想是使用轉發引用,這是可能的,只有給予構造函數自己的參數包。

在上面的例子中,移動右值引用並複製左值引用。否則,調用者將負責所引用對象的生命週期,並且非常容易出錯。如評論中所述,如果需要,可以提交std::ref
無論如何,您可以在構造函數中更改策略,因爲您有實際的類型及其值。

爲避免繼承,我使用了tuple來打包轉換以備後用。無論何時調用operator(),都會得到它們的引用。
我想擴展構造函數S與一點sfinae檢查參數包(TU)是相同的。爲此,您可以使用通用版本std::is_same(如果需要,請參見here以獲取可能的實現)。
顯然這個例子是最小的一個。您可以在實際代碼中使用多個轉換,這是從S<T, T>類型切換到S<T1, T2, TAndSoOn>類型的問題。

正如你通過執行上面的例子可以看到的,當你構造S時,複製和移動構造函數會被正確地調用。 operator()解開元組並且與引用一起工作,所以在這種情況下你沒有額外的副本。

+0

這很好,因爲我基本上從元組中獲得了很多想法。使用繼承的一個好處是,我可以使用遞歸併避免使用累加器變量,例如:'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()上進行改造。

相關問題