Alternative for std::bind in modern C++

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.


std::bind came from boost::bind, which was necessary before we had lambdas.

Unfortunately std::bind made it into the standard at the same time as lambdas, so it was immediately almost irrelevant.

In c++14 and beyond you can capture the function and args in a variadic lambda:

template<class T>
template<typename F, typename ... ARGS>
T Container<T>::addTask(F && func, ARGS && ... args)
{
    container.emplace_back( [func = std::forward<F>(func),
                             args...] 
                             ()  mutable // make mutable if you want to move the args in to func
                             {
                                 return func(std::move(args)...);
                             });

    //.....
}

You don't quite get perfect forwarding this way. There is a copy implicit in the capture of args...

This solved in c++17

template<class T>
template<typename F, typename ... ARGS>
T Container<T>::addTask(F && func, ARGS && ... args)
{
    container.emplace_back( [func = std::forward<F>(func),
                             args = std::make_tuple(std::forward<ARGS>(args)...)                                 ] 
                             ()  mutable // make mutable if you want to move the args in to func
                             {
                                 return std::apply(func, std::move(args));
                             });

    //.....
}