【问题标题】:How to store variadic template arguments?如何存储可变参数模板参数?
【发布时间】:2013-05-27 21:57:46
【问题描述】:

是否可以以某种方式存储参数包以供以后使用?

template <typename... T>
class Action {
private:        
    std::function<void(T...)> f;
    T... args;  // <--- something like this
public:
    Action(std::function<void(T...)> f, T... args) : f(f), args(args) {}
    void act(){
        f(args);  // <--- such that this will be possible
    }
}

后来:

void main(){
    Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);

    //...

    add.act();
}

【问题讨论】:

标签: c++ c++11 variadic-templates


【解决方案1】:

要在此处完成您想做的事情,您必须将模板参数存储在一个元组中:

std::tuple<Ts...> args;

此外,您必须稍微更改构造函数。特别是,使用 std::make_tuple 初始化 args 并允许在参数列表中使用通用引用:

template <typename F, typename... Args>
Action(F&& func, Args&&... args)
    : f(std::forward<F>(func)),
      args(std::forward<Args>(args)...)
{}

此外,您必须像这样设置一个序列生成器:

namespace helper
{
    template <int... Is>
    struct index {};

    template <int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> {};

    template <int... Is>
    struct gen_seq<0, Is...> : index<Is...> {};
}

您可以使用这样的生成器来实现您的方法:

template <typename... Args, int... Is>
void func(std::tuple<Args...>& tup, helper::index<Is...>)
{
    f(std::get<Is>(tup)...);
}

template <typename... Args>
void func(std::tuple<Args...>& tup)
{
    func(tup, helper::gen_seq<sizeof...(Args)>{});
}

void act()
{
   func(args);
}

那就是它!所以现在你的类应该是这样的:

template <typename... Ts>
class Action
{
private:
    std::function<void (Ts...)> f;
    std::tuple<Ts...> args;
public:
    template <typename F, typename... Args>
    Action(F&& func, Args&&... args)
        : f(std::forward<F>(func)),
          args(std::forward<Args>(args)...)
    {}

    template <typename... Args, int... Is>
    void func(std::tuple<Args...>& tup, helper::index<Is...>)
    {
        f(std::get<Is>(tup)...);
    }

    template <typename... Args>
    void func(std::tuple<Args...>& tup)
    {
        func(tup, helper::gen_seq<sizeof...(Args)>{});
    }

    void act()
    {
        func(args);
    }
};

Here is your full program on Coliru.


更新:这是一个不需要指定模板参数的辅助方法:

template <typename F, typename... Args>
Action<Args...> make_action(F&& f, Args&&... args)
{
    return Action<Args...>(std::forward<F>(f), std::forward<Args>(args)...);
}

int main()
{
    auto add = make_action([] (int a, int b) { std::cout << a + b; }, 2, 3);

    add.act();
}

And again, here is another demo.

【讨论】:

  • 你能在你的回答中稍微扩展一下吗?
  • 由于 Ts... 是类模板参数,而不是函数模板参数,因此 Ts&&... 没有定义通用引用包,因为参数包没有发生类型推导。 @jogojapan 显示了确保您可以将通用引用传递给构造函数的正确方法。
  • 注意引用和对象生命周期! void print(const std::string&amp;); std::string hello(); auto act = make_action(print, hello()); 不好。我更喜欢std::bind 的行为,它会复制每个参数,除非你用std::refstd::cref 禁用它。
  • 我认为@jogojapan 有一种更简洁易读的解决方案。
  • @Riddick 将args(std::make_tuple(std::forward&lt;Args&gt;(args)...)) 更改为args(std::forward&lt;Args&gt;(args)...)。顺便说一句,我很久以前写过这段代码,我不会使用这段代码来将函数绑定到某些参数。我现在只会使用std::invoke()std::apply()
【解决方案2】:

您可以为此使用std::bind(f,args...)。它将生成一个可移动且可能可复制的对象,该对象存储函数对象和每个参数的副本以供以后使用:

#include <iostream>
#include <utility>
#include <functional>

template <typename... T>
class Action {
public:

  using bind_type = decltype(std::bind(std::declval<std::function<void(T...)>>(),std::declval<T>()...));

  template <typename... ConstrT>
  Action(std::function<void(T...)> f, ConstrT&&... args)
    : bind_(f,std::forward<ConstrT>(args)...)
  { }

  void act()
  { bind_(); }

private:
  bind_type bind_;
};

int main()
{
  Action<int,int> add([](int x, int y)
                      { std::cout << (x+y) << std::endl; },
                      3, 4);

  add.act();
  return 0;
}

注意std::bind 是一个函数,您需要将调用它的结果作为数据成员存储。该结果的数据类型不容易预测(标准甚至没有精确指定),所以我在编译时使用decltypestd::declval 的组合来计算该数据类型。见上文Action::bind_type的定义。

还要注意我是如何在模板化构造函数中使用通用引用的。这确保您可以传递与类模板参数 T... 不完全匹配的参数(例如,您可以使用对某些 T 的右值引用,并且您将按原样将它们转发到 bind 调用。)

最后说明:如果您想将参数存储为引用(以便您传递的函数可以修改,而不仅仅是使用它们),您需要使用std::ref 将它们包装在引用对象中。仅传递 T &amp; 将创建值的副本,而不是引用。

Operational code on Coliru

【讨论】:

  • 绑定右值不是很危险吗?当add 被定义在与act() 不同的范围内时,这些不会失效吗?构造函数不应该得到ConstrT&amp;... args而不是ConstrT&amp;&amp;... args吗?
  • @Angelorf 抱歉我的回复晚了。您的意思是调用bind() 中的右值?由于bind() 保证会复制(或移动到新创建的对象中),我认为不会有问题。
  • @jogojapan 快速注意,MSVC17 要求构造函数中的函数也被转发到 bind_ (即 bind_(std::forward<:function>>( f),std::forward(args)...) )
  • 在初始化程序中,bind_(f, std::forward&lt;ConstrT&gt;(args)...) 根据标准是未定义的行为,因为该构造函数是实现定义的。不过,bind_type 被指定为可复制和/或可移动构造,因此 bind_{std::bind(f, std::forward&lt;ConstrT&gt;(args)...)} 应该仍然有效。
【解决方案3】:

这个问题来自 C++11 天。但是对于那些现在在搜索结果中找到它的人来说,一些更新:

std::tuple 成员通常仍然是存储参数的直接方式。 (如果您只想调用特定函数,则类似于@jogojapan'sstd::bind 解决方案也可以工作,但如果您想以其他方式访问参数,或者将参数传递给多个函数等,则不行。)

在 C++14 及更高版本中,std::make_index_sequence&lt;N&gt; or std::index_sequence_for&lt;Pack...&gt; 可以替换在0x499602D2's solution 中看到的helper::gen_seq&lt;N&gt; 工具:

#include <utility>

template <typename... Ts>
class Action
{
    // ...
    template <typename... Args, std::size_t... Is>
    void func(std::tuple<Args...>& tup, std::index_sequence<Is...>)
    {
        f(std::get<Is>(tup)...);
    }

    template <typename... Args>
    void func(std::tuple<Args...>& tup)
    {
        func(tup, std::index_sequence_for<Args...>{});
    }
    // ...
};

在 C++17 及更高版本中,std::apply 可用于解包元组:

template <typename... Ts>
class Action
{
    // ...
    void act() {
        std::apply(f, args);
    }
};

这里是a full C++17 program,展示了简化的实现。我还更新了make_action 以避免tuple 中的引用类型,这对于右值参数总是不利的,而对于左值参数来说相当危险。

【讨论】:

    【解决方案4】:

    我认为你有一个 XY 问题。当您可以在调用点使用 lambda 时,为什么还要麻烦存储参数包?即,

    #include <functional>
    #include <iostream>
    
    typedef std::function<void()> Action;
    
    void callback(int n, const char* s) {
        std::cout << s << ": " << n << '\n';
    }
    
    int main() {
        Action a{[]{callback(13, "foo");}};
        a();
    }
    

    【讨论】:

    • 因为在我的应用程序中,一个动作实际上有 3 个不同的函子,它们都是相关的,我宁愿包含它的类包含 1 个动作,而不是 3 个 std::function
    猜你喜欢
    • 2019-03-29
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-22
    • 2018-07-27
    • 2021-11-29
    相关资源
    最近更新 更多