2

The for loop in main compiles fine but in the print function it doesn't, with the diagnostic

for(auto i:x) cout<<i<<" ";        

  -----------^std::end  
'end' was not declared in this scope; did you mean 'std::end'?

being issued. Here's the full code:

#include <iostream>
void print(int x[],int n)
{
   for(auto i:x) std::cout<<i<<" ";
}

int main()
{
    int arr[] = {1, 5, 2, 1, 4, 3, 1, 7, 2, 8, 9, 5};
    int n = sizeof(arr) / sizeof(int);
    for(auto i:arr)
        std::cout<<i<<" ";
    print(arr, n);
    return 0;
}
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • Don't forget namespace std – ElMaestroDeToMare Sep 28 '21 at 06:46
  • 5
    When declared as a function argument, `int x[]` is really `int* x`. That is, `x` is a pointer and can't be used with range-for loops. Either use `std::vector`, `std::array`, or make the function a template with the array size as template argument and pass the array by reference. Using `std::array` is probably the simplest. – Some programmer dude Sep 28 '21 at 06:46
  • The basic mistake is mixing C elements (in this case, `[]` arrays, but pointers are another "contestant") with C++. An unfortunate result of most students being taught C *first* instead of starting with idiomatic C++ right away. But a whooping +1 for the self-contained example code! – DevSolar Sep 28 '21 at 12:33

3 Answers3

2

The issue is that the array arr decays to a pointer when passed to the function print. The mechanism behind the range-based for loop is via the use of std::begin and std::end. That goes some way in explaining the compiler diagnostic: they don't work if applied to pointer types.

This pointer decay is the normal behavior when passing array parameters to functions, but is by no means universal. For example, std::begin and std::end obviate this with some template trickery (otherwise they'd be useless for array types), something which you can do too by writing

template<typename T>
void print(T&& x, int n)
{
   for(auto i:x) std::cout<<i<<" ";
}

instead.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
1

The plain old datatype int[] has no end-iterator, if you pass it to a function. In this case you yust pass a pointer to the first item. In order to use a range based for loop you have to use a data container, which has a begin- and end-iterator, such as std::vector

Here is your code in a more C++ way.

#include<vector>
#include<iostream>
    
void print(const std::vector<int> &x) {
    for(auto i:x) {
       std::cout<<i<<" ";
    }
}

int main() {
    const std::vector<int> arr {1, 5, 2, 1, 4, 3, 1, 7, 2, 8, 9, 5};
    for(auto i:arr) {
        std::cout << i << " ";
    }

    print(arr);
    return 0;
}
schorsch312
  • 5,553
  • 5
  • 28
  • 57
1

@schorsch312's answer is correct, but the suggestion to have the print() function take an std::vector is not the "C++ way": Using std::vector instead of a plain array in your main program is just fine, but when writing a function - I would recommend not to take a type more specific than what you actually need to perform the function's work, and specifically not allocate memory on the heap with a new vector.

For the case of you having a C array in your program, @Bathsheba's suggestion of a print() is better in my opinion, in that it will allow you to pass the array as-such, and thus iterate over it properly.

However - for pedagogical reasons, I would suggest you consider this version print():

template <std::size_t N>
void print(const int (&arr)[N]) {
    for(auto i : arr) {
       std::cout << i << " ";
    }
}

which illustrates how print() can take an array reference. You need the template when you don't know the array size apriori. (and this also works of course.)

Alternatively, you could use the standard library algorithm, std::for_each, like so:

#include <vector>
#include <iostream>
#include <algorithm>

int main()
{
    int arr[] {1, 5, 2, 1, 4, 3, 1, 7, 2, 8, 9, 5};
    for(auto i:arr) {
        std::cout << i << ' ';
    }
    std::cout << '\n';
    std::for_each(
        std::begin(arr), std::end(arr),
        [](auto i) { std::cout << i << ' '; }
    );
}

Note that when you use the ranged-for loop, what happens is basically the equivalent of the for_each invocation, or of a raw for loop over an iterator beginning at std::begin(arr) and ending at std::end(arr).

Yet another alternative - which does not require concerning yourself with iterator pairs - is to have a print() function taking a span (What is a "span" and when should I use one?). Here it makes some sense since a plain C array can be referred to by a span: It has contiguous element storage and a specific length. If you were to be using C++20, your span-using print function would look like this:

#include <span> 

void print(const std::span<int> &sp) {
    for(auto i : sp) {
       std::cout << i << ' ';
    }
}

and again you don't have to specify the length in your main function. C++14 doesn't have spans in the standard library, but the C++ Guidelines Support Library (see here or here) has it, as gsl::span - and it works in C++14.


Nitpicks:

  • There is no need to return 0 from main() - that happens automatically.
  • Please use spaces between #include directives and the angle-brackets (i.e. #include <foo>, not #include <foo>.
  • Please use spaces before and after << operators.
  • To print a space, you don't need a string; the single space character suffices, i.e. std::cout << ' ' rather than std::cout << " ".
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • Why are you showcasing all kinds of workarounds for C arrays instead of `std::array` if you are opposed to memory allocation? And OP is probably going to use those constructs in greater contexts as well, and `std::vector` *is* the go-to container (premature optimization and all that jazz)... – DevSolar Sep 30 '21 at 11:06
  • @DevSolar: 1. Because the question is about passing an array to a function. 2. `std::vector` is the go-to container - but not as a parameter to a function. Anyway, I'll clarify that in the answer body. – einpoklum Sep 30 '21 at 12:36