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 char
s:
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:
- 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. - 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
Write Applications in C or C++ for Android
C++11 Lambda Implementation and Memory Model
Mingw .Exe Requires a Few Gcc Dll's Regardless of the Code
Search a Vector of Objects by Object Attribute
C#-Like Properties in Native C++
Pointer to Array with Const Qualifier in C & C++
Operator Overloading in C++ as Int + Obj
How to Avoid Repeating the Class Name in the Implementation File
Invalid Conversion from 'Const Char*' to 'Char'
Does Static Constexpr Variable Inside a Function Make Sense
What Is the Idiomatic Way in Cmake to Add the -Fpic Compiler Option
Linking Different Libraries for Debug and Release Builds in Cmake on Windows
How to Create a Pause/Wait Function Using Qt
Performance Issue for Vector::Size() in a Loop in C++
Is the Backslash Acceptable in C and C++ #Include Directives