Error with ranged for inside function
Asked Answered
A

8

14

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?

Antonetteantoni answered 24/11, 2016 at 16:35 Comment(6)
I think the problem is that in main you're dealing with an array, while in function with a pointer. In array you know when to stop, but with pointer you have idea. Hence, There are begin and end functions for array, but not for a pointer. Note that you can't pass an array as a parameter, it will be automatically converted to a pointerEleven
@ВиталикБушаев does this different representation of the array cause some lost of information that the ranged for can't handle ?Antonetteantoni
its not a different representation, its a different valueLayne
A few other questions talk round this subject e.g.: https://mcmap.net/q/827687/-array-length-with-pointers You would have to make the size information explicit, or use a different data typeScone
the reason you have an compile error in the second version is the function definition also has to have <template std::size_t len>. You forward declare the template but never define it, and (also) try to overload with an ill-formed function definitionLayne
Why is it strange? In the first (working) code v is an array, and in the second (not working) code it is a pointer. Arrays and pointers are not the same thing.Retard
P
11

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.

Primordium answered 24/11, 2016 at 23:19 Comment(0)
P
6

"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."

Yes. Arrays decay into pointers quite easily, and a pointer has no knowledge of array length. The range based for loop is required to evaluate begin() and end() from the datatype.
My suggestion is to avoid C style arrays, and use std::array instead:

#include <iostream>
#include <array>

void printList(std::array<int,6> const& v);

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

  std::array<int,6> v{{3, 4, 6, 9, 2, 1}};
  printList(v);

  return 0;
}

void printList(std::array<int,6> const& v) {
  for (auto a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}
Puissance answered 24/11, 2016 at 16:44 Comment(3)
Thank! Indeed this solution is really good! Can you just explain why the double bracket when defining v ? Why can't you just do something like std::array< int, 6 > v = {3, 4, 6, 9, 2, 1} ?Antonetteantoni
@Antonetteantoni Look at #13905700Hondo
@Antonetteantoni The double braces are required in C++11 but not C++14. You can avoid the double braces entirely through = {1,2,3,...};Puissance
F
5

There is big difference between array and pointer.

You can iterate array using range-based for, since size of array is known on compile-time. However, what are you passing to function - is the pointer to first element of array. Size is not known on this step, that's why range-based for is failing.

In your second example with template, the catch is, you forgot template <std::size_t len> in definition of printList, so you have 2 different functions, templated and non-templated one, which is called.

In this exact case - I would recommend to use more modern std::array

Fineman answered 24/11, 2016 at 16:41 Comment(1)
Thanks! I just realize that the template is include of the signature, that was causing two function to exist! Thanks!Antonetteantoni
R
4

Such for loops use begin and end member functions in order to determine where's the start and where's the end of the sequence.

For example, std::vector does have these functions and thus supports that kind of iteration. Pointers are, in fact, mere integers that represent memory addresses and don't have these functions, which makes it impossible to iterate over a pointer (which doesn't make much sense on its own) in this manner.

You could do the iteration like this instead:

void printList(int *begin, int *end) {
    for(; begin < end; ++begin)
        std::cout << *begin;
    std::cout << std::endl;
}

This works inside main in your case because arrays do have begin and end as the size of the array is known. However, passing the array to a function makes it decay to a pointer.

Redundant answered 24/11, 2016 at 16:40 Comment(1)
I see your point, but why does that work inside of main function ? Does the normal array representtion (int[]) hold such information ?Antonetteantoni
H
4

When passing a array to a function, it decays to a pointer, thus it loses the ability to be used with std::begin and std::end. A modern way to do what you want is to use std::array (you shouldn't use C-style arrays in C++ if possible):

#include <iostream>
#include <array>

template <typename T, size_t ArraySize>
void printList(const std::array<T, ArraySize>& v)
{
    for (auto a : v) {
        std::cout << a << " ";
    }
    std::cout << std::endl;
}

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

  std::array<int,6> v = {3, 4, 6, 9, 2, 1};

  printList(v);

  return 0;
}    
Hondo answered 24/11, 2016 at 16:46 Comment(0)
S
3

The array inside main has a known size.

Once passed to the printList function it has decayed to a pointer to ints, hence the errors you get.

Scone answered 24/11, 2016 at 16:42 Comment(0)
D
2

You can pass an array of a fixed size to a function:

void printList(int (&v)[6]) {        // pass by reference
    for (auto& a : v) {              // use reference to avoid making a copy
        std::cout << a << " ";
    }
}

However, of course we dont want to write a function that works only for arrays with a certain size. This is where it makes sense to use a template:

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

The nice thing is that when you call the function you dont even notice that it is a template, because the compiler can deduce the template parameter (it knows the size of course):

int main() {
    int v[] = {3, 4, 6, 9, 2, 1};
    printList(v); 
}

prints:

3 4 6 9 2 1
Dragster answered 24/11, 2016 at 16:55 Comment(0)
B
1
int v[] = {3, 4, 6, 9, 2, 1};

got type of int[6], while int *v.. well its type is int *.You can use pointer to int to access array, but it doesn't carry information about size of that array. You can pass array like this, but you'll limit yourself by size of array:

void foo(int (&p)[6])

or make template:

template <std::size_t size> void foo( int (&p)[size] )

If for some reason you can't use automic for() loops (e.g. for portability to platforms where C++11\14 support is dubious) you need use either std::array\std::vector or pass pointer and size of array

Bridlewise answered 24/11, 2016 at 16:52 Comment(2)
ups, didnt see that you made almost the exact same suggestion just a bit shorter and fasterDragster
we wrote it at same time XDBridlewise

© 2022 - 2024 — McMap. All rights reserved.