2016-05-01 46 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()上进行改造。

相关问题