0

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?

All The Rage
  • 743
  • 5
  • 24
  • Sounds like a case for `std::transform`, or you know, `for(int ...)` with a little bit of threading. – Captain Giraffe Apr 16 '22 at 05:35
  • Algorithms (like `for_each`) operate on **ranges**, not containers. In general, there is on way to get from an iterator back to the container that it came from (if, in fact, it actually came from a container) without some additional information. Algorithms don't have that information. – Pete Becker Apr 16 '22 at 13:26
  • (STL is dead. Long live the Standard Library.) – aschepler Apr 16 '22 at 16:22
  • See my edit. Anyone know if the pointer math answer in fact has a hazard, or not? – All The Rage Apr 16 '22 at 18:15
  • @aschepler, oh, I didn't know the name had changed. I've been using C++ since the '80s but I try not to put too much effort into tracking its evolution. – All The Rage Apr 16 '22 at 18:16

1 Answers1

1

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);
});
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • Thank you. This is a reasonable answer. It's unfortunately verbose. That enumerate syntax looks quite promising, though. Very Python-like. – All The Rage Apr 16 '22 at 18:13