How Does the Range-Based for Work for Plain Arrays

How does the range-based for work for plain arrays?

It works for any expression whose type is an array. For example:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
n *= 2;
delete [] arraypointer;

For a more detailed explanation, if the type of the expression passed to the right of : is an array type, then the loop iterates from ptr to ptr + size (ptr pointing to the first element of the array, size being the element count of the array).

This is in contrast to user defined types, which work by looking up begin and end as members if you pass a class object or (if there is no members called that way) non-member functions. Those functions will yield the begin and end iterators (pointing to directly after the last element and the begin of the sequence respectively).

This question clears up why that difference exists.

Range based for loop in function which passes an array as value

The reason it doesn't compile is that in C++ a function parameter such as char array[] is adjusted to char* array. Your function really looks like

int print_a(char* array)
{
....
}

and the range based loops cannot deal with a pointer.

One solution is to pass the array by reference. C++ does not allow you to pass plain arrays by value. For example, this would accept an array of 5 chars:

int print_a(const char (& array)[5])
{
for(char c : array) cout << c;
cout << endl;
return 42;
}

In order to generalise this to arrays of different sizes, you can use a template:

template <std::size_t N>
int print_a(const char (& array)[N])
{
for(char c : array) cout << c;
cout << endl;
return 42;
}

Of course, there are easier ways to print a null-terminated string:

char hello[] {"Hello!"};
cout << hello << endl;

And there are standard library types that make passing string or char buffer objects around easier. For example, std::string, std::vector<char>, std::array<char, N> (where N is a compile time constant.)

Range based for-loop on array passed to non-main function

With the array decaying into a pointer you're losing one important piece of information: its size.

With an array reference your range based loop works:

void foo(int (&bar)[3]);

int main() {
int bar[3] = {1,2,3};
for (int i : bar) {
cout << i << endl;
}
foo(bar);
}

void foo(int (&bar)[3]) {
for (int i : bar) {
cout << i << endl;
}
}

or, in a generic fashion (i.e. without specifying the array size in the function signature),

template <std::size_t array_size>
void foo(int (&bar)[array_size]) {
for (int i : bar) {
cout << i << endl;
}
}

Try it out

Range-based for loop on a dynamic array?

To make use of the range-based for-loop you have to provide either begin() and end() member functions or overload the non-member begin() and end() functions.
In the latter case, you can wrap your range in a std::pair and overload begin() and end() for those:

    namespace std {
template <typename T> T* begin(std::pair<T*, T*> const& p)
{ return p.first; }
template <typename T> T* end(std::pair<T*, T*> const& p)
{ return p.second; }
}

Now you can use the for-loop like this:

    for (auto&& i : std::make_pair(array, array + size))
cout << i << endl;

Note, that the non-member begin() and end() functions have to be overloaded in the std namespace here, because pair also resides in namespace std. If you don't feel like tampering with the standard namespace, you can simply create your own tiny pair class and overload begin() and end() in your namespace.

Or, create a thin wrapper around your dynamically allocated array and provide begin() and end() member functions:

    template <typename T>
struct wrapped_array {
wrapped_array(T* first, T* last) : begin_ {first}, end_ {last} {}
wrapped_array(T* first, std::ptrdiff_t size)
: wrapped_array {first, first + size} {}

T* begin() const noexcept { return begin_; }
T* end() const noexcept { return end_; }

T* begin_;
T* end_;
};

template <typename T>
wrapped_array<T> wrap_array(T* first, std::ptrdiff_t size) noexcept
{ return {first, size}; }

And your call site looks like this:

    for (auto&& i : wrap_array(array, size))
std::cout << i << std::endl;

Example

Why doesn't C++ support range based for loop for dynamic arrays?

int* array = new int[len];
for[] (int i : array) {}

There are several points which must be addressed; I'll tackle them one at a time.

Does the run-time knows the size of the array?

In certain conditions, it must. As you pointed out, a call to delete[] will call the destructor of each element (in reserve order) and therefore must know how many there are.

However, by not specifying that the number of elements must be known, and accessible, the C++ standard allows an implementation to omit it whenever the call to the destructor is not required (std::is_trivially_destructible<T>::value evaluates to true).

Can the run-time distinguish between pointer and array?

In general, no.

When you have a pointer, it could point to anything:

  • a single item, or an item in an array,
  • the first item in an array, or any other,
  • an array on the stack, or an array on the heap,
  • just an array, or an array part of a larger object.

This is the reason what delete[] exists, and using delete here would be incorrect. With delete[], you the user state: this pointer points to the first item of a heap-allocated array.

The implementation can then assume that, for example, in the 8 bytes preceding this first item it can find the size of the array. Without you guaranteeing this, those 8 bytes could be anything.

Then, why not go all the way and create for[] (int i : array)?

There are two reasons:

  1. As mentioned, today an implementation can elide the size on a number of elements; with this new for[] syntax, it would no longer be possible on a per-type basis.
  2. It's not worth it.

Let us be honest, new[] and delete[] are relics of an older time. They are incredibly awkward:

  • the number of elements has to be known in advance, and cannot be changed,
  • the elements must be default constructible, or otherwise C-ish,

and unsafe to use:

  • the number of elements is inaccessible to the user.

There is generally no reason to use new[] and delete[] in modern C++. Most of the times a std::vector should be preferred; in the few instances where the capacity is superfluous, a std::dynarray is still better (because it keeps track of the size).

Therefore, without a valid reason to keep using these statements, there is no motivation to include new semantic constructs specifically dedicated to handling them.

And should anyone be motivated enough to make such a proposal:

  • the inhibition of the current optimization, a violation of C++ philosophy of "You don't pay for what you don't use", would likely be held against them,
  • the inclusion of new syntax, when modern C++ proposals have gone to great lengths to avoid it as much as possible (to the point of having a library defined std::variant), would also likely be held against them.

I recommend that you simply use std::vector.

range-based for loops in c++

There is nothing strange or unsafe about that usage. The array's size is known at compile time, so it can be used in the loop. This is an example of a template function that allows you to find out the length of an array:

template< class T, std::size_t N >
std::size_t length( const T (&)[N] )
{
return N;
}

Foo f[5];
std::cout << length(f) << "\n";

This should make it clear that you cannot use this technique, or range based loops, on dynamically sized C-style arrays.

Of course, if you have range based loops then you should also have std::array (if not, you can probably get ti from std::tr1 or boost), so you can avoid the C-style array entirely:

extern std::array<float, 100> bunch;

for (auto &f : bunch) {
f += someNumber;
}


Related Topics



Leave a reply



Submit