0

I wondered if there is an option to also hand over the current processed index with QtConcurrent::mapped(someVector, &someFunction)) (also filter, filtered, map,...)

What I want: I want to do something with the elements in someVector based on the current index in it. but since the function someFunction is only taking the type T which is also used for the QVector<T> vector.

What I did: Because I needed this, I created a QVector<std::pair<int, T>> and manually created the index for the elements.

Since this requires more space and is not a nice solution, I thought maybe there could be another solution.

Docs: https://doc.qt.io/qt-5/qtconcurrent-index.html

schlenger
  • 1,447
  • 1
  • 19
  • 40
  • Something is wrong if an element needs to know its number in a container – Vladimir Bershov Oct 30 '20 at 16:12
  • 1
    @vladimir, I disagree. Nothing is wrong with processing the container as a numbered sequence of elements. This is quite a common pattern, that functions [like `enumerate` were added to Python](https://www.python.org/dev/peps/pep-0279/). – Mike Oct 30 '20 at 22:24

1 Answers1

0

If your input is a QVector, you can make use of the fact that QVector stores all the elements contiguously. This means that given a reference to an element e in a QVector v, then the index of e can be obtained by:

std::ptrdiff_t idx = &e - &v.at(0);

Below is a complete example using QtConcurrent::mapped:

#include <iterator>
#include <numeric>
#include <type_traits>
#include <utility>

#include <QtCore>
#include <QtConcurrent>


// lambda functions are not directly usable in QtConcurrent::mapped, the
// following is a necessary workaround
// see https://stackoverflow.com/a/49821973
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{});
}

int main(int argc, char* argv[]) {
    QCoreApplication app(argc, argv);

    // a vector of numbers from 0 to 500
    QVector<int> seq(500, 0);
    std::iota(seq.begin(), seq.end(), 0);
    qDebug() << "input: " << seq;
    QFuture<int> mapped = QtConcurrent::mapped(seq, wrap([&seq](const int& x) {
        // the index of the element in a QVector is the difference between
        // the address of the first element in the vector and the address of
        // the current element
        std::ptrdiff_t idx = std::distance(&seq.at(0), &x);
        // we can then use x and idx however we want
        return x * idx;
    }));
    qDebug() << "output: " << mapped.results();

    QTimer::singleShot(100, &app, &QCoreApplication::quit);
    return app.exec();
}

See this question for a related discussion. Note that the linked question has a cleaner answer that involves the usage of zip and counting iterators from boost (or possibly their C++20 ranges counterparts), but I don't think that this would play well with QtConcurrent::map when map slices the sequence into blocks, and distributes these blocks to multiple threads.

Mike
  • 8,055
  • 1
  • 30
  • 44