Damir Tenishev
Damir Tenishev

Reputation: 3402

Is it safe to insert a range in std::vector if its source is at the tail of the same vector?

Is it safe to insert a range in std::vector if its source is the tail of the same vector?

I need to insert some elements to std::vector as copies of elements in the same vector which positioned later than the inserted place.

So, I wrote the following code demo :

#include <iostream>
#include <ranges>
#include <vector>

int main()
{
    {
        std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        v.reserve(2*v.size());

        // Works just fine on both CLang and MSVC
        v.insert(
            v.begin() + 3,
            v.begin() + 5,
            v.end()
        );

        std::cout << "insert: ";
        std::ranges::copy(v, std::ostream_iterator<int>{std::cout, ", "});
        std::cout << std::endl;
    }

    {
        std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        auto range_to_insert = std::ranges::subrange(v.begin() + 5, v.end());

        v.reserve(v.size() + range_to_insert.size());   // To avoid reallocation and iterators invalidation during insert_range

        // If previous reservation took place, crashes address sanitizer on MSVC and gives wrong results in Debug on my local PC with the latest MSVC x64
        v.insert_range(
            v.begin() + 3,
            range_to_insert
        );

        std::cout << "insert_range: ";
        std::ranges::copy(v, std::ostream_iterator<int>{std::cout, ", "});
        std::cout << std::endl;
    }
}

I can’t reproduce this on godbolt, but on my local PC with the latest MSVC x64 in Debug it shows:

insert: 0, 1, 2, 5, 6, 7, 8, 9, 3, 4, 5, 6, 7, 8, 9,
insert_range: 0, 1, 2, -572662307, -572662307, -572662307, -572662307, -572662307, 3, 4, 5, 6, 7, 8, 9,

while in Release it shows the same result as on godbolt:

insert: 0, 1, 2, 5, 6, 7, 8, 9, 3, 4, 5, 6, 7, 8, 9, 
insert_range: 0, 1, 2, 5, 6, 7, 8, 9, 3, 4, 5, 6, 7, 8, 9,

In all cases address sanitized at _Copy_memmove with long to show error log. And this crashes if and only if I use this line:

v.reserve(v.size() + range_to_insert.size());

I expected to have quite opposite behavior, and introduced it to avoid reallocation during insert_range. In case I remove this line, code works fine both in Release and Debug.

I can’t see any limitations for such insert neither for std::vector<T,Allocator>::insert_range, nor for std::vector<T,Allocator>::insert.

The question is, are such operations safe or there is some violation? Can I insert elements, copying from the tail of the vector (which could affect iterators somehow) and can I insert elements, if indexes of inserted elements overlap with old indexes of source elements?

I do understand that some things in the question above are unrelated with ranges insert operations where both indexes and iterators involved, but I hope you get the point correctly.

Upvotes: 0

Views: 64

Answers (0)

Related Questions