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 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.)
Unrecognized range-based for loop?
When an array is passed by value as an argument of a function it implicitly is converted to pointer to its first element. Also parameters that declare arrays are adjusted to pointers.
So for example these function declarations
void printarray( int array[100] );
void printarray( int array[10] );
void printarray( int array[] );
declares the same one function and equivalent to
void printarray( int *array );
So you need to pass also the size of the array to the function as for example
void printarray( const int array[]. size_t n )
{
for ( size_t i = 0; i < n; i++ )
{
std::cout << a[i] << std::endl;
}
}
You could write a template function specially for arrays passed by reference as for example
template <size_t N>
void printarray( const int ( &array )[N] )
{
for ( int x : array)
{
std::cout << x << std::endl;
}
}
or
template <typename T, size_t N>
void printarray( const T ( &array )[N] )
{
for ( auto x : array)
{
std::cout << x << std::endl;
}
}
However compared with the previous function it has a drawback because arrays of different sizes are different types and the compiler will generate as many functions from the template as many arrays of different types you are going to use with the function.
And you could use standard algorithms as for example std::copy
or std::for_each
to output an array.
For example
#include <iostream>
#include <algorithm>
#include <iterator>
int main()
{
int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
std::copy( std::begin( array ), std::end( array ),
std::ostream_iterator<int>( std::cout, "\n" ) );
return 0;
}
Another approach is to use standard class std::array
that has appropriate member functions begin
and end
that are used by the range based for statement. For example
#include <iostream>
#include <array>
const size_t N = 10;
void printarray( const std::array<int, N> &array )
{
for ( int x : array ) std::cout << x << std::endl;
}
int main()
{
std::array<int, N> array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printarray( array );
return 0;
}
But in this case you need also to write a template function if you are going to output objects of class std::array
with different numbers or types of elements.
For example
#include <iostream>
#include <array>
template <typename T, size_t N>
void printarray( const std::array<T, N> &array )
{
for ( auto x : array ) std::cout << x << std::endl;
}
int main()
{
std::array<int, 10> array1 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printarray( array1 );
std::array<char, 10> array2 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' };
printarray( array2 );
return 0;
}
C++ basics: ranged based for-loop and passing C-style arrays to functions
The range-based for loop works for all types that can be used with std::begin
and std::end
.
Arrays and pointers are not the same. An array has a fixed size, a pointer does not. Hence std::begin
and std::end
work for arrays - but not for pointers. That also explains why the range-based for loop works for one, but not the other.
Arrays may also decay to pointers. This happens, for example, when passed to functions that take a pointer parameter. Or, when passed to functions which take an indeterminate-sized array parameter (which is effectively the same as a pointer parameter). When that happens, size information is lost again.
So it depends on how you define the function. The first function takes an array, the second a pointer. That's why the first preserves size information and the loop works.
However, it limits what the function can take. The second function can take an int b[2]
, while the first cannot.
Can we iterate through an array passed to a function using for-each loop?
A pointer is not an array. If you pass a pointer to the first element of an array to a function then its no longer an array, but a pointer.
You can use a range based loop when you pass the array by reference:
#include <iostream>
template <size_t N>
void foo(int (&x)[N]) {
for (int i : x) std::cout << i << " ";
}
int main() {
int x[] = {1,2,3};
foo(x);
}
Output:
1 2 3
This works, because the range based loop uses std::begin(x)
and std::end(x)
to get iterators to the begin and end of the array. Pointers don't have a begin or end.
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.
Related Topics
What Does the Standard Say About How Calling Clear on a Vector Changes the Capacity
Does the 'Offsetof' MACro from <Stddef.H> Invoke Undefined Behaviour
Is It Safe to Push_Back an Element from the Same Vector
When Is Overloading Pass by Reference (L-Value and R-Value) Preferred to Pass-By-Value
Number of Combinations (N Choose R) in C++
No Match for 'Operator<<' in 'Std::Operator
Os Specific Instructions in Cmake: How To
Std::Fstream Buffering VS Manual Buffering (Why 10X Gain with Manual Buffering)
Moving Elements from Std::Vector to Another One
Overloading Operator<<: Cannot Bind Lvalue to 'Std::Basic_Ostream<Char>&&'
C/C++: Optimization of Pointers to String Constants
How to Disassemble a Binary Executable in Linux to Get the Assembly Code
C++11: How to Alias a Function
Why C++ Copy Constructor Must Use Const Object
Std::Sort Does Not Always Call Std::Swap
Visual C++: #Include Files from Other Projects in the Same Solution