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