Range-Based for in C++11

What is the meaning of range-based for loop is a C++11 extension and what is expected expression?

warning: range-based for loop is a C++11 extension [-Wc++11-extensions]

What this means is that you are compiling your code with a version of C++ selected that is prior to C++11, but as an extension, your compiler is going to allow you to do it anyway. However, the code is not portable because not all compilers will allow it. That is why it is warning you.

If you want your code to be portable, you should either stop using the range-based-for, or compile with the C++11 version (or greater) of the C++ Standard selected.

This may mean adding a flag like -std=c++11 to your compile command, but it varies by compiler and development environment.

I would also advise configuring your compiler to adhere to strict standards if you want portable code. For example, with GCC or clang this can be done by adding -pedantic-errors to the compile command.

Can C++11 and C++17 Range-Based For Loop iterate to a specific position instead of full range of the map?

You either need an external counter to make early exit, eg:

int n = 0;
for(auto&& [k, v] : map)
{
if(++n > 10) break;
std::cout << k << ": " << v << std::endl;
}

Or, if you are not afraid of copying the map, you can do:

auto copy = std::map<...>{map.begin(), std::next(map.begin(), 10)};
for(auto&& [k, v] : copy)
{
std::cout << k << ": " << v << std::endl;
}

Finally, if you can use C++20, then you can simply do this:

#include <ranges>
for(auto&& [k, v] : map | std::views::take(10))
{
std::cout << k << ": " << v << std::endl;
}

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.

What is the correct way of using C++11's range-based for?

TL;DR: Consider the following guidelines:

  1. For observing the elements, use the following syntax:

    for (const auto& elem : container)    // capture by const reference
    • If the objects are cheap to copy (like ints, doubles, etc.),
      it's possible to use a slightly simplified form:

        for (auto elem : container)    // capture by value
  2. For modifying the elements in place, use:

    for (auto& elem : container)    // capture by (non-const) reference
    • If the container uses "proxy iterators" (like std::vector<bool>), use:

        for (auto&& elem : container)    // capture by &&

Of course, if there is a need to make a local copy of the element inside the loop body, capturing by value (for (auto elem : container)) is a good choice.



Detailed Discussion

Let's start differentiating between observing the elements in the container
vs. modifying them in place.

Observing the elements

Let's consider a simple example:

vector<int> v = {1, 3, 5, 7, 9};

for (auto x : v)
cout << x << ' ';

The above code prints the elements (ints) in the vector:

1 3 5 7 9

Now consider another case, in which the vector elements are not just simple integers,
but instances of a more complex class, with custom copy constructor, etc.

// A sample test class, with custom copy semantics.
class X
{
public:
X()
: m_data(0)
{}

X(int data)
: m_data(data)
{}

~X()
{}

X(const X& other)
: m_data(other.m_data)
{ cout << "X copy ctor.\n"; }

X& operator=(const X& other)
{
m_data = other.m_data;
cout << "X copy assign.\n";
return *this;
}

int Get() const
{
return m_data;
}

private:
int m_data;
};

ostream& operator<<(ostream& os, const X& x)
{
os << x.Get();
return os;
}

If we use the above for (auto x : v) {...} syntax with this new class:

vector<X> v = {1, 3, 5, 7, 9};

cout << "\nElements:\n";
for (auto x : v)
{
cout << x << ' ';
}

the output is something like:

[... copy constructor calls for vector<X> initialization ...]

Elements:
X copy ctor.
1 X copy ctor.
3 X copy ctor.
5 X copy ctor.
7 X copy ctor.
9

As it can be read from the output, copy constructor calls are made during range-based for loop iterations.

This is because we are capturing the elements from the container by value
(the auto x part in for (auto x : v)).

This is inefficient code, e.g., if these elements are instances of std::string,
heap memory allocations can be done, with expensive trips to the memory manager, etc.
This is useless if we just want to observe the elements in a container.

So, a better syntax is available: capture by const reference, i.e. const auto&:

vector<X> v = {1, 3, 5, 7, 9};

cout << "\nElements:\n";
for (const auto& x : v)
{
cout << x << ' ';
}

Now the output is:

 [... copy constructor calls for vector<X> initialization ...]

Elements:
1 3 5 7 9

