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
P
andQ
are 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_t
in the<cstddef>
header ([support.types.layout]
).(5.2) Otherwise, if
P
andQ
point to, respectively, array elementsi
andj
of the same array objectx
, the expressionP - Q
has 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
a
andb
be dereferenceable iterators andc
be a non-dereferenceable iterator of typeI
such thatb
is reachable froma
andc
is reachable fromb
, and letD
beiter_difference_t<I>
. The typeI
models 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)
.