0

I know I can have any type in for loop to iterate over:

#include <fstream>
#include <iostream>

using namespace std;

int main()
{
    int ar[] ={ 1, 2, 3 };
    for (int i:ar)
    {
        cout << i << endl;
    }
}

But I could not have an pointer type:

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

int main(int argc, char const *argv[])
{
    for (char *p:argv) //or better char const *p
    // using auto keyword expands to error-type in vscode
    {
        ifstream in(p);
    }

    return 0;
}

will give:

error: ‘begin’ was not declared in this scope
     for (char *p:argv)
error: ‘end’ was not declared in this scope
     for (char *p:argv)
                  ^~~~

So I am assuming, the syntax of c++ for loop (auto var : vector/array) is not the same as c-style, old-fashion loop for(int i=0; i<size; i++)? Because I am required (in case of using c++ style for loop) to provided a structure with valid iterator (thus the errors, looking for begin() and end() iterators. But then why did the first case works? The first case, an array with ints, is also a structure without any kind of iterators, but old pointers (to access). So why the first is okay, but the second isn't?

milanHrabos
  • 2,010
  • 3
  • 11
  • 45
  • If it's an array it can know the length so it knows how long the loop goes. If it's a pointer it has no idea where the array ends - it'd have to loop through all of RAM starting at the address given by `argv`. – Oliver Dain Aug 05 '20 at 22:38
  • @OliverDain that is, of course true, but that is not the question. I see no reference about the c++-for-loop to state, it needs only structure with iterators. And again, why then the first int-array case compiled? It also has no information about the size so as you are saying, the for loop would have to go through all RAM, but it compiled. So your statement is not true – milanHrabos Aug 05 '20 at 22:42
  • I think this question has been answered multiple times on SO, for example here: https://stackoverflow.com/questions/15904896/range-based-for-loop-on-a-dynamic-array – Bert Aug 05 '20 at 22:44
  • @Bert The question is similar, but the answers there don't apply well, you can't replace `argv` with a `std::vector` or other container. – Barmar Aug 05 '20 at 22:46
  • Although [this](https://stackoverflow.com/questions/6361606/save-argv-to-vector-or-string) could be useful. – Barmar Aug 05 '20 at 22:47

1 Answers1

1

The difference in those samples is hidden inside the rules of passing parameters to functions.

In the first example, when you write int ar[] = {1, 2, 3};, the type of ar is int[3] - "an array of 3 ints". std::begin is defined for arrays, so the code compiles and works.

In the second example, char const *argv[] is actually the same as char const **argv, because in function parameters, you cannot pass an array by value, and the syntax [] is compiled exactly as if you would have used *. Obviously, there is no std::begin for pointers, so the code does not work.

To iterate over the arguments, you would have to use an ordinary for loop. For example,

for (int i = 0; i < argc; ++i) {
  char const* arg = argv[i];
};

Edit: Just to clarify - to use a range-based for loop, std::begin(a) and std::end(a) should be callable and return an iterator. (not entirely true since C++17 - std::end could return anything that is comparable with the iterator, this is called a sentinel in the C++20 working draft)

In case of an array with known bound (such as int[3]), std::begin returns a pointer to the first element, and std::end returns a pointer past-the-end. An iterator in C++ does not have to be a derived class of some special base, it just has to have the right semantics. Pointers are also iterators.

shananton
  • 508
  • 2
  • 12
  • Is it only for `c++`? the array-decaying to pointer, or does `c` handles it as well the same? I know I cannot pass an array by value – milanHrabos Aug 05 '20 at 22:45
  • @milanHrabos This is something C++ copied from C. – Barmar Aug 05 '20 at 22:48
  • @Barmar as far as I know, arrays as well as struct and local vairables are all stored in stack anyway. The only way it would *not* be stored in stack is case of pointer allocated by `malloc` (and thus in `heap`) and assign to that array. – milanHrabos Aug 05 '20 at 22:54
  • @milanHrabos Where the array is stored is irrelevant to this. If you declare a global array it's not stored in the stack. The `argv` array is not in the stack. – Barmar Aug 05 '20 at 22:56
  • @Barmar it is. It is actually pushed to stack before call to `main` (arg_n-1, arg_n-2, ... arg2, arg1, ret, call main, rbp, ...). Where other would you place arguments to main? or any other function? There are just 2 options - stack, and registers. The stack is more preferable (at least for x86) – milanHrabos Aug 05 '20 at 23:01
  • The argument to `main()` is a pointer, that pointer is in the stack. I'm talking about where the array it points to is located. That's set up by the operating system, and it could be anywhere in memory. – Barmar Aug 05 '20 at 23:03
  • @Barmar when array is defined globaly, then it is read-only (in `text` segmet). Ant to your second statement, `it could be anywhere in memory.` No. It is in specific place - your stack or register. In my architecture `x64`, it is in `%rsi` (`argv`) since that is the first register used, defined by ABI, and the second `%rdi` holds `argc`as second argument before call the main. You have to get rid of thoughts like a pointer can be *anywhere* in memory. [1] – milanHrabos Aug 05 '20 at 23:11
  • [2] It could in very old architectures, but in modern ones with virtual memory scheme, there are predefined address where process-allocated data will be. It is just not *random* – milanHrabos Aug 05 '20 at 23:11
  • @milanHrabros by the way, it's good to be careful when assuming how c++ does something and even better to check what the standard has to say. Such assumptions can lead to undefined behavior and [very surprising results with optimization enabled](https://en.cppreference.com/w/cpp/language/ub#UB_and_optimization) (my favorite is the signed integer overflow :^) ) – shananton Aug 05 '20 at 23:24
  • @milanHrabos, No, you're free to modify global variables unless you attach `const`. That is, it's entirely legal to do `int arr[3];` at global scope and `arr[0] = 1;` later. – chris Aug 05 '20 at 23:57