Использование шаблонов Variadi c для передачи произвольного числа переменных в функцию - PullRequest
1 голос
/ 06 марта 2020

У меня есть шаблонный класс, который в основном позволяет использовать от 0 до 3 различных типов. Эти типы используются в конструкторе для получения значений, которые впоследствии передаются другому конструктору. В настоящее время это выглядит следующим образом (обратите внимание, код сокращен до 2-х параметров и удален ненужный код):

template<class EditorDialog, typename FirstOpt, typename SecondOpt>
class GenericItemDelegateBase {
public:
    GenericItemDelegateBase( const FirstOpt &first, const SecondOpt &second ) : first( first ), second( second ) {}
protected:
    const FirstOpt first;
    const SecondOpt second;
private:
    EditorDialog *createEditor() const {
        return new EditorDialog( first, second );
    }
};

template<class EditorDialog, typename FirstOpt>
class GenericItemDelegateBase<EditorDialog, FirstOpt, void> {
public:
    GenericItemDelegateBase( const FirstOpt &first ) : first( first ) {}
protected:
    const FirstOpt first;
private:
    EditorDialog *createEditor() const {
        return new EditorDialog( first );
    }
};

template<class EditorDialog>
class GenericItemDelegateBase<EditorDialog, void, void> {
public:
    GenericItemDelegateBase() {}
private:
    EditorDialog *createEditor() const {
        return new EditorDialog();
    }
};

template<class EditorDialog, typename FirstOpt = void, typename SecondOpt = void>
class GenericItemDelegate : public GenericItemDelegateBase<EditorDialog, FirstOpt, SecondOpt> {
public:
    using Base = GenericItemDelegateBase<EditorDialog, FirstOpt, SecondOpt>;
    using Base::Base;
};

Как видите, в коде много дублирования. Мне удалось удалить некоторые из дубликатов, используя наследование и версию Base, в которой есть часть дубликата кода (например, удалить).

Я думал о том, чтобы попытаться использовать variadi c шаблонов, чтобы разрешить произвольное количество параметров (я выбрал 3 для этого, который работал до сих пор, но возможно в будущем нам может понадобиться больше). Я начал реализовывать это так:

template<class EditorDialog>
class GenericItemDelegate {
    GenericItemDelegate() {}
};

template<class EditorDialog, typename type, typename... args>
class GenericItemDelegate : public GenericItemDelegate<EditorDialog, type, args...> {
    using Base = GenericItemDelegate<EditorDialog, type, args...>;
    GenericItemDelegate( const type &var, args &&... rest ) : Base( std::forward<args>( rest )... ), var( var ) {}
protected:
    const type var;
};

Проблема, с которой я столкнулся, заключается в том, что в этой версии я понятия не имею, как реализовать функцию createEditor. Он должен передать все переменные в конструктор EditorDialog, что я не уверен, как это сделать (или если это вообще возможно).

QWidget *createEditor() const {
    return new EditorDialog( [all vars...] );
}

Я не знаю, что это возможно с Структура наследования, которую я настроил, поскольку, в зависимости от используемого EditorDialog, он может иметь конструктор, который принимает три параметра, но не тот, который принимает только два.

Я полностью иду по неверному пути? Или это возможно сделать?

Примеры использования:

int main() {
    auto a = GenericItemDelegate<int>();
    auto b = GenericItemDelegate<int, int>( 3 );
    auto c = GenericItemDelegate<std::vector<int>, int>( 3 );
    auto d = GenericItemDelegate<std::vector<int>, int, std::allocator<int>>( 3, std::allocator<int>() );
}

Ответы [ 3 ]

2 голосов
/ 06 марта 2020

Вы можете использовать std :: tuple для хранения пакета Оптов, затем передать std::index_sequence, чтобы можно было получить их с помощью std::get.

Что-то вроде этого

template<class...>
class GenericItemDelegateBase_impl;

template<class EditorDialog, std::size_t... Is, class... Opts>
class GenericItemDelegateBase_impl<EditorDialog, std::index_sequence<Is...>, Opts...> : public QItemDelegate {
public:
    GenericItemDelegateBase_impl( QSqlDatabase &connection, QObject *parent, Opts... opts ) : QItemDelegate( parent ), connection( connection ), m_opts(std::move(opts)...) {}
protected:
    QSqlDatabase connection;
    std::tuple<Opts...> m_opts;
private:
    QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const override {
        return new EditorDialog( connection, parent, std::get<Is>(m_opts)...);
    }
};

template <class EditorDialog, class... Opts>
using GenericItemDelegateBase = GenericItemDelegateBase_impl<EditorDialog, std::make_index_sequence<sizeof...(Opts)>, Opts...>;

Так как здесь много типов Qt, я не пытался скомпилировать это, но часть из некоторых возможных опечаток или мелких ошибок должна быть в порядке.

Редактировать Как рекомендуется в комментариях, используя std::apply и лямбду, мы можем еще больше упростить код. Для использования c++14 лямбда-выражения (автоматические параметры) требуется *1013*.

#include <tuple>

template<class EditorDialog, class... Opts>
class GenericItemDelegateBase : public QItemDelegate {
public:
    GenericItemDelegateBase( QSqlDatabase &connection, QObject *parent, Opts... opts ) : QItemDelegate( parent ), connection( connection ), m_opts(std::move(opts)...) {}
protected:
    QSqlDatabase connection;
    std::tuple<Opts...> m_opts;
private:
    QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &, const QModelIndex & ) const override {
        return std::apply([&](auto&&... opts) { return new EditorDialog(connection, parent, opts...); }, m_opts);
    }
};
1 голос
/ 06 марта 2020

Как насчет другого интерфейса и типа стирания?

template<class EditorDialog>
class GenericItemDelegate
{
    std::function<std::unique_ptr<EditorDialog>()> mFactory;
public:
    template <typename ... Ts>
    GenericItemDelegateBase(Ts... args) :
        mFactory([=](){ return std::make_unique<EditorDialog>(args...); })
    {}
//private:
    EditorDialog* createEditor() const { return mFactory().release(); }
};

А затем использовать его как:

GenericItemDelegate<int> a;
GenericItemDelegate<int> b(3);
GenericItemDelegate<std::vector<int>> c(3);
GenericItemDelegate<std::vector<int>> d(3, std::allocator<int>());
1 голос
/ 06 марта 2020

Вы можете сохранить свои аргументы в std::tuple и затем передать их в EditorDialog, например:

template<class EditorDialog, typename ... Args>
class GenericItemDelegateBase {
public:
    GenericItemDelegateBase(Args&&... args)
        : args(std::forward<Args>(args)...)
    {}

protected:
    std::tuple<Args...> args;

private:
    EditorDialog createEditor() const {
        return std::make_from_tuple<EditorDialog>(args);
    }
};

И тогда будет работать следующее:

auto a = GenericItemDelegateBase<int>();
auto b = GenericItemDelegateBase<int, int>( 3 );
auto c = GenericItemDelegateBase<std::vector<int>, int>( 3 );
auto d = GenericItemDelegateBase<std::vector<int>, int, std::allocator<int>>( 3, std::allocator<int>() );

Обратите внимание, что для использования std::make_from_tuple требуется C ++ 17 .

ОБНОВЛЕНИЕ

Поскольку для QT требуется createEditor функция для возврата указатель , как предложено @ MSalters в комментариях ниже, вы можете иметь:

EditorDialog *createEditor() const {
    return new EditorDialog{ std::make_from_tuple<EditorDialog>(args) };
}
...