Is it valid to use pointer-difference to find the index of an element within a range-based for loop?
A number of questions have been asked here concerning using indices and range-based loops together, but they almost all say to not use range-based loops if you also need the index of an element. But it seems like, at least for std::vector, std::array, and other containers which are contiguous in memory, you could use pointer differences to find the index of the element, provided you're iterating over element references. For example:
// Prints the indices of all elements for which Foo::bar is true.
void Example(const std::vector<Foo>& v) {
for (const auto& e : v) {
if (e.bar()) {
auto index = &e - v.data(); // Valid???
std::cout << index << std::endl;
}
}
}
The above code compiles and runs correctly, but I'm not completely certain of its validity. Can any language lawyers confirm or deny whether this is an acceptable method to find the index of the element?
In addition, is it safe to assume that if a container has a data() member, then its elements are contiguous in memory, and is therefore safe to use with this approach?
CodePudding user response:
If the underlying iterator meets the requirements of the LegacyContiguousIterator (C 17), then yes. This requirement indicates that *(itr n) is equivalent to *(addressof(*itr) n).
This is from https://en.cppreference.com/w/cpp/named_req/ContiguousIterator
C 20 replaced it with the contiguous_iterator concept.
The Cppreference page indicates vector<bool> does not meet the above concepts, but all other vectors do. As do string, string_view, array, and the iterators for the begin/end overloads for valarray.
CodePudding user response:
auto index = &e - v.data(); // Valid???
Unless Foo is an alias to bool, yes.
&e and v.data() having pointer type, [expr.add]/5 applies:
When two pointer expressions
PandQare subtracted, the type of the result is an implementation-defined signed integral type; this type shall be the same type that is defined asstd::ptrdiff_tin the<cstddef>header ([support.types.layout]).(5.2) Otherwise, if
PandQpoint to, respectively, array elementsiandjof the same array objectx, the expressionP - Qhas the valuei−j.
So, unless e or v.data() are not part of the same array object, this is well-defined. This could happen if Foo is an alias for bool, otherwise this condition is met per vector.overview/2 ensuring [container.requirements.general], itself ensuring [iterator.concept.contiguous]/2:
Let
aandbbe dereferenceable iterators andcbe a non-dereferenceable iterator of typeIsuch thatbis reachable fromaandcis reachable fromb, and letDbeiter_difference_t<I>. The typeImodels contiguous_iterator only if(2.2)
to_address(b) == to_address(a) D(b - a), and
(2.3)to_address(c) == to_address(a) D(c - a).
