2016-05-01 3 views
3

Я строю библиотеку обучения компьютера, пытаясь получить максимальную отдачу от встроенных функций C++, особенно C++ 11. У меня есть множество классов, которые выполняют модификацию ввода, называемого Transformations. Теперь я хочу построить конвейер из них, связав их один за другим (и, в конечном итоге, имея в конце цепи алгоритм машинного обучения, например, классификатор или регресс).Конструктор класса шаблонов Variadic с lvalues ​​и rvalues ​​

Я думаю, что класс с параметрами вариационного шаблона идеально подходит для этого варианта использования. Дело в том, что я хочу принимать как rvalues, так и lvalues ​​в конструкторе.

В случае rvalue я хочу переместить его, а в случае lvalue я хочу сохранить ссылку на него (хотя я все еще не уверен на 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 в списке инициализации и что должно быть типами T и Ts в конструкторе ,

Редактировать: В случае с lvalue он должен быть не 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, чтобы проверить, что пакет параметров (T и U) - это то же самое. Для этого вы можете использовать обобщенную версию 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

Кроме того, если я не ошибался, единственный способ, которым я могу взять lvalue и иметь ссылку на него, - это если вызывающий абонент передает '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() на преобразование.

Смежные вопросы