Home > database >  How does std::thread store variadic arguments passed through its constructor?
How does std::thread store variadic arguments passed through its constructor?

Time:06-22

I am trying to make a class in which you could pass into its constructor a callable object and a variable number of arguments. These constructor arguments would be stored somewhere. Then there would be a method that would run the passed in callable object with its arguments.

Basically I am trying to make a class extremely similar to std::thread, the only difference being it would not be a thread; the run method would execute in sequence with the program.

What I'm ultimate trying to do is have a bunch of arbitrary tasks that I can put in a single vector and then be able to run at any time. That is why I don't want template parameters for the class itself, just on its constructor. That way I can store multiple Function_Containers in one group.

For instance:

class Function_Container {
public:
   template<typename Callable, typename... Args>
   Function_Container(Callable callable, Args... args) 
   {
      //store callable and args somewhere for later use
   }

   void run() {
      callable(args...); //call our callable with the passed in arguments
   }
};

void addLetter(std::string& s, char c) {
   s.push_back(c);
}

void multiplyNum(int& x, int multiplier) {
   x *= multiplier;
}

int main()
{
   std::string str = "goat";
   Function_Container task1(addLetter, std::ref(str), 's');
   
   int x = 5;
   Function_Container task2(multiplyNum, std::ref(x), 5);

   std::vector<Function_Container> tasks = { task1, task2 };
   
   for (Function_Container& f : tasks) {
      f.run();
   }

   std::cout << str << ", " << x << std::endl;
}

output:

goats, 25

It seems to me that the way std::thread stores variadic arguments passed through its constructor is similar to the method I am trying to implement here. I've looked at the thread header, and it looks like they are using tuples to store the variadic arguments, with some really crazy stuff like std::decay. What exactly are they doing?

CodePudding user response:

you should store the params in std::tuple and invoke them using std::apply

#include <functional>
#include <tuple>
#include <vector>

template <class R>
class Function_Wrapper {
 public:
  template <typename Callable, typename... Args>
  Function_Wrapper(Callable&& callable, Args&&... args)
      : fn_([=, args = std::make_tuple(std::forward<Args>(args)...)]() {
          return std::apply(callable, args);
        }) {}

  decltype(auto) run() {
    // call our callable with the passed in arguments
    return fn_();
  }

  decltype(auto) operator()() { return run(); }

 private:
  std::function<R()> fn_;
};

int add(int a, int b) { return a   b; }

int main() {
  std::vector<Function_Wrapper<int>> f{{&add, 9, 30}, {&add, 1, 2}};
  return f[0].run()   f[1]();
}

Here in Compiler Explorer

CodePudding user response:

Your Function_Container looks more like a function wrapper which stores the arguments for later use. This part can be solved with a capturing lambda, so you don't need to create your own type for that. To store such lambdas in a container, you can use a std::vector<function<void()>>.

Here's a version where I've instead made Function_Container to be a wrapper around such a vector and turned the constructor into a member function called add.

class Function_Container {
public:
    template <class Callable, class... Args>
    void add(Callable&& callable, Args&&... args) {
        callables.emplace_back([c = std::forward<Callable>(callable),
                                args = std::make_tuple(std::forward<Args>(args)...)]
        {
            std::apply(c, args); // apply unpacks the tuple to get the parameter pack back
        });
    }

    auto begin() { return callables.begin(); }
    auto end() { return callables.end(); }

private:
    std::vector<std::function<void()>> callables;
};

Now Function_Container would be used to store the tasks and executing a task would then be to call it (instead of calling run()).

void addLetter(std::string& s, char c) { s.push_back(c); }

void multiplyNum(int& x, int multiplier) { x *= multiplier; }

int main() {
    Function_Container tasks;

    std::string str = "goat";
    tasks.add(addLetter, std::ref(str), 's');

    int x = 5;
    tasks.add(multiplyNum, std::ref(x), 5);
    
    for (auto& task : tasks) {
        task();
    }

    std::cout << str << ", " << x << std::endl;
}

Demo

  • Related