如何存储可变参数模板参数?

Eri*_*c B 79 c++ variadic-templates c++11

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后是:

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

    //...

    add.act();
}
Run Code Online (Sandbox Code Playgroud)

0x4*_*2D2 61

要完成您想要完成的任务,您必须将模板参数存储在元组中:

std::tuple<Ts...> args;
Run Code Online (Sandbox Code Playgroud)

此外,您将不得不稍微更改构造函数.特别是,初始化argsstd::make_tuple,也允许您在参数列表中普遍引用:

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

而且,你必须像这样设置一个序列生成器:

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...> {};
}
Run Code Online (Sandbox Code Playgroud)

你可以用一个这样的生成器来实现你的方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
};
Run Code Online (Sandbox Code Playgroud)

这是关于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();
}
Run Code Online (Sandbox Code Playgroud)

再次,这是另一个演示.

  • 注意参考和对象的生命周期!`void print(const std :: string&); std :: string hello(); auto act = make_action(print,hello());`不好.我更喜欢`std :: bind`的行为,除非你用`std :: ref`或`std :: cref`禁用它,否则它会复制每个参数. (3认同)
  • 由于 Ts... 是类模板参数,而不是函数模板参数,因此 Ts&amp;&amp;... 没有定义通用引用包,因为参数包没有发生类型推导。@jogojapan 显示了确保您可以将通用引用传递给构造函数的正确方法。 (2认同)

jog*_*pan 22

你可以用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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

最后注意事项:如果要将参数存储为引用(以便您传递的函数可以修改,而不是仅仅使用它们),则需要使用std::ref它们将它们包装在引用对象中.仅传递一个T &会创建一个值的副本,而不是一个引用.

Coliru上的操作代码


asc*_*ler 7

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

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

在 C++14 及更高版本中,std::make_index_sequence<N>或者std::index_sequence_for<Pack...>可以替换helper::gen_seq<N>0x499602D2 的解决方案中看到的工具:

#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...>{});
    }
    // ...
};
Run Code Online (Sandbox Code Playgroud)

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

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

这是一个完整的 C++17 程序,显示了简化的实现。我还更新make_action以避免 中的引用类型tuple,这对于右值参数总是不利的,而对于左值参数则相当危险。