Without any spurious (and potentially expensive) copy constructor call.

So, when observing elements in a container (i.e., for read-only access),
the following syntax is fine for simple cheap-to-copy types, like int, double, etc.:

for (auto elem : container) 

Else, capturing by const reference is better in the general case,
to avoid useless (and potentially expensive) copy constructor calls:

for (const auto& elem : container) 

Modifying the elements in the container

If we want to modify the elements in a container using range-based for,
the above for (auto elem : container) and for (const auto& elem : container)
syntaxes are wrong.

In fact, in the former case, elem stores a copy of the original
element, so modifications done to it are just lost and not stored persistently
in the container, e.g.:

vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v) // <-- capture by value (copy)
x *= 10; // <-- a local temporary copy ("x") is modified,
// *not* the original vector element.

for (auto x : v)
cout << x << ' ';

The output is just the initial sequence:

1 3 5 7 9

Instead, an attempt of using for (const auto& x : v) just fails to compile.

g++ outputs an error message something like this:

TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
x *= 10;
^

The correct approach in this case is capturing by non-const reference:

vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
x *= 10;

for (auto x : v)
cout << x << ' ';

The output is (as expected):

10 30 50 70 90

This for (auto& elem : container) syntax works also for more complex types,
e.g. considering a vector<string>:

vector<string> v = {"Bob", "Jeff", "Connie"};

// Modify elements in place: use "auto &"
for (auto& x : v)
x = "Hi " + x + "!";

// Output elements (*observing* --> use "const auto&")
for (const auto& x : v)
cout << x << ' ';

the output is:

Hi Bob! Hi Jeff! Hi Connie!

The special case of proxy iterators

Suppose we have a vector<bool>, and we want to invert the logical boolean state
of its elements, using the above syntax:

vector<bool> v = {true, false, false, true};
for (auto& x : v)
x = !x;

The above code fails to compile.

g++ outputs an error message similar to this:

TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of
type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen
ce {aka std::_Bit_reference}'
for (auto& x : v)
^

The problem is that std::vector template is specialized for bool, with an
implementation that packs the bools to optimize space (each boolean value is
stored in one bit, eight "boolean" bits in a byte).

Because of that (since it's not possible to return a reference to a single bit),
vector<bool> uses a so-called "proxy iterator" pattern.
A "proxy iterator" is an iterator that, when dereferenced, does not yield an
ordinary bool &, but instead returns (by value) a temporary object,
which is a proxy class convertible to bool.
(See also this question and related answers here on StackOverflow.)

To modify in place the elements of vector<bool>, a new kind of syntax (using auto&&)
must be used:

for (auto&& x : v)
x = !x;

The following code works fine:

vector<bool> v = {true, false, false, true};

// Invert boolean status
for (auto&& x : v) // <-- note use of "auto&&" for proxy iterators
x = !x;

// Print new element values
cout << boolalpha;
for (const auto& x : v)
cout << x << ' ';

and outputs:

false true true false

Note that the for (auto&& elem : container) syntax also works in the other cases
of ordinary (non-proxy) iterators (e.g. for a vector<int> or a vector<string>).

(As a side note, the aforementioned "observing" syntax of for (const auto& elem : container) works fine also for the proxy iterator case.)

Summary

The above discussion can be summarized in the following guidelines:

  1. For observing the elements, use the following syntax:

    for (const auto& elem : container)    // capture by const reference
    • If the objects are cheap to copy (like ints, doubles, etc.),
      it's possible to use a slightly simplified form:

        for (auto elem : container)    // capture by value
  2. For modifying the elements in place, use:

    for (auto& elem : container)    // capture by (non-const) reference
    • If the container uses "proxy iterators" (like std::vector<bool>), use:

        for (auto&& elem : container)    // capture by &&

Of course, if there is a need to make a local copy of the element inside the loop body, capturing by value (for (auto elem : container)) is a good choice.



Additional notes on generic code

In generic code, since we can't make assumptions about generic type T being cheap to copy, in observing mode it's safe to always use for (const auto& elem : container).

(This won't trigger potentially expensive useless copies, will work just fine also for cheap-to-copy types like int, and also for containers using proxy-iterators, like std::vector<bool>.)

Moreover, in modifying mode, if we want generic code to work also in case of proxy-iterators, the best option is for (auto&& elem : container).

(This will work just fine also for containers using ordinary non-proxy-iterators, like std::vector<int> or std::vector<string>.)

So, in generic code, the following guidelines can be provided:

  1. For observing the elements, use:

    for (const auto& elem : container)
  2. For modifying the elements in place, use:

    for (auto&& elem : container)

range-based for in c++11

No, unluckily. See what the standard says:

The range-based for statement
for ( for-range-declaration : expression ) statement
is equivalent to

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

where __range, __begin, and __end are variables defined for exposition only

In other words, it already iterates from begin to end and already dereferences the iterator, which you never get to see.

C++11 range-based for loop on derived objects

The cppreference.com page states that a range-based for loop expression produces a code similar to the following (__range, __begin and __end are for exposition only):

for ( range_declaration : range_expression ) loop_statement

{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}

With that description, I deduce that the __begin and __end must be of the same type, in fact, this isn't your case. A pointer to Parent isn't a pointer to Derived, they must be casted explicitly and the range-based for loop does not perform any cast at all.

In order to achieve what you're looking for, I would create a helper class that performs the cast for me1, for example:

struct Helper
{
Helper(Parent *p) : obj(p) {}
Parent *const obj;
operator Derived *() { return static_cast<Derived *>(obj); }
};

This helper allows us to do the following:

std::vector<Parent*> objects { new Derived(), new Derived() };

for (Helper h : objects) {
Derived *d = h;
d->use();
}

We can templatize the helper class in order to use it on other Parent-Derived contexts:

template <typename T> struct THelper
{
THelper(Parent *p) : obj(p) {}
Parent *const obj;
operator T *() { return static_cast<T *>(obj); }
};

...
...

for (THelper<Derived> h : objects) {
Derived *d = h;
d->use();
}

for (THelper<Derived2> h : objects) {
Derived *d = h;
d->use();
}

But if your objects vector contains mixed types, could be dangerous.

As a improvement, you can replace the static_cast with a dynamic_cast and check for nullptr, but you must be aware of it's overhead.

This isn't a solution that I would use on a project of mine but I hope it looks clean and neat to you ;)

Edit

however, the conversion (whether explicit with static_cast or through the Helper class) is still required as an additional line inside the for loop.

As we talked before, the cast (static or dynamic) is mandatory, but if you're concerned about the neatness of the loop being truncated by the addition of an additional line, overloading the operator -> should do the trick:

struct Helper
{
Helper(Parent *p) : obj(p) {}
Parent *const obj;
operator Derived *() { return static_cast<Derived *>(obj); }
Derived *operator ->() { return static_cast<Derived *>(obj); }
};

for (Helper h : objects) {
h->use();
}

Note that if you're using the operator -> you cannot check for nullptr, which would be necessary if your objects vector have mixed derived types.

I've updated the live demoin order to include the new code.


1 Although, as we can read on the comments, there are better ways to achieve your goal.

Does a C++11 range-based for loop condition get evaluated every cycle?

It is only evaluated once. The standard defines a range-based for statement as equivalent to:

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

where range-init is the expression (surrounded by parentheses) or braced-init-list after the :

Range-based for loop C++11 from optimization side

There are no fundamental reasons why a ranged-based for loop would be slower than a manual loop. The code that a ranged-based for loop is defined to be identical to is a relatively optimal loop.

In your case, each loop iteration logically calls size(). If at any point in the loop the compiler becomes unable to prove that size() cannot change in your loop, the compiler must actually call size() (which typically involves subtracting two pointers) and check it. This could cost some performance in your manual loop case.

In the for(:) loop it could cost correctness, in that if the vector being looped over has its begin or end iterator (or current iterator) invalidated during the loop, the loop continues to loop over the old iterators and undefined behavior can result.

So the two loops are not (in general) equivalent.

In the cases where the looped-over range is unchanging, the for(:) loop will perform as well, or better, than most hand-crafted loops that just iterate over elements, because most hand-crafted loops don't make a copy of end() and compare against that copy.

In this particular case, the overhead of IO (and to a lesser extent, formatting) will massively overwhelm any loop overhead.



Related Topics



Leave a reply



Submit