Reputation: 440
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?
Upvotes: 14
Views: 865
Reputation: 5044
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
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)
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();
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.
Upvotes: 11
Reputation: 344
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;
}
Upvotes: 4
Reputation: 122133
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
Upvotes: 2
Reputation: 14589
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
Upvotes: 1
Reputation: 37806
"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;
}
Upvotes: 6
Reputation: 44828
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.
Upvotes: 4
Reputation: 19232
The array inside main
has a known size.
Once passed to the printList
function it has decayed to a pointer to int
s, hence the errors you get.
Upvotes: 3
Reputation: 4493
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
Upvotes: 5