Search code examples
c++templatesvariadic-templates

Using Variadic Templates to pass arbitary number of variables to function


I have a template class which basically allows zero to 3 different types to be used with it. These types are used in the constructor to get values which are later passed to another constructor. Currently, it looks like this (note, code shortened to use 2 params, and remove unneeded code):

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

As you can see, there's a lot of duplication in the code. I was able to remove some of the duplication by using inheritance, and having a Base version which has some of the duplicate code (remove in example).

My thought was to try to use variadic templates to allow an arbitrary number of parameters (I picked 3 for this, which has worked so far, but it's possible in the future we may need more). I started to implement this like so:

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

The problem I am having, is in this version, I have no idea how to implement the createEditor function. It must pass all the variables to the EditorDialog constructor, which I am not sure how to do (or if this is even possible).

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

I don't know that this is possible with the inheritance structure I have setup, since, depending on the EditorDialog being used, it may have a constructor which takes in the three parameters, but not one that takes in only two.

Am I going down the wrong track entirely? Or is it possible to make this work?

Usage examples:

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

Solution

  • You can use a std::tuple to store the pack of Opts, then pass in a std::index_sequence to make it possible to retrieve them with std::get.

    Something like so

    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...>;
    

    Since there are a lot of Qt types in here I havn't tried to compile this, but a part from some possible typos or smaller mistakes it should be ok.

    Edit As recommended in the comments, using std::apply and a lambda we can simplify the code even further. This requires c++14 for the use of generic lambdas (auto parameters).

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