11

Why does this code

#include <algorithm>
#include <iterator>
#include <vector>

int main()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.reserve(v.size() * 2);  // Reserve enough space to keep iterators valid
    std::copy(v.begin(), v.end(), std::back_inserter(v));
    return 0;
}

give me the debug assertion failure, Expression: vector iterators incompatible (Visual C++ 2008)?

user541686
  • 205,094
  • 128
  • 528
  • 886

1 Answers1

14

Iterators corresponding to elements are only invalidated when the vector has to be reallocated, which reserve avoids.

However, v.end() won't stay valid.

The Standard's description of push_back and insert guarantees that

Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.

v.end() is not "before the insertion point".

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 2
    +1 Dang... that's so darn annoying. What's the best workaround? – user541686 Apr 06 '13 at 20:29
  • A `for` loop operating on the index? – Ben Voigt Apr 06 '13 at 20:30
  • @Mehrdad, I don't imagine `insert` avoids this mess? – chris Apr 06 '13 at 20:30
  • @chris: No, it's described in the same language, iterators at or after the insertion point become invalid. – Ben Voigt Apr 06 '13 at 20:31
  • @BenVoigt: Sorry, I meant a workaround using iterators... this was just an example to show the problem; `for` loops with indices don't generalize well to other containers. :( – user541686 Apr 06 '13 at 20:31
  • @Mehrdad: Modifying a container mid-iteration doesn't generalize well. I suppose you could copy all except the last element, and then `push_back` it separately following the loop. – Ben Voigt Apr 06 '13 at 20:32
  • Aw man that's ugly. :( (Iterating a container while modifying it is perfectly fine in principle though, as long as the caller can reserve enough space before having you insert into it.) Oh well... – user541686 Apr 06 '13 at 20:33
  • @Mehrdad: How about using `std::copy_n` and `v.size()`? – Ben Voigt Apr 06 '13 at 20:35
  • @BenVoigt: Doesn't exist in C++03 :( And it's quite limited too, since it wouldn't work for e.g. `copy_if`. – user541686 Apr 06 '13 at 20:36
  • @Mehrdad, You *could* roll your own I suppose. For `copy_n`, you could use [this](http://en.cppreference.com/w/cpp/algorithm/copy_n). It's easy to adjust for working with a predicate, but really only so for others if you don't need to recreate too much :/ It feels bad doing that sort of thing. – chris Apr 06 '13 at 20:42
  • @chris: Well, `copy_n` is the easiest case to do that for, but the real problem is with `copy_if` and more complicated cases, where you don't know how many elements you will insert exactly. But even for `copy_n`, rolling your own implementation isn't a great idea: `std::copy` is often optimized by the implementation (e.g. with `memcpy`) for POD types and such, and you won't be able to do that correctly and efficiently with `copy_n`. – user541686 Apr 06 '13 at 20:45
  • @Mehrdad: The memcpy optimization for blit-able objects should be even easier with `copy_n` than `copy`. – Ben Voigt Apr 06 '13 at 20:46
  • @BenVoigt: Uhm, not really. Blittability of objects isn't enough -- I need to know the containers' iterator implementations as well, to be able to convert them to pointers. It's easy enough for `vector`, but not for e.g. `deque`. And writing a `copy_n` function that only works on `vector`s of PODs is rather silly and pointless. – user541686 Apr 06 '13 at 20:49
  • @Mehrdad: I didn't say it was easy. I said it's easier than with `std::copy`, which also requires contiguity before turning to `memcpy`. – Ben Voigt Apr 06 '13 at 20:51
  • @BenVoigt: Your assumption is still wrong though. `std::copy` doesn't require contiguity of all the elements; for example, consider `deque`, where only some of the elements might be contiguous. `std::copy` could very well contain optimizations for calling `memcpy`'s for each contiguous region, but there's no way I could implement that myself. – user541686 Apr 06 '13 at 20:52
  • @Mehrdad: The contiguity requirement is the same for `copy` and `copy_n`. Someone managed to implement it, so it clearly is possible. – Ben Voigt Apr 06 '13 at 20:54
  • @BenVoigt: Neither `copy` nor `copy_n` requires contiguity, so I'm not sure what requirement you're referring to. "Someone" managed to do it because he was designing both the containers and the algorithms, so he didn't have an abstraction barrier to break. My point is that it's impossible to do this **portably** because there's no way for me to optimize it without knowing the internals of each container whose iterator might be given to me, but the implementation certainly can (and does, in some cases). – user541686 Apr 06 '13 at 20:55
  • @Mehrdad: Optimization of `std::copy` requires (1) using a trait to detect random-access iterators or (2) special-casing for containers which have contiguous sequences. It's the optimization of using `memcpy` to implement `std::copy` that requires contiguity, and this is the same for `copy_n`. – Ben Voigt Apr 06 '13 at 21:13
  • @BenVoigt: We're not going anywhere without an example. Can you demonstrate to me an example of how you think an optimized `copy_n` could be implemented for `std::deque::iterator`? – user541686 Apr 06 '13 at 21:14
  • Neither `std::copy` nor `copy_n` can portably be optimized for a container without random-access iterators. Please note that the author of the Standard library is prohibited from using non-portable optimizations, because `std::copy` has to work for specializations of `std::deque` also. `copy_n` doesn't face any problems not encountered with `copy`. – Ben Voigt Apr 06 '13 at 21:39
  • @BenVoigt: *"Neither `std::copy` nor `copy_n` can portably be optimized for a container without random-access iterators."*... You completely ignored the question, so I'll ask it again: **Can you show me a portable optimized example of `std::copy[_n]`** for `std::deque::iterator`, which **does** have random-access iterators? (Specializations are 100% irrelevant -- the implementation could easily detect a user-defined specialization and use slower code for it, e.g. by failing to find a special type inside that code. There's no need to compromise correctness here.) – user541686 Apr 07 '13 at 07:45
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/27728/discussion-between-ben-voigt-and-mehrdad) – Ben Voigt Apr 07 '13 at 15:50
  • So if you v.reserve() without allocating, `v.end()` is not usable until it allocates? – TankorSmash Sep 16 '16 at 21:45