如何存储变量模板参数?

问题描述 投票:84回答:4

能否以某种方式存储参数包,以便以后使用?

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
4个回答
65
投票

为了完成你想做的事情,你必须将你的模板参数存储在一个元组中。

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

此外,你还必须改变一下你的构造函数。特别是,初始化 args 附带 std::make_tuple 并且还允许在你的参数列表中使用通用引用。

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

这是你在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();
}

再来,这里是另一个演示。


23
投票

你可以使用 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... (例如,你可以使用rvalue引用到一些的 T 你会把它们原封不动地转发到。bind 调用)。)

最后需要注意的是:如果你想把参数存储为引用(这样你传递的函数就可以修改,而不仅仅是使用它们),你需要使用 std::ref 来将它们包裹在引用对象中。仅仅通过一个 T & 将创建一个值的副本,而不是一个引用。

Coliru上的操作代码


3
投票

我认为你有一个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();
}

2
投票

这个问题是C++11时代的问题。但对于现在在搜索结果中找到它的人,有些更新。

A std::tuple 成员仍然是一般存储参数的直接方式。(A 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...>{});
    }
    // ...
};

在C++17及以后的版本中。std::apply 可以用来处理元组的解包。

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

这里是 完整的C++17程序 显示了简化的实现。我还更新了 make_action 中的引用类型,以避免在 tuple,这对于rvalue参数来说总是不好的,而对于lvalue参数来说则是相当危险的。

© www.soinside.com 2019 - 2024. All rights reserved.