So I'm trying to create a class which has a container for functors of a different type.
This is a simplified version of it.
template<class T>
class Container
{
public:
template<typename F, typename ... ARGS>
void addTask(F && func, ARGS && ... args);
private:
std::deque<std::function<T()>> container;
//.....
};
template<class T>
template<typename F, typename ... ARGS>
T Container<T>::addTask(F && func, ARGS && ... args);
{
container.emplace_back(std::bind(f,args...));
//.....
}
There are still few problems that I cannot solve yet.
std::bind
and store a different object or a pointer?int
,void
...)?consexpr
bind.std::bind is a Standard Function Objects that acts as a Functional Adaptor i.e. it takes a function as input and returns a new function Object as an output with with one or more of the arguments of passed function bound or rearranged.
Bind function with the help of placeholders helps to manipulate the position and number of values to be used by the function and modifies the function according to the desired output.
std::bind return type The return type of std::bind holds a member object of type std::decay<F>::type constructed from std::forward<F>(f), and one object per each of args... , of type std::decay<Arg_i>::type, similarly constructed from std::forward<Arg_i>(arg_i).
From a comment by the OP.
There are. This is simplified. I'm using futures and a special container in the real code. It is meant to be used in a multithreading environment
This is called burying the lede.
If you are storing callables to be invoked in other threads, in the other thread you want signature void()
. In this thread you want a std::future
to be populated.
As for binding arguments, while a number of std
functions do this for you, I find it is best to ask for callables with pre-bound arguments. They can do it outside, using std::bind
or lambdas or whatever other means they choose.
So this then comes
template<class Func,
class R = std::decay_t<std::result_of_t<Func const&()>>
>
std::future< R >
addTask( Func&& func ) {
auto task = std::packaged_task<R()>(std::forward<Func>(func));
auto ret = task.get_future();
container.push_back( std::packaged_task<void()>( std::move(task) ) );
return ret;
}
std::deque< std::packaged_task<void()> > container;
throw in some mutexes and shake and bake.
Here I use std::packaged_task<void()>
as a pre-written move-only type-erased container for anything with that signature. We don't use the future
it can produce, which is a waste, but it is shorter than writing your own move-only invoke-once owning function object.
I personally just wrote myself a light weight move-only std::function<void()>
esque class instead of using std::packaged_task<void()>
, but it was probably unwise.
The future returned from addTask
gets fullfilled when the packaged_task<R()>
is invoked, which is invoked when the packaged_task<void()>
is invoked (possibly in another thread).
Outside of the structure, callers can give you any zero-argument callable object.
99 times out of 100, a simple [some_arg]{ some_code; }
or even []{ some_code; }
works. In complex cases they can mess around with std::bind
or C++14 improvements with more complex lambdas.
Putting the storing of the arguments into addTask
mixes the responsibility of the thread-task-queue with messing with arguments.
In fact, I'd write a thread-safe queue separately from my thread-pool, and have the thread-pool use it:
template<class T>
struct thread_safe_queue;
struct thread_pool {
thread_safe_queue< std::packaged_task<void()> > queue;
// etc
};
In C++17, a replacement for your bind looks like:
[
func = std::forward<Func>(func),
args = std::make_tuple( std::forward<Args>(args)... )
]() mutable {
std::apply( func, std::move(args) );
}
In C++14 you can write notstd::apply
pretty easy. Move-into-lambda requires C++14, so if you need to efficiently move arguments you need std bind or a manual function object in C++11.
I will argue that placing the argument binding strongly in the domain of the code using the thread pool is best.
That also permits the thread pool to do things like pass the tasks optional extra arguments, like "cancellation tokens" or the like.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With