0

The foreach loop type doesn't work if the array being iterated has come from a parameter and was declared in another scope.

// C++ program to demonstrate use of foreach 

#include <iostream> 
using namespace std; 
void display(int arr[])
{
    for (int x : arr) 
        cout << x << endl; 
}
int main() 
{ 
    int arr[] = { 10, 20, 30, 40 }; 
    display(arr);
} 

Here's what the error looks like:

Here's what the error looks like

What are begin and end? What is happening behind the scenes that gives this error? Is the for loop implicitly changed to an iterator declaration before running? is this because C style arrays are not container objects and therefore don't have begin and end? If this is true then why does this code work:

#include <iostream> 
using namespace std;
int main() 
{ 
    int arr[] = { 10, 20, 30, 40 }; 
    for (int x : arr) 
        cout << x << endl;
} 

Is there some information decaying happening when passed as an argument?

Pat. ANDRIA
  • 2,330
  • 1
  • 13
  • 27
Neon
  • 41
  • 5
  • 3
    From [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask): "_**DO NOT post images of code, data, error messages, etc.** - copy or type the text into the question_" – Ted Lyngmo Mar 12 '21 at 09:50
  • 2
    https://stackoverflow.com/questions/1461432/what-is-array-to-pointer-decay – Ted Lyngmo Mar 12 '21 at 09:52
  • 3
    When you declare `int arr[]` as an **argument** you don't actually declare an array variable, you declare a *pointer* variable. The compiler will treat it as `int* arr`. – Some programmer dude Mar 12 '21 at 09:52
  • 1
    And the call `display(arr)` will be equal to `display(&arr[0])`. – Some programmer dude Mar 12 '21 at 10:02
  • 1
    [You could always do this](https://pastebin.com/ceSRB367), but note you cannot use that construct interchangeably with non-native arrays; you would have to provide an alternative overload that accepts a second size argument. – WhozCraig Mar 12 '21 at 10:25

4 Answers4

3

That for loop is called a range-based for loop. The compiler is transforming that code into something else. You can see all the details here https://en.cppreference.com/w/cpp/language/range-for (it's different for C++11, C++17, and C++20).

However, in principle, your code becomes this:

{
  auto && __range = arr;
  auto __begin = begin_expr;
  auto __end = end_expr;
  for ( ; __begin != __end; ++__begin) {
    x = *__begin;
    cout << x << endl;
  }
}

The thing here is that begin_expr and end_expr can be different things. If arr was an array, like int arr[3], __range would be __range + 3. If arr was a class with members begin() and end() it would be __range.begin(). Otherwise, it would be begin(__range).

This last case is were your code is landing, because arr is not an array but a pointer to int (int [] arr means int* arr). And there is no begin() and end() for int*.

It works here, because in this example, arr is an array of 4 integers:

int arr[] = { 10, 20, 30, 40 }; 
for (int x : arr) 
   cout << x << endl; 

There are different ways to do this. This is how I'd prefer to do it myself:

template <typename T>
void display(std::vector<T> const & arr)
{
    for (int x : arr) 
        cout << x << endl; 
}

int main() 
{ 
    std::vector<int> arr { 10, 20, 30, 40 };
    display(arr);
}

You can check https://cppinsights.io/ to see how the compiler transforms your code (to some degree). For instance, your code becomes this (mind that it does not work if the code does not compile, so I had to comment out the loop):

#include <iostream> 
using namespace std; 

void display(int * arr)  // <-- notice int*
{
}


int main()
{
  int arr[4] = {10, 20, 30, 40};  // <-- notice int[4]
  display(arr);
}

And if you take this code:

int main() 
{ 
    int arr[] = { 10, 20, 30, 40 }; 
    for (int x : arr) 
       cout << x << endl; 
}

the result is this:

int main()
{
  int arr[4] = {10, 20, 30, 40};
  {
    int (&__range1)[4] = arr;
    int * __begin1 = __range1;
    int * __end1 = __range1 + 4L;
    for(; __begin1 != __end1; ++__begin1) 
    {
      int x = *__begin1;
      std::cout.operator<<(x).operator<<(std::endl);
    }
    
  }
}
Marius Bancila
  • 16,053
  • 9
  • 49
  • 91
1

When you give a C-style array to a function, you need to give its size as well because the function is not aware of the array's size. The array argument int x[] is considered as a pointer on the array's first element (thus like an int* x). In consequence, the function can't tell how big the array is as it only has the pointer (the address of the first element). You need to specify how many int (or other types) from the provided adress the function shall read to obtain valid elements.

#include <iostream>
using namespace std;

void display(int arr[], int nsize)
{
    int i=0;
    while (i < nsize){
        cout << *arr << endl;
        // you move the pointer to the next element to be read
        arr++;
        i++;
    }
}

int main()
{
    int arr[] = { 10, 20, 30, 40 };
    int n= sizeof(arr)/sizeof(int);
    display(arr, n);
}

Pat. ANDRIA
  • 2,330
  • 1
  • 13
  • 27
1

The first piece of code doesn't work, because

void display(int arr[])

is actually equivalent to

void display(int *arr)

and you can't use a single pointer for iteration, because it doesn't have any length information. See also What is array to pointer decay.


The second piece of code works because the range-expression in a range-based for must be

any expression that represents a suitable sequence (either an array or an object for which begin and end member functions or free functions are defined, see below) or a braced-init-list.

Source

So instead of the non-existing member functions arr.begin() and arr.end() it can use the free functions std::begin() and std::end() which are overloaded for arrays.

Lukas-T
  • 11,133
  • 3
  • 20
  • 30
1

first of all, let me suggest you to edit your question as @Ted mentions in his comment.

I must admit the error message is not quite intuitive. The appearance of missing begin and end stems from the definition of range-based for loop. As you can see, you're passing an array of unknown size as the range_expression, right? Linked page has further to say about this case:

If range_expression is an expression of array type, then begin_expr is __range and end_expr is (__range + __bound), where __bound is the number of elements in the array (if the array has unknown size or is of an incomplete type, the program is ill-formed)

Your second code snippet actually uses an array with known size, since its use is in the same scope as its initialization. Array initialization states:

When initializing an array of unknown size, the largest subscript for which an initializer is specified determines the size of the array being declared.

Let me offer you a standard library template for better grip on arrays. It is .. err .. std::array :). What you get is basically value semantics and consistence with other types:

  • accepting argument by value makes a copy of the whole content,
  • accepting argument by (const) reference has a familiar syntax,
  • you don't implicitly lose the size information when passing an array object.

So your code will happen to look like this:

#include <array>
#include <iostream> 
using namespace std; 
void display(const array<int>& arr)
{
    for (int x : arr) 
        cout << x << endl; 
}
int main() 
{ 
    std::array arr{ 10, 20, 30, 40 }; 
    display(arr);
} 
neonxc
  • 802
  • 9
  • 21