使用 Variadic 模板将任意数量的变量传递给函数
Using Variadic Templates to pass arbitary number of variables to function
我有一个模板 class,它基本上允许使用零到三种不同的类型。这些类型在构造函数中用于获取稍后传递给另一个构造函数的值。目前,它看起来像这样(注意,代码缩短为使用 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
版本(例如删除)。
我的想法是尝试使用可变参数模板来允许任意数量的参数(我为此选择了 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>() );
}
您可以使用 std::tuple 来存储 Opt 包,然后传入 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
和 lambda 我们可以进一步简化代码。这需要 c++14
才能使用通用 lambda(自动参数)。
#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);
}
};
您可以将参数存储到 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
函数到 return 指针 ,正如下面评论中 @MSalters 所建议的,您可以:
EditorDialog *createEditor() const {
return new EditorDialog{ std::make_from_tuple<EditorDialog>(args) };
}
不同接口和类型擦除如何?
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>());
我有一个模板 class,它基本上允许使用零到三种不同的类型。这些类型在构造函数中用于获取稍后传递给另一个构造函数的值。目前,它看起来像这样(注意,代码缩短为使用 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
版本(例如删除)。
我的想法是尝试使用可变参数模板来允许任意数量的参数(我为此选择了 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>() );
}
您可以使用 std::tuple 来存储 Opt 包,然后传入 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
和 lambda 我们可以进一步简化代码。这需要 c++14
才能使用通用 lambda(自动参数)。
#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);
}
};
您可以将参数存储到 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
函数到 return 指针 ,正如下面评论中 @MSalters 所建议的,您可以:
EditorDialog *createEditor() const {
return new EditorDialog{ std::make_from_tuple<EditorDialog>(args) };
}
不同接口和类型擦除如何?
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>());