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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16868129/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:43:17  来源:igfitidea点击:

How to store variadic template arguments?

c++c++11variadic-templates

提问by Eric B

Is it possible to store a parameter pack somehow for a later use?

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

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

Then later on:

然后后来:

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

    //...

    add.act();
}

采纳答案by 0x499602D2

To accomplish what you want done here, you'll have to store your template arguments in a tuple:

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

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

Furthermore, you'll have to change up your constructor a bit. In particular, initializing argswith an std::make_tupleand also allowing universal references in your parameter list:

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

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

Moreover, you would have to set up a sequence generator much like this:

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

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...> {};
}

And you can implement your method in terms of one taking such a generator:

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

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

And that it! So now your class should look like this:

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

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.

这是您在 Coliru 上的完整程序。



Update: Here is a helper method by which specification of the template arguments aren't necessary:

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

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.

再一次,这是另一个演示。

回答by jogojapan

You can use std::bind(f,args...)for this. It will generate a movable and possibly copyable object that stores a copy of the function object and of each of the arguments for later use:

您可以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;
}

Notice that std::bindis a function and you need to store, as data member, the result of calling it. The data type of that result is not easy to predict (the Standard does not even specify it precisely), so I use a combination of decltypeand std::declvalto compute that data type at compile time. See the definition of Action::bind_typeabove.

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

Also notice how I used universal references in the templated constructor. This ensures that you can pass arguments that do not match the class template parameters T...exactly (e.g. you can use rvalue references to some of the Tand you will get them forwarded as-is to the bindcall.)

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

Final note: If you want to store arguments as references (so that the function you pass can modify, rather than merely use, them), you need to use std::refto wrap them in reference objects. Merely passing a T &will create a copy of the value, not a reference.

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

Operational code on Coliru

Coliru 上的操作代码

回答by Casey

I think you have an XY problem. Why go to all the trouble to store the parameter pack when you could just use a lambda at the callsite? i.e.,

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

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

回答by aschepler

This question was from C++11 days. But for those finding it in search results now, some updates:

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

A std::tuplemember is still the straightforward way to store arguments generally. (A std::bindsolution similar to @jogojapan'swill also work if you just want to call a specific function, but not if you want to access the arguments in other ways, or pass the arguments to more than one function, etc.)

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

In C++14 and later, std::make_index_sequence<N>or std::index_sequence_for<Pack...>can replace the helper::gen_seq<N>tool seen in 0x499602D2's solution:

在 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...>{});
    }
    // ...
};

In C++17 and later, std::applycan be used to take care of unpacking the tuple:

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

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

Here's a full C++17 programshowing the simplified implementation. I also updated make_actionto avoid reference types in the tuple, which was always bad for rvalue arguments and fairly risky for lvalue arguments.

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