98

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();
}
abyss.7
  • 13,882
  • 11
  • 56
  • 100
Eric B
  • 4,367
  • 5
  • 33
  • 43
  • 4
    Yes; store a tuple and use some sort of index pack to make the call. – Kerrek SB Jun 01 '13 at 01:21
  • Does this answer your question? [C++ How to store a parameter pack as a variable](https://stackoverflow.com/questions/15537817/c-how-to-store-a-parameter-pack-as-a-variable) – user202729 Jan 05 '20 at 07:30

4 Answers4

70

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 args with an std::make_tuple and also allowing universal references in your parameter list:

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.


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.

David G
  • 94,763
  • 41
  • 167
  • 253
  • how would you call f(args) using a tuple? Can you expand the tuple back into the parameter pack? – Eric B Jun 01 '13 at 01:24
  • 1
    could you expand on that a little in your answer? – Eric B Jun 01 '13 at 01:27
  • Well, I do want to store them for later; I'll edit my question for clarity. – Eric B Jun 01 '13 at 01:32
  • @EricB Okay, now that I've seen your update I know what to do. I will revise my question in a minute. – David G Jun 01 '13 at 01:41
  • @EricB Ok, the update is up and I included a working version of your program. – David G Jun 01 '13 at 01:59
  • @EricB Are you still having any problems? – David G Jun 01 '13 at 02:02
  • Can you store different instantiations of such in the same container, when the number of how many objects of each instantiation exists is determined at runtime? I can only think of superclassing or void*. – user1358 Jun 03 '13 at 08:26
  • Sure you can. [`std::vector`](http://en.cppreference.com/w/cpp/container/vector) is a runtime container for which you can specify the size. – David G Jun 03 '13 at 12:05
  • Too much overloading of the name `func`. – Ben Voigt Jul 29 '13 at 15:06
  • 1
    Since Ts... is a class template parameter, not a function template parameter, Ts&&... does not define a pack of universal references as there is no type deduction occurring for the parameter pack. @jogojapan shows the correct way to ensure you can pass universal references to the constructor. – masrtis Jul 29 '13 at 16:51
  • 3
    Watch out for references and object lifetimes! `void print(const std::string&); std::string hello(); auto act = make_action(print, hello());` is no good. I'd prefer the behavior of `std::bind`, which makes a copy of each argument unless you disable that with `std::ref` or `std::cref`. – aschepler Feb 16 '14 at 22:37
  • 1
    I think @jogojapan has a way more concise and readable solution. – Tim Kuipers Oct 26 '16 at 11:22
  • Why doesn't this work for named (lvalue) arguments? It complains that it can't bind to rvalue reference, but I thought `Args&&...` is a universal reference. – Riddick May 28 '19 at 10:35
  • @Riddick Can you show me an example where this happens? – David G May 29 '19 at 17:19
  • I followed the link to your code, created a variable `int n = 2` in the main file and substituted that instead (of rvalue `2`). It failed to compile in both cases (examples provided). – Riddick May 29 '19 at 19:51
  • 1
    @Riddick Change `args(std::make_tuple(std::forward(args)...))` to `args(std::forward(args)...)`. BTW I wrote this a long time ago and I wouldn't use this code for the purpose of binding a function to some arguments. I would just use [`std::invoke()`](https://en.cppreference.com/w/cpp/utility/functional/invoke) or [`std::apply()`](https://en.cppreference.com/w/cpp/utility/apply) nowadays. – David G May 29 '19 at 20:49
24

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:

#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::bind is 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 decltype and std::declval to compute that data type at compile time. See the definition of Action::bind_type above.

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 T and you will get them forwarded as-is to the bind call.)

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::ref to wrap them in reference objects. Merely passing a T & will create a copy of the value, not a reference.

Operational code on Coliru

jogojapan
  • 68,383
  • 11
  • 101
  • 131
  • Isn't it dangerous to bind rvalues? Wouldn't those get invalidated when `add` is defined in a different scope then where `act()` is called? Shouldn't the constructor get `ConstrT&... args` rather than `ConstrT&&... args`? – Tim Kuipers Oct 26 '16 at 14:43
  • 1
    @Angelorf Sorry for my late reply. You mean rvalues in the call to `bind()`? Since `bind()` is guaranteed to make copies (or to move into freshly created objects), I don't think there can be a problem. – jogojapan Oct 30 '16 at 13:37
  • @jogojapan Quick note, MSVC17 requires the function in constructor to be forwarded to bind_ as well ( i. e. bind_(std::forward>(f),std::forward(args)...) ) – Outshined Aug 08 '17 at 11:41
  • 1
    In the initializer, `bind_(f, std::forward(args)...)` is undefined behavior according to the standard, since that constructor is implementation-defined. `bind_type` is specified to be copy and/or move-constructible, though, so `bind_{std::bind(f, std::forward(args)...)}` should still work. – joshtch Oct 12 '18 at 22:43
11

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

A std::tuple member is still the straightforward way to store arguments generally. (A std::bind solution similar to @jogojapan's will 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.)

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:

#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::apply can be used to take care of unpacking the tuple:

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

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

aschepler
  • 70,891
  • 9
  • 107
  • 161
3

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.,

#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();
}
Casey
  • 41,449
  • 7
  • 95
  • 125
  • Because in my application, an Action actually has 3 different functors that are all related, and I'd rather classes that contain it contain 1 Action, and not 3 std::function – Eric B Jun 03 '13 at 15:27