Search code examples
c++c++17c++20stl-algorithm

Call a member function of a vector of elements with a vector of arguments in parallel


Given this piece of code:

struct T
{
  void f(int const);
};

void f(std::vector<T> &u, std::vector<int> const &v)
{
  for (std::size_t i = 0; i < u.size(); ++i)
    u[i].f(v[i]);
}

Is there a standard way to parallelize the body of void f(std::vector<T> &u, std::vector<int> const &v)?

This happens to work by chance (https://godbolt.org/z/gRv9Ze):

void f(std::vector<T> &u, std::vector<int> const &v)
{
  auto const indices = std::views::iota(0u, u.size()) | std::views::common;

  std::for_each(std::execution::par_unseq, std::begin(indices), std::end(indices),
                [&](std::size_t const i) { u[i].f(v[i]); });
}

but it is reportedly wrong to rely on such behavior (see this bug report and this answer). Indeed, this doesn't run in parallel (https://godbolt.org/z/MPGdHF):

void f(std::vector<T> &u, std::vector<int> const &v)
{
  std::ranges::iota_view<std::size_t, std::size_t> const indices(0u, u.size());

  std::for_each(std::execution::par_unseq, std::begin(indices), std::end(indices),
                [&](std::size_t const i) { u[i].f(v[i]); });
}

I'm pretty sure there should be a standard way to make a function like that run in parallel. I'm probably missing an obvious algorithm, but std::transform does not seem to be appropriate here, and the others even less so.


Solution

  • Staying within std, your best bet is std::transform with an output iterator that ignores what is given to it

    struct unit_iterator {
        using difference_type = std::ptrdiff_t;
        using value_type = std::tuple<>;
        using pointer = std::tuple<> *;
        using const_pointer = const std::tuple<> *;
        using reference = std::tuple<> &;
        using const reference = const std::tuple<> &;
        using iterator_category = std::random_access_iterator_tag;
    
        reference operator*() { return value; }
        const_reference operator*() const { return value; }
        reference operator[](difference_type) { return value; }
        const_reference operator[](difference_type) const { return value; }
        pointer operator->() { return &value; }
        const_pointer operator->() const { return &value; }
    
        unit_iterator& operator++() { return *this; }
        unit_iterator operator++(int) { return *this; }
        unit_iterator& operator+=(difference_type) { return *this; }
        unit_iterator operator+(difference_type) const { return *this; }
    
        unit_iterator& operator--() { return *this; }
        unit_iterator operator--(int) { return *this; }
        unit_iterator& operator-=(difference_type) { return *this; }
        unit_iterator operator-(difference_type) const { return *this; }
    
        difference_type operator-(unit_iterator) const { return 0; }
    
        bool operator==(unit_iterator) const { return true; }
        bool operator!=(unit_iterator) const { return false; }
        bool operator<(unit_iterator) const { return false; }
        bool operator<=(unit_iterator) const { return true; }
        bool operator>(unit_iterator) const { return false; }
        bool operator>=(unit_iterator) const { return true; }
    private:
        static value_type value;
    };
    
    
    void f(std::vector<T> &u, std::vector<int> const &v)
    {
      std::transform(std::execution::par_unseq, begin(u), end(u), begin(v), unit_iterator{},
                     [](T & u, int v) { u.f(v); return std::tuple<>{}; });
    }