C++ Logo

std-proposals

Advanced search

Re: [std-proposals] vector::push_back must not invalidate past-the-end iterator

From: Nikolay Mihaylov <nmmm_at_[hidden]>
Date: Mon, 8 Dec 2025 17:26:57 +0200
I think the rule of iterator invalidation is for different purpose:

std::vector<int> v;
v.reserve(2); // 2 elements are reserved. let suppose the memory
pointer is 0x100, so the iterators are 0x100, 0x108, 0x110 etc.
v.push_back(1);

auto b = std::begin(v); // points to element 0
auto e = std::begin(v); // points to element 1

v.push_back(1); // ok, no resize, but e points to element 1

v.push_back(1); // now the vector allocates new memory and copies the data
(unless you are on Windows). now the memory pointer points to 0x200.

b and e no pointing to a free-ed memory. Unfortunately in reality, in most
cases the memory exists and contains "correct" information. so code like
this can work, in most of the cases:

printf("%d\n", *b); // will print 1 in most of the cases.
                            // in other cases it may print something else.
(see bellow)
                            // in other cases it may do general protection
error.
                           // but in all cases, this is wrong and we call
it UB.

I am 100% sure you know that


------------------------
*EXAMPLE 1 - not invalidated, but does not point where you think it is.*

#include <cstdio>
#include <vector>

int main(){
std::vector<int> v;
v.reserve(2);
v.push_back(1);

auto b = std::begin(v);
auto e = std::end(v);

v.push_back(2);

printf("%d\n", *e);
}

[nmmm_at_xps15 ~]$ ./a.out
2
------------------------



*EXAMPLE 2 - invalidation*
#include <cstdio>
#include <vector>

int main(){
std::vector<int> v;
v.reserve(2);
v.push_back(1);
v.push_back(1);

auto b = std::begin(v);
auto e = std::begin(v);

printf("%p\n", (void *) & *b );

v.push_back(1);

printf("%d\n", *b);

printf("%p\n", (void *) & v.front() );
}

[nmmm_at_xps15 ~]$ ./a.out
0x5d99bb1d52b0
-644107819
0x5d99bb1d56e0


---------------

On Mon, Dec 8, 2025 at 5:02 PM Sebastian Wittmeier via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> So you are saying the standard overspecified that behavior of the end
> iterator, as long as no new allocation happened?
>
>
>
> Besides possibly not necessary with all implementations, what is the
> practical use case of allowing it?
>
>
>
> With 'it' in this case being the previous end iterator (Or past the end?
> end is already 'past the end') now pointing to an element.
>
>
>
>
>
> Trying to find use cases for not allowing it
>
> ================================
>
>
>
> 1) In theory (not 100% sure) there could be an implementation (even on
> x64), where any end pointer is marked (by using a bit), so that it can
> still be compared, but any dereference leads to a trap.
>
> Then b==e would still be true, but e could not be derenferenced, whereas b
> can.
>
>
>
> 2) Other possibility - perhaps too slow: All pointer arithmetics is
> checked.
>
> ++b checks for the current size of the underlying storage and returns
>
> - a valid pointer, if b was not the last element and
>
> - .end(), if it was.
>
>
> -----Ursprüngliche Nachricht-----
> *Von:* Nikl Kelbon via Std-Proposals <std-proposals_at_[hidden]>
> *Gesendet:* Mo 08.12.2025 15:45
> *Betreff:* [std-proposals] vector::push_back must not invalidate
> past-the-end iterator
> *An:* std-proposals <std-proposals_at_[hidden]>;
> *CC:* Nikl Kelbon <kelbonage_at_[hidden]>;
> The standard needs to better clarify the section on vector iterator
> invalidation.
>
> *Why i think its important:*
>
> Some implementations added checks like: "oh, we must store all .end()
> iterators into global map under mutex and mark them invalid on each
> push_back, it will be SO useful for our developers!"
> So, minimal example where it breaks completely:
>
> std::vector<int> v; v.reserve(10); v.push_back(1); auto b = v.begin(); auto e = v.end(); v.push_back(1); ++b; REQUIRE(b == e); // assertion failure: // _STL_VERIFY(this->_Getcont() == _Right._Getcont(), "vector iterators incompatible");
>
>
> Its common pattern when using vector to reserve memory and push values,
> there are no "better way to do it", thats why it must be valid
>
>
> *Now about standard:*
>
> here's a quote from the standard regarding append_range and, apparently,
> push_back (https://eel.is/c++draft/vector#modifiers-2):
>
>
> If no reallocation happens, then references, pointers, and iterators
> before the insertion point remain valid but those at or after the insertion
> point, including the past-the-end iterator, are invalidated
>
>
> It explicitly states that despite there are no relocation happen, the
> past-the-end iterator is invalidated, although there's no reason for a
> vector to be so.
> Yes, a* past-the-end iterator will no longer be past-the-end, but that
> doesn't make it invalid*. In any implementation, even a foolish one, it's
> hard to imagine how, without relocation, this iterator could become
> anything other than just an iterator to the last element of the vector.
>
> I think in this case *standard should separate invalidation and what
> happens here - its not invalidation rly.*
>
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2025-12-08 15:29:41