【问题标题】:Variadic template class constructor with lvalues and rvalues具有左值和右值的可变模板类构造函数
【发布时间】:2016-05-01 05:16:54
【问题描述】:

我正在构建一个机器学习库,试图充分利用 C++ 的内置特性,尤其是 C++11。我有多种类可以修改输入,称为Transformations。现在我想构建它们的管道,将它们一个接一个地链接起来(最终在链的末端有一个机器学习算法,如分类器或回归器)。

我认为具有可变模板参数的类是这个用例的完美匹配。问题是我想在构造函数中同时接受右值和左值。

在右值的情况下我想移动它,在左值的情况下我想保留对它的引用(尽管我仍然不能 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的类型应该是什么。

编辑:在左值的情况下,它应该是非常量,因为管道可以修改转换。

【问题讨论】:

  • 典型的标准库方法是复制所有内容。需要引用语义的用户可以使用reference_wrappers。

标签: c++ c++11 variadic-templates rvalue-reference lvalue


【解决方案1】:

这是您可以做什么的示例(请注意,在下面的代码中,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);
}

基本思想是使用转发引用,这只能通过给构造函数自己的参数包来实现。

在上面的例子中,右值引用被移动,左值引用被复制。否则调用者将负责被引用对象的生命周期,并且很容易出错。如 cmets 中所述,如果需要,可以提交 std::ref
无论如何,您可以在构造函数中更改策略,因为那里有实际的类型和它们的值。

为了避免继承,我使用了tuple 来打包转换以供以后使用。因此,每当调用 operator() 时,它们的引用就会被删除。
我会扩展S 的构造函数,并使用一些sfinae 来检查参数包(TU)是否相同。为此,您可以使用std::is_same 的通用版本(如果需要,请参阅here 了解可能的实现)。
显然,这个例子是一个最小的例子。您可以在实际代码中使用多个转换,只需从类型S&lt;T, T&gt; 切换到类型S&lt;T1, T2, TAndSoOn&gt;

正如您通过执行上面的示例所看到的,当您构造S 时,复制和移动构造函数被正确调用。 operator() 解包元组并使用引用,因此在这种情况下您没有额外的副本。

【讨论】:

  • 这很好,因为我基本上从元组中获取了很多想法。使用继承的一个好处是我可以使用递归并避免使用累加器变量,例如:fit_transform(const Matrix&amp; input) { return Pipeline&lt;Ts...&gt;::fit_transform(_transformation.fit_transform(input)); },其中 fit_transform 执行一些学习(通过拟合),然后转换输入数据并将其传递给管道。元组可以做到这一点吗?
  • 另外,如果我没听错的话,我可以获取左值并引用它的唯一方法是调用者传递std::reference_wraper?有没有办法将这个功能透明地提供给调用者?谢谢!
  • @FedericoAllocati 您可以使用一些结构来支持您的需求,无论是递归类型还是存储引用。
【解决方案2】:

我不确定这是否符合您的要求

#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

【讨论】:

  • 是的,这是一些东西,但我希望没有几乎重复的代码(因为管道提供的其余功能)。如果我没有得到另一个答案,我会标记你的!
【解决方案3】:

你可以这样做:

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() 都会花费您一个分支。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-26
    • 1970-01-01
    • 2018-05-18
    • 2012-05-09
    • 1970-01-01
    相关资源
    最近更新 更多