c++tbbc++23intel-oneapi

How can I make TBB parallel_for_each work with std::views::zip?


I'm replacing our own in-house ZipIterator/ZipRange implementation with std::views::zip and am running into a wall when it comes to TBB's parallel_for_each.

With our own ZipRange, it was perfectly capable of modifying one of the elements returned by the ZipRange, but when I replace it with std::views::zip, the functor seems to get a copy, and not a modifiable reference as I would expect, resulting in the parallel_for_each having to effect and leaving the "result" container unmodified.

Since it worked with our (very euhm, involved) in-house ZipIterator/ZipRange, and std::ranges::for_each does what it's supposed to do, but tbb::parallel_for_each leaves the elements unchanged, as demonstrated with this program:

#include <ranges>
#include <vector>
#include <iostream>
#include <algorithm>

#include <tbb/parallel_for_each.h>

int main()
{
    const std::vector<int> ints{1,2,3};
    const std::vector<double> doubles{0.1, 0.2, 0.3};

    const auto multiply = [](auto&& intDoubleProduct)
    {
        auto&& [i, d, product] = intDoubleProduct;
        product = i * d;
    };

    {
        std::vector<double> product = {0, 0, 0};
        std::ranges::for_each(std::views::zip(ints, doubles, product), multiply);
        std::cout << "std::ranges::for_each result: (";
        for(auto value : product)
            std::cout << value << ", ";
        std::cout << '\n';
    }
    {
        std::vector<double> product = {0, 0, 0};
        tbb::parallel_for_each(std::views::zip(ints, doubles, product), multiply);
        std::cout << "tbb::parallel_for_each result: (";
        for(auto value : product)
            std::cout << value << ", ";
        std::cout << '\n';
    }
}

https://godbolt.org/z/EvMWGsTP5

How can I make a read-write std::views::zip work with tbb::parallel_for_each? The only relevant reference I could discover is what seems like an admission of a bug in the TBB code, but I would think that has long been fixed (I cannot find the referenced code any more, so maybe this case was lost in some refactoring on their end?): https://community.intel.com/t5/Intel-oneAPI-Threading-Building/tbb-parallel-for-and-std-for-each/m-p/848767


Solution

  • The issue is that std::ranges::zip_view<Rs...>::value_type is std::tuple<std::ranges::range_value_t<Rs>...>. You need to turn it into a range of std::tuple<std::ranges::range_reference_t<Rs>...> instead.

    You can do that by zip_transforming with std::forward_as_tuple wrapped in a lambda (as it isn't an addressable function).

    See Godbolt

    std::ranges::for_each doesn't use the value_type of the passed in range, it does1 f(proj(*it)).

    1. something not observably different to