【问题标题】:Using Variadic Templates to pass arbitary number of variables to function使用可变参数模板将任意数量的变量传递给函数
【发布时间】:2020-03-06 14:01:54
【问题描述】:

我有一个模板类,它基本上允许使用 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 版本(在示例中删除)。

我的想法是尝试使用可变参数模板来允许任意数量的参数(我为此选择了 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>() );
}

【问题讨论】:

  • 如果你能把你的例子写成minimal reproducible example,那就太好了
  • @NutCracker,我添加了一个使用示例,但不确定 MRE 还需要什么。我可以缩短当前代码以使用不超过 2 个参数,但我认为 3 显示更多我正在处理的内容......编辑:缩短。
  • 我的意思是尝试发布一些更容易测试的代码......可能没有 QT 组件
  • @NutCracker,刚刚进行了编辑:P

标签: c++ templates variadic-templates


【解决方案1】:

您可以使用 std::tuple 存储 Opts 包,然后传入 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 类型,所以我没有尝试编译它,但是由于一些可能的拼写错误或较小的错误,应该没问题。

编辑 按照 cmets 中的建议,使用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::apply
  • @MSalters 您不能使用std::apply 调用构造函数。我猜您可以使用std::tuple_catm_opts 添加其他参数,但这看起来更干净IMO。距离std::apply不远,但也不一样。
  • 那就是std::make_from_tuple :)
  • @Quentin 你确定吗? std::make_from_tuple 按值返回,代码中有动态分配。如果您切换到智能指针,它可以工作,但我不认为在这里。 This 编译但似乎不起作用。
  • 哎呀,我猜不是......但我认为 MSalters 建议像std::apply([](auto &amp;&amp;... opts) { return new EditorDialog(connection, parent, opts...); }, m_opts) 这样的东西,不要直接调用构造函数
【解决方案2】:

不同的接口和类型擦除怎么样?

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

【讨论】:

  • 我可能对 Qt 的工作方式有误(这是为了),但我认为您的解决方案会导致一些错误;就像过早地创建对象,并给它错误的父对象。 createEditor 实际上接受另一个父级传递给EditorDialog 的构造函数。它当然适用于我削减到的 MRE,但我认为我削减了太多。
  • @ChrisMM:对象仅在createEditor 中。您拥有args 的副本来创建std::function(就像其他人为std::tuple 所做的那样)。 “createEditor 实际上接受另一个父级传递给构造函数”,您可以相应地更改工厂。
  • 我没有看到function 周围的unique_ptr,我的错。仍然需要稍微修改它以使用parent,但这很容易。还是 +1
【解决方案3】:

您可以将参数存储到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 函数返回 指针,正如下面 cmets 中的 @MSalters 所建议的,您可以:

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

【讨论】:

  • 当我尝试这个时,它抱怨我的构造函数没有接受正确的参数......
  • 是的,我在 Godbolt 上试过了,效果很好。在尝试使用我拥有的 Qt 类时,对于那些只有一个构造函数(父级)参数的类,它会失败。我将您的代码修改为始终包含QObject *parent。即return new EditorDialog( parent, args );,那就是它失败的时候
  • @ChrisMM 好的...无论如何,我很高兴能提供帮助
  • 是的。我认为这就是我使用它的方式,所以无论如何我都会为你 +1。不过,我使用了 super 的答案。
  • @NutCracker std::make_from_tuple 使用值语义,所以你不能进行动态分配并返回一个指针,这似乎是 OP 的意图。
猜你喜欢
  • 1970-01-01
  • 2014-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多