3

I was hoping to create a variadic template function which sits in front of the QtConcurrent::run functions that does some stuff and then passes the parameters on.

QtConcurrent::run is massively overloaded - check out qtconcurrentrun.h

Is it possible to create a variadic template function that I can call which will pass through to QtConcurrent::run ? This is what I have thus far:

template <typename returnT, typename... Args>
static auto Run(Args&&... args) -> QFuture<returnT>
{
     // Do Stuff

     // Now call through to start the task
     QFuture<returnT> future = QtConcurrent::run(std::forward<Args>(args)...);

     QFutureWatcher<void>* futureWatcher = new QFutureWatcher<void>(); //A QFutureWatcher<void> is special, see QFutureWatcher QT docs.
     futureWatcher->setFuture(future);
     QObject::connect(futureWatcher, &QFutureWatcher<void>::finished, [=]() { 
        // Do stuff
        futureWatcher->deleteLater();
     });
     return future;
  }

I'm struggling to work out how to deduce the return type, so I've got the returnT as a separate template param. This doesn't compile (VS2012 Nov CTP) when called with:

Tasking::TaskManager::Run<void>([&]() { while (stopTask == false); });

With the top couple error messages being:

1> error C2065: '<lambda_86e0f4508387a4d4f1dd8316ce3048ac>' : undeclared identifier
1>          Implementation\TaskingTests\TaskManagerTests.cpp(31) : see reference to function template instantiation 'QFuture<void> Tasking::TaskManager::Run<void,TaskManagerTests::WaitsForTaskTest::<lambda_86e0f4508387a4d4f1dd8316ce3048ac>>(TaskManagerTests::WaitsForTaskTest::<lambda_86e0f4508387a4d4f1dd8316ce3048ac> &&)' being compiled
1>C:\tkbt\Launch2.0.0\ICDE\IceLibrary\Implementation\Tasking/TaskManager.hpp(108): error C2974: 'std::forward' : invalid template argument for '_Ty', type expected
1>          C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\type_traits(1780) : see declaration of 'std::forward'
1>          C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\type_traits(1774) : see declaration of 'std::forward'
1>C:\tkbt\Launch2.0.0\ICDE\IceLibrary\Implementation\Tasking/TaskManager.hpp(108): error C2780: 'QFuture<T> QtConcurrent::run(const Class *,T (__cdecl Class::* )(Param1,Param2,Param3,Param4,Param5) const,const Arg1 &,const Arg2 &,const Arg3 &,const Arg4 &,const Arg5 &)' : expects 7 arguments - 0 provided
1>          c:\qt\qt5.0.2\5.0.2\msvc2012_64\include\qtconcurrent\qtconcurrentrun.h(333) : see declaration of 'QtConcurrent::run'

Any help much appreciated.

gremwell
  • 1,419
  • 17
  • 23

1 Answers1

1

I guess that TaskManager.hpp(108) is the line where you call QtConcurrent::run.

What you are experiencing seems to be this MSVC bug. In short, variadic templates cannot forward lambdas in MSVC. You probably will have to use an oldscool functor in this case or provide nonvariadic overloads to support lambdas, maybe for the first few arguments. If I had to guess I'd think QtConcurrent::run's first argument has to be a function and the other arguments are its parameters, meaning you never get to call Run without arguments. You could rewrite your function template to have one fixed "normal" parameter for the function and the parameter pack for the function arguments.

For the return type deduction you might want to use decltype. Together that would look like this:

template <class F, class... Args>
static auto Run(F&& f, Args&&... args) 
  -> decltype(QtConcurrent::run(std::forward<F>(f), std::forward<Args>(args)...))
{
  auto future = QtConcurrent::run(std::forward<F>(f), std::forward<Args>(args)...);

  //I suppose this can not be a smart pointer?
  auto futureWatcher = new QFutureWatcher<void>(); 

  futureWatcher->setFuture(future);
  QObject::connect(futureWatcher, &QFutureWatcher<void>::finished, [=]() { 
    // Do stuff
    futureWatcher->deleteLater();
  });
  return future;
}

This way, the lambda would be passed to the normal template parameter F, wich should be ok for forwarding, i.e. the bug should not happen this way.

Update: If QtConcurrent::run does not give you the correct return type right away, you could go by using decltype on the function and its arguments:

static auto Run(F&& f, Args&&... args) 
  -> QtFuture<decltype(f(std::forward<Args>(args)...))>

Maybe you'll need to add some std::remove_reference and std::remove_const to the decltype to get the right future type.

Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • Thank you for pointing out the MSVC bug. I have implemented an overload which takes a functor: – gremwell Jul 04 '13 at 00:41
  • template static auto Run(Functor functor) -> QFuture { // stuff} This works with the lambda. Hopefully my struggles with getting the return type to work properly were caused by this as well. – gremwell Jul 04 '13 at 00:47
  • Your comment "I suppose this can not be a smart pointer": It could be a shared_ptr, and that would also work and is probably safer lest I leave out the deleteLater. Thanks. – gremwell Jul 04 '13 at 00:54
  • I don't know Qt, and the deleteLater confused me a bit. Some frameworks have their own means to deal with reference counting that are not compatible with `shared_ptr` glad to read Qt isn't one of them :) – Arne Mertz Jul 04 '13 at 05:28
  • deleteLater pretty much schedules it for deletion when it gets back to the main event loop. I'm having doubts about shared_ptr being safer now though, because connect takes a copy and keeps it for the duration of the object, so it's a chicken and egg problem. Delete later will stay. – gremwell Jul 04 '13 at 07:34