3

I am trying to use QtConcurrent::mapped into a QVector<QString>. I already tried a lot of methods, but it seems there are always problems with overloading.

QVector<QString> words = {"one", "two", "three", "four"};

using StrDouble = std::pair<QString, double>;

QFuture<StrDouble> result = QtConcurrent::mapped<StrDouble>(words, [](const QString& word) -> StrDouble {
    return std::make_pair(word + word, 10);
});

This snippet returns the following error:

/home/lhahn/dev/cpp/TestLambdaConcurrent/mainwindow.cpp:23: error: no matching function for call to ‘mapped(QVector<QString>&, MainWindow::MainWindow(QWidget*)::<lambda(const QString&)>)’
 });
  ^

I saw this post, which says that Qt cannot find the return value of the lambda, so you have to use std::bind with it. If I try this:

using StrDouble = std::pair<QString, double>;
using std::placeholders::_1;

auto map_fn = [](const QString& word) -> StrDouble {
    return std::make_pair(word + word, 10.0);
};

auto wrapper_map_fn = std::bind(map_fn, _1);

QFuture<StrDouble> result = QtConcurrent::mapped<StrDouble>(words, wrapper_map_fn);

But the the error is still similar:

/home/lhahn/dev/cpp/TestLambdaConcurrent/mainwindow.cpp:28: error: no matching function for call to ‘mapped(QVector<QString>&, std::_Bind<MainWindow::MainWindow(QWidget*)::<lambda(const QString&)>(std::_Placeholder<1>)>&)’
 QFuture<StrDouble> result = QtConcurrent::mapped<StrDouble>(words, wrapper_map_fn);
                                                                                  ^

I also tried wrapping the lambda inside std::function but unfortunately similar results.

  • Note that this example is just for reproduction, I need a lambda because I am also capturing variables in my code.
lhahn
  • 1,241
  • 2
  • 14
  • 40
  • Apparently, QtConcurrent does not support lambdas yet... As far as I can read from the sources, they want function pointers or member function pointers: https://github.com/qt/qtbase/blob/dev/src/concurrent/qtconcurrentfunctionwrappers.h – Felix May 02 '17 at 10:10

3 Answers3

4

The following compiles for me:

QVector<QString> words = {"one", "two", "three", "four"};
std::function<StrDouble(const QString& word)> func = [](const QString &word) {
    return std::make_pair(word + word, 10.0);
};

QFuture<StrDouble> result = QtConcurrent::mapped(words, func);

Output of qDebug() << result.results():

(std::pair("oneone",10), std::pair("twotwo",10), std::pair("threethree",10), std::pair("fourfour",10))

  • 2
    It works because it have nothing in the capture list. QtConcurrent do not works with lambda function with captures. – benlau May 28 '17 at 15:08
  • 2
    Correct: I mean QtConcurrent::mapped do not works with lambda function with captures – benlau May 28 '17 at 15:09
1

Unfortunately that QtConcurrent::mapped does not support lambda function with captures. You could need a custom implementation. For example, you may make a one with AsyncFuture:

template <typename T, typename Sequence, typename Functor>
QFuture<T> mapped(Sequence input, Functor func){
    auto defer = AsyncFuture::deferred<T>();

    QList<QFuture<T>> futures;
    auto combinator = AsyncFuture::combine();

    for (int i = 0 ; i < input.size() ; i++) {
        auto future = QtConcurrent::run(func, input[i]);
        combinator << future;
        futures << future;
    }

    AsyncFuture::observe(combinator.future()).subscribe([=]() {
        QList<T> res;
        for (int i = 0 ; i < futures.size(); i++) {
            res << futures[i].result();
        }
        auto d = defer;
        d.complete(res);
    });

    return defer.future();
}

Usage:

auto future = mapped<int>(input, func);

Complete Example:

https://github.com/benlau/asyncfuture/blob/master/tests/asyncfutureunittests/example.cpp#L326

benlau
  • 301
  • 3
  • 9
1

QtConcurrent::map[ped] works with functor types that have the result_type member type. Thus you need to wrap the lambda in a class that provides such type. The std::function wrapper provides this, but it might have more overhead - thus we can make our own.

Taking code from How to extract lambda's Return Type and Variadic Parameters Pack back from general template<typename T>, we have:

#include <utility>
#include <type_traits>

template <class T> struct function_traits : function_traits<decltype(&T::operator())> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
   // specialization for pointers to member function
   using functor_type = ClassType;
   using result_type = ReturnType;
   using arg_tuple = std::tuple<Args...>;
   static constexpr auto arity = sizeof...(Args);
};

template <class Callable, class... Args>
struct CallableWrapper : Callable, function_traits<Callable> {
   CallableWrapper(const Callable &f) : Callable(f) {}
   CallableWrapper(Callable &&f) : Callable(std::move(f)) {}
};

template <class F, std::size_t ... Is, class T>
auto wrap_impl(F &&f, std::index_sequence<Is...>, T) {
   return CallableWrapper<F, typename T::result_type,
         std::tuple_element_t<Is, typename T::arg_tuple>...>(std::forward<F>(f));
}

template <class F> auto wrap(F &&f) {
   using traits = function_traits<F>;
   return wrap_impl(std::forward<F>(f),
                    std::make_index_sequence<traits::arity>{}, traits{});
}

The wrapped functor, in addition to the result_type needed by Qt, also has the functor_type, arg_tuple, and arity.

Instead of passing the lambda directly, pass the wrapped functor:

auto result = QtConcurrent::mapped<StrDouble>(words, wrap([](const QString& word){
    return std::make_pair(word + word, 10);
}));

The value returned by wrap is a functor that implements result_type.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313