Add Elements to a Vector During Range-Based Loop C++11

Add elements to a vector during range-based loop c++11

No you cannot rely on this behaviour. Modifying the vector inside the loop results in undefined behaviour because the iterators used by the loop are invalidated when the vector is modified.

The range based for loop

for ( range_declaration : range_expression) loop_statement

is essentially equivalent to

{
auto && __range = range_expression ;
for (auto __begin = std::begin(__range),
__end = std::end(__range);
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}

When you modify the vector, the iterators __begin and __end are no longer valid and the dereferencing __begin results in undefined behaviour.

Range-base for and insert on a vector C++11

No, you can't. The standard mandates that the raged-based for behaves like a given algorithm. This algorithm uses iterators, which get invalidated when you modify the vector.

The simplest way for me is to to use iterators. Note that when we insert, we also reassign the iterator so that we always have a valid iterator:

auto it = vec.begin();
while(it < vec.end()) {
if (condition(*it)) {
new_elem = function(*it);
it = vec.insert(it + 1, new_elem);
}
++it;
}

inserting into the back of std::list during range based for loop

Yes, inserting / removing elements of a std::list does not invalidate pointers, references, or iterators to elements, except for the removed element. Not even the end-iterator is changed or invalidated.

Thus, it is safe.

But as one has to carefully ponder about safety, it is still inadvisable.

Setting vector elements in range-based for loop

Change this loop statement

for(auto n: *CTdata)

to

for(auto &n : *CTdata)

that is you have to use references to elements of the vector.

Fill a vector of pointers with a range-based for loop

Take the elements by reference:

for (auto& r : v)
pv.push_back(&r);

Your original range-based for loop iterates the vector by value, making each i a fresh copy of each item. The expression &i therefore does not retrieve the address of element inside v.

Iterating by reference, each r refers to the very element inside v, and thus the address-of expression &r correctly retrieve the element's address.

C++11: Range-looping vector from the second element?

Create a wrapper for which begin() and end() return the correct iterators and then you can use that as the second argument.

#include <iostream>
#include <vector>

template< typename Collection >
class FromNth
{
Collection& coll_;
size_t offset_;

public:
FromNth( Collection& coll, size_t offset )
: coll_( coll ), offset_( offset )
{
}

// will nicely resolve to const_iterator if necessary
auto begin() const -> decltype( coll_.begin() )
{ return coll_.begin() + offset_; }

auto end() const -> decltype( coll_.end() )
{ return coll_.end(); }
};

template< typename Collection >
FromNth<Collection> makeFromNth( Collection& collection, size_t offset )
{
return FromNth<Collection>( collection, offset );
}

template< typename Collection >
auto begin( const FromNth<Collection> & wrapper ) -> decltype( wrapper.begin() )
{
return wrapper.begin();
}

template< typename Collection >
auto end( const FromNth<Collection> & wrapper ) -> decltype( wrapper.end() )
{
return wrapper.end();
}

int main()
{
std::vector< int > coll { 2, 3, 5, 7, 11, 13, 17, 19, 23 };

for( auto x : makeFromNth( coll, 1 ) )
{
std::cout << x << '\n';
}
return 0;
}

Note that my fromNth "begin" is undefined behaviour if the size of the input is less than the offset. (If it's equal then it's well defined and begin == end). Therefore do a size check first.

Note: if you are using a recent enough version of boost then iterator_range may already provide you such a "collection" that is similar to my "FromNth".

for( auto const& s : boost::make_iterator_range( v.begin() + 1, v.end() ) )
{
process( s );
}

Note: the code above worked on CodingGround using C++11 GNU 4.8.3. (That site is very slow though). From C++14 you will not need the ->decltype statements (which are needed in C++11 for templates).

Output:

sh-4.3$ g++ -std=c++11 -o main *.cpp
sh-4.3$ main
3
5
7
11
13
17
19
23

Erasing vector elements while in a range-based loop vs. standard loop

In the first case, for each iteration std::vector::size function is being called. Thus, if you delete all the elements in the first iteration, std::vector::size function which is called before the start of second iteration will return 0. Therefore, second iteration won't happen because the condition i < test.size() is not satisfied.

In the second case, range-based for loop uses iterators instead of std::vector::size function. When you call std::vector::erase you invalidate all the iterators including the end() iterator. Therefore, second case is actually UB (Undefined Behavior) and you should never rely on that.

From the docs:

std::vector::erase

... Invalidates iterators and references at or after the point of the
erase, including the end() iterator.

Is it legal to add elements to a preallocated vector in a range-based for loop over that vector?

Your code exhibits undefined behavior, but it is tricky and tends to only be caught in debug builds.

When you do a v.push_back, all iterators are invalidated if size passes capacity. You avoid this with a reserve.

However, even if you don't cause the capacity to grow, the past-the-end iterator is still invalidated. In general, iterator invalidation rules do not distinguish between "the iterator's 'value' would be garbage/refer to a different object" and "the iterator's 'location' is no longer valid". When either occurs, the iterator is simply deemed invalid. As the end iterator is no longer the end iterator (it goes from referring to nothing, to referring to something, in almost every implementation), the standard simply states it is invalidated.

This code:

for (auto e: v)
v.push_back(e*e);

expands to roughly:

{
auto && __range = v;
for (auto __begin = v.begin(),
__end = v.end();
__begin != __end;
++__begin
)
{
auto e = *__begin;
v.push_back(e*e);
}
}

the v.push_back call invalidates the __end iterator, which is then compared against, and the debug build correctly flags the undefined behavior as a problem. Debug MSVC iterators are pretty careful with invalidation rules.

The release build does undefined behavior, and as the vector iterator is basically a thin wrapper around a pointer, and the pointer to the past-the-end element becomes the pointer to the last element after a push back without a capacity overflow, it "works".

range based for loop for custom linked list containing pointers in C++, only returns objects

The standard defines the ranged based for to be equivalent to:

{
auto && __range = range-init;
for ( auto __begin = begin-expr,
__end = end-expr;
__begin != __end;
++__begin )
{
for-range-declaration = *__begin;
statement
}
}

The reason ele shouldn't be a pointer is because of the dereference of the iterator (for-range-declaration = *__begin;), so the for-range-declaration needs to be a reference type or the actual value type.

Hence you need to change your iterator class such that the operator* returns Element* or Element*&.

Can range-based C++11 for do/check extra operations/conditions?

Unfortunately, you can't put the increment into the range based for loop. However, in your specific case - as std::vector stores its elements contigously in memory - you can simulate option 2 by falling back to pointers (thanks to @M.M and @Jarod42 for corrections and improvements):

for ( const int& val : v )  {
std::cout << "v at index " << &val-v.data() << " is " << val;
}

more generic:

for ( const auto& val : v )  {
std::cout << "v at index " << std::addressof(val)-v.data() << " is " << val;
}

The other thing you can do is to write a index_range class, that represents a collections of indexes over which you can iterate in your range based for loop:

struct index_range_it {
size_t idx;
size_t operator*(){
return idx;
}
index_range_it& operator++() {
idx++;
return (*this);
}
};

bool operator!=(index_range_it l,index_range_it r) {
return l.idx != r.idx;
}

struct index_range {
size_t size;
index_range_it end(){return index_range_it{size};}
index_range_it begin(){return index_range_it{0};}
};

int main()
{
for (auto i: index_range{v.size()}){
std::cout << "v at index " << i << " is " << v[i];
}
}

A full fledged implementation of this idea can be found e.g. here

Such a range can then also be composed to something, where the iterator returns a proxy object containing the index as well as a reference to the current object and with c++17's structured binding that would be even more convenient to use.



Related Topics



Leave a reply



Submit