9

Why does this piece of code:

void printarray(int array[]) {
    for (int x: array) {
        std::cout << x << std::endl;
    }
}

Generate this compile-time error?

error: 'begin' was not declared in this scope
    for (int x: array) {

What am I getting wrong about range-based for loops?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
user6245072
  • 2,051
  • 21
  • 34
  • 6
    In this context `int array[]` really isn't an array, but equivalent to `int* array`. Inside the function we don't know the size of the parameter. See [Sizeof an array in the C programming language](http://stackoverflow.com/questions/1975128/sizeof-an-array-in-the-c-programming-language) – Bo Persson Jun 14 '16 at 12:41
  • use the easiest way: void printarray( std::vector array) {... – Marian Munteanu Jun 14 '16 at 13:46
  • @Bo Persson Could inlining the function help? – user6245072 Jun 14 '16 at 14:50
  • @user - No, inlining doesn't change the way a function works. Only possibly the execution speed. – Bo Persson Jun 14 '16 at 15:11

3 Answers3

13

Your problem is that array is not actually an array. When you write

void printarray(int array[])

It is the same as

void printarray(int* array)

Since you cannot tell how many elements a pointer points to without an additional size parameter you cannot use it with a ranged based for loop.

What you need to do is pass the array by reference so that the array doers not decay into a pointer. If you know the exact size of the array then you can use

void printarray(int (&array)[size_you_want_here])

If you want to make the function more generic so it can work with different size arrays then you can use a template like

template<std::size_t N>
void printarray(int (&array)[N])

I both of the above cases you now have an actual array instead of a pointer so you can use it with a ranged based for loop.

Also note we can make the function completely generic using

template<typename T, std::size_t N>
void printarray(T (&array)[N]) {
    for (auto&& x : array) {
        std::cout << x << "\n";
    }
}

You will also notice I change std::endl to "\n". Normally you do not want to use endl as it explicitly calls flush() on the stream. Generally "\n" is all you need and at the end if the output is still not flushed then you can call flush() yourself.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
5

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;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
1

The parameter printarray receives is actually an int*, the range for wouldn't know where to stop. In this case you will need to send the length as a parameter and do a regular for

gbehar
  • 1,229
  • 10
  • 10