c++arraysranged-loops

Error with ranged for inside function


I'm having a little bit of trouble with the ranged for in C++. I'm trying to used it to display the element on and int array (int[]) and it works completely fine when I do that on the main function, like in:

int main(int argc, char const *argv[]) {

  int v[] = {3, 4, 6, 9, 2, 1};

  for (auto a : v) {
      std::cout << a << " ";
  }
  std::cout << std::endl;

  return 0;
}

I get my desired and expected output which is:

3 4 6 9 2 1

But things get a little weird when I try to use the ranged for inside a function, as an example I'm having a problem with this code:

void printList(int *v);

int main(int argc, char const *argv[]) {

  int v[] = {3, 4, 6, 9, 2, 1};

  printList(v);

  return 0;
}

void printList(int *v) {
  for (auto a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

Which for me is the same as I was doing inside of main, and also using the normal for works completely fine. The weird error is as follows:

p4.cpp: In function ‘void printList(int*)’:
p4.cpp:15:17: error: ‘begin’ was not declared in this scope
   for (auto a : v) {
                 ^
p4.cpp:15:17: note: suggested alternative:
In file included from /usr/include/c++/5/string:51:0,
                 from /usr/include/c++/5/bits/locale_classes.h:40,
                 from /usr/include/c++/5/bits/ios_base.h:41,
                 from /usr/include/c++/5/ios:42,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from p4.cpp:1:
/usr/include/c++/5/bits/range_access.h:105:37: note:   ‘std::begin’
   template<typename _Tp> const _Tp* begin(const valarray<_Tp>&);
                                     ^
p4.cpp:15:17: error: ‘end’ was not declared in this scope
   for (auto a : v) {
                 ^
p4.cpp:15:17: note: suggested alternative:
In file included from /usr/include/c++/5/string:51:0,
                 from /usr/include/c++/5/bits/locale_classes.h:40,
                 from /usr/include/c++/5/bits/ios_base.h:41,
                 from /usr/include/c++/5/ios:42,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from p4.cpp:1:
/usr/include/c++/5/bits/range_access.h:107:37: note:   ‘std::end’
   template<typename _Tp> const _Tp* end(const valarray<_Tp>&);
                                     ^

I would like to know why this error happens, the reason I think that this be may happening is, since I'm the pointer representation of the array some information is lost, but why this information is lost I don't know. Does someone know that in depth? Also I've looked for this alternative solution:

template <std::size_t len>
void printList(int (&v)[len]) {
  for (int a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

Which works fine but if I use something like that:

template <std::size_t len>
void printList(int (&v)[len]);

int main(int argc, char const *argv[]) {
           .........
}

void printList(int (&v)[len]) {
  for (int a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

I get the error:

p4.cpp:15:25: error: ‘len’ was not declared in this scope
 void printList(int (&v)[len]) {
                         ^
p4.cpp: In function ‘void printList(...)’:
p4.cpp:16:16: error: ‘v’ was not declared in this scope
   for (int a : v) {

Why dos that happen? Is there any simple solution without using the template format? Is there a way that I can use argument as way to pass the array and the implicit size information?


Solution

  • Range based for-loops are inherently nothing but syntactical sugar, i.e. retrieved from cppreference

    for ( range_declaration : range_expression ) loop_statement (until C++20)

    for ( init-statement(optional) range_declaration : range_expression ) loop_statement (since C++20)

    is functionally equivalent to the following:

    {
        auto && __range = range_expression ;
        for (auto __begin = begin_expr, __end = end_expr;
                __begin != __end; ++__begin) {
            range_declaration = *__begin;
            loop_statement
        }
    }
    

    or, if you use c++17 or later, which effectively allows different types for __begin and __end.

    {
        init-statement // only since C++20
        auto && __range = range_expression ;
        auto __begin = begin_expr ;
        auto __end = end_expr ;
        for ( ; __begin != __end; ++__begin) {
            range_declaration = *__begin;
            loop_statement
        }
    }
    

    where begin_expr and end_expr are formed as follows

    1. 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)

    2. If range_expression is an expression of a class type C that has a member named begin and/or a member named end (regardless of the type or accessibility of such member), then begin_expr is __range.begin() and end_expr is __range.end();

    3. Otherwise, begin_expr is begin(__range) and end_expr is end(__range), which are found via argument-dependent lookup (non-ADL lookup is not performed).


    Let's see how this applies to your case:

    In the first case v is certainly an expression of array type or to be exact of type int(&)[6], so we use case (1) where __bound = 6 etc (omitting full deducted replacements for brevity)

    In the second case, when you have a function, v has the type int* and since it is not an array type nor does a pointer have members we default to case (3) which uses ADL to determine the function to call for begin(__range) which does not yield a result for pointer types, hence the compiler complains with error: ‘begin’ was not declared in this scope.

    In the third case, you have made an error when trying to define the function template printList. You have to preserve the template<...> part that you included in the declaration, otherwise it's just a definition for a function. That's why the compiler tells you error: ‘len’ was not declared in this scope. Correct and working code is thus

    template <std::size_t len>
    void printList(int (&v)[len]);
    
    int main(int argc, char const *argv[]) {
      int v[] = {3, 4, 6, 9, 2, 1};
      printList(v);
      return 0;
    }
    
    template <std::size_t len>
    void printList(int (&v)[len]) {
      for (int a : v) {
        std::cout << a << " ";
      }
      std::cout << std::endl;
    }
    

    Other answers are already proposing using a different container type such as std::array<int, 6> raising a valid point. Definitely have a look at them, especially with brace-initialization you can upgrade to them at almost no cost.