On 03/08/19 23:14 -0400, Sam Varshavchik wrote:
Andrew Lutomirski writes:
>On a cursory search of the standard, I couldn't find where it says
>what operator* on this type of iterator does at all, let alone whether
>it's valid for one-past-the-end iterators, but I'm pretty sure that
>your code is, indeed, wrong.
This finally piqued my curiosity enough to take some time to look into
this. That expression applied the "*" operator to a random access
iterator. So, going down the path:
# A class or pointer type X satisfies the requirements of a random
access# iterator if, in addition to satisfying the requirements for
bidirectional# iterators, ...
In a similar fashion, a bidirectional iterator delegates some
requirements to a forward iterator.
The forward iterator delegates some of its requirements to an input iterator.
At the end of the road, in [input.iterators]:
# *a reference, convertible to T Requires: a is dereferenceable.
Then, finally, in [iterator.requirements.general]:
# Values of an iterator i for which the expression *i is defined are
This reads to me like a definition that circularly defines itself.
[input.iterators] says "*a requires that a is dereferencable". And
[iterator.requirements.general] says that something is dereferencable
if "*" for it is defined. That certainly clears that up…
Full disclosure: it's true that the next sentence is:
# The library never assumes that past-the-end values are dereferenceable.
But this only states that the library assumes that, it doesn't
authoritatively state that.
Because some past-the-end iterators are dereferenceable. The
past-the-end iterator for a sequence [a,b) which is subsequence of
[a,c) will be dereferenceable is b!=c.
But the end() iterator for a container is both past-the-end and
non-dereferenceable, because it's not a subsequence of some larger
sequence. It really is past the end and there is no object at that
But going back to the previous point, if we also want to see what
going down "*i refers the unary * operator" route, as a means of
avoiding the self-definition, we see that:
# The unary * operator performs indirection: … the result is an lvalue
That's irrelevant in this case. See [expr.pre] which says "Subclause
[expr.post] defines the effects of operators when applied to types for
which they have not been overloaded."
GCC's std::vector::iterator is not a pointer, it's a class with an
overloaded operator*, so its semantics are not required to be the same
as the built-in * on pointers.
Nothing here requires the pointer to be valid, just that "*"
an lvaule. Nothing more, nothing else. That's it. Whether the pointer
Wrong. An lvalue can only refer to a valid object.
is valid, this gets punted only when the lvalue-to-rvalue conversion
# ... if the object to which the glvalue refers contains an invalid pointer
# value the behavior is implementation-defined.
No, that is not saying you can have an lvalue formed by dereferencing
an invalid pointer value. It's saying that applying the
lvalue-to-rvalue conversion to an invalid pointer value is
implementation defined. i.e.
int* ptr = new int(0);
lvalue_to_rvalue_conv(ptr); // implementation defined
That wording was added by https://wg21.link/cwg616
and is irrelevant
here. Dereferencing invalid or null pointer values is not allowed in
But if you immediately apply the & operator, this conversion never
True in C, not in C++.
The C standard is even more explicit, and even blesses this construct,
# The unary & operator yields the address of its operand.
# [ … ]
# if the operand is the result of a  operator, neither the &
operator # nor the unary * that is implied by the  is evaluated and
the result is as # if the & operator were removed and the  operator
were changed to a + # operator
I could not find some similar verbiage in the C++ standard, but based
You can't find it because there is no such rule in C++. There is an
open issue asking whether it should be allowed, but it's been open
(and inactive) for many years. See https://wg21.link/cwg232
on all of the above I have to conclude that this is …too late today,
and I should really get some sleep…
Anyway, even if that issue was resolved, it would be irrelevant when
considering an overloaded operator*.
Applying & to the result of an overloaded operator* still has to
evaluate the overloaded operator* and then evaluate & (which might
also use an overloaded operator&). So any rule that said that &*p is a
no-op when p is a pointer would not apply when p is a class type, like
But not after rereading the specification for a vector itself, where I
found something I glossed over the first time:
# A vector satisfies all of the requirements of a container and … of a#
The reference from "contiguous container" goes to:
# A contiguous container is a container that supports random access iterators
# and whose member types iterator and const_iterator are contiguous iterators.
The reference from "contiguous iterators" goes to:
# Iterators that further satisfy the requirement that, for integral values
# n and dereferenceable iterator values a and (a + n), *(a + n) is
# equivalent to *(addressof(*a) + n), are called contiguous iterators.
If n is foo.size() then (a + n) is not a dereferenceable iterator, so
the *(addressof(*a) + n) equivalence doesn't hold. Because *a is
There might be more to the "contiguous" semantics that
whether or not "&foo[foo.size()]" is defined behavior, or not, but
here addressof(*a) already puts us squarely in pointer equivalence
territory, and seems to again go into the unary "*" operator direction
Nope, it does whatever std::vector::iterator::operator* does. Which is
not necessarily the same as the built-in unary * for pointers.
here, despite the conflicting wording.
It's undefined. GCC's assertion is correct. Fix the code.
>…now it's definitely too late in the day.