Stephen Eckels
Stephen Eckels

Reputation: 479

C++ Template class member on a template function

I have a thread pool that should accept any std::packaged_task and give a future.

template<typename RetType>
template<typename Args...>
std::future<RetType> submitWork(std::packaged_task<RetType(Args...)>&& callableWork>);

As you can see the packaged_task is templated. Now my thread pool uses a lockless queue as a member of the class

class ThreadPool
{
public:
private:
    llQueue<boost::variant<???>> workQueue;
}

I want the work queue to be a variant of the types that submitWork gets called by. Ex: this code

bool runByPool(int var)
{
     //do stuff
}

int runAlso(char c)
{
    //do other stuff
}

ThreadPool pool; // 4 worker threads
pool.submitWork<bool(int)>(std::bind(runByPool, 1));
pool.submitWork<int<c>>(std::bind(runAlso, 'a'));

Gives the following type to workQueue at compilation:

llQueue<boost::variant<std::packaged_task<bool(int)>,
                       std::packaged_task<int(c)>
                      >
        >

How do i make the member of the class use the types of the templated submitWork? I want to force the llQueue to only hold std::packaged_task's and i used a variant so that i can avoid heap allocation since this need to be highly, highly performant.

I would like to avoid heap allocations and i need the same pool to be able to execute any work with any return type or parameter type

Upvotes: 0

Views: 175

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

Most the code you posted does not compile.

pool.submitWork<bool(int)>(std::bind(runByPool, 1));

the signature of std::bind(runByPool, 1) is bool() not bool(int). The same error is in your other example, well, ignoring the other syntax errors there.

std::future<RetType> submitWork(std::packaged_task<RetType&&(Args&&...)&& callableWork>);

this signature is insanity. It should be;

std::future<RetType> submitWork(std::packaged_task<RetType(Args...)> callableWork);

next, it makes almost no sense to take work that still needs args of a non-uniform type. Which is reflected in your examples.

In fact, taking a packaged task here is pointless.

std::future<RetType> submitWork(std::function<RetType()> callableWork>);

makes more sense. You take an operation returning a T, and return a future T.

llQueue<boost::variant<???>> workQueue;

there is no need for a variant here. You want a queue of tasks you can run. Their return type should already be routed elsewhere, and their arguments are already bound.

llQueue<std::function<void()>> workQueue;

now there remains a technical issue. std::function<void()> requires that it be copyable; bit the easy way to wire the callableWork to a future leaves you with a non-copyable packaged task.

There are a few ways around this. The first is to shove the packaged task into a shared ptr then store that in a function. The second is noting that packaged_task<T()> is a move only callable with signature void(), which can be stored in a packaged_task<void()>.

So we come full circle.

struct ThreadPool {
  template<class F, class R=std::result_of_t<F&()>>
  std::future<R> submitWork(F f){
    auto task=std::packaged_task<R()>(std::move(f));
    auto r=task.get_future();
    workQueue.push_back(std::packaged_task<void()>(std::move(task)));
    return r;
  }
  std::vector<std::packaged_task<void()>> workQueue;
  // or:
  //llQueue<std::packaged_task<void()>> workQueue;
  // with changes to how things are enqueued
};

and as a bonus it deduces the return type for you. Live example.

I believe I have seen at least one C++ compiler that screwed up and made packaged tasks require copyable contents. So the shared ptr containing function may be your backup plan.

Upvotes: 1

Related Questions