In code such as this:

std::for_each(std::execution::par, Vals.begin(), Vals.end(), [&](const MyType& val) {
    Other[ind] = f(val); // ind should be the index of val in Vals.
});

What is the best way to get the value ind?

This is similar to these questions:

But the main solutions discussed in those questions don't work here:

it - Vals.begin()

I don't have an iterator.

std::distance(Vals.begin(), it)

Again, no iterator.

And having a separate loop counter variable doesn't work in the parallel for_each case.

The solution I've found is this:

size_t ind = &val - &(*Vals.begin());

But this feels like working against the language instead of with it. It's also a bit error prone and inelegant. Is there something better?

As an aside, is this issue well understood and appreciated? I face this several times a day. I feel like way more than half the time that I'm iterating over a container I need the index, not just the data. And range-based for and std::for_each are just no help at all.

EDIT: When I say "error prone", my main concern is that a compiler might choose to make a copy of val on the stack when calling the language. This would be semantically acceptable since val is const. But in that case, the pointer to val would not be in the array, so subtracting &*Vals.begin() would be very wrong. Does the language allow such a compiler implementation, or can we know for sure that a pointer to val will be inside Vals?

1

There are 1 answers

1
康桓瑋 On

If this inelegant formula bothers you, you can transform the original range of values into a range of iterators, so that you can get the iterator directly in the lambda to calculate the index and get the underlying type through operator*()

auto ValIters = std::views::iota(Vals.begin(), Vals.end());
std::for_each(std::execution::par, ValIters.begin(), ValIters.end(), 
  [&](auto iter) {
    Other[iter - Vals.begin()] = f(*iter);
  });

Demo

While currently C++20 <ranges> does not work well with C++17 parallel algorithms, but if you're using libstdc++, I think the above example is fine, although it's not strictly well-formed.

Hopefully in C++26 we can finally write directly like this (with the help of views::enumerate)

ranges::for_each(std::execution::par, views::enumerate(Vals), [&](auto e) {
  Other[e.index] = f(e.value);
});