56

In the below example, i has function scope. But it seems that I cannot use i in the second for loop. Why does for (i : v1) not work, but for (int i : v1) works?

#include<iostream>
#include<string>
#include<vector>

int main()
{
    std::vector<int> v1;
    int i;
    while(std::cin>>i)
    {
        v1.push_back(i);
    }

    for(i : v1) //for (int i:v1) works
        std::cout<<i<<"\t";
    cout<<std::endl;
    return 0;
}
13
  • 23
    @NeilButterworth: actually, I can see how it can be surprising that range-based for requires a declaration rather than merely naming a variable. ... and I'd be surprised if typical textbooks cover the rational for this oddity. Commented Dec 16, 2017 at 20:28
  • 4
    Answer's because that's the way the language works, but I hope this question doesn't get locked. I bet the answer to why the language works this way is quite interesting. Commented Dec 16, 2017 at 20:37
  • 10
    @NeilButterworth - I imagine it would be the same place where they see int i; for(i = 0; i < 10; ++i) used. Not such a big leap to try that on a range-based for as well. Commented Dec 16, 2017 at 20:43
  • 3
    @NeilButterworth: actually, I don't think it is something you'd see somewhere. It could be seen as a logical use of range-based for and it is actually illogical that it is not supported! There is a reason but I think this reason may even be somewhat feeble... Commented Dec 16, 2017 at 20:44
  • 2
    @NeilButterworth - Syntax is immaterial. Your view may be skewed from your years of experience. To a novice, it may make perfect sense. Commented Dec 16, 2017 at 20:44

4 Answers 4

41

It's a syntactical issue that a range-based for loop requires a declaration of a named variable, i.e. it requires a type specifier (cf, for example, cppreference.com):

for ( range_declaration : range_expression ) loop_statement

range_declaration - a declaration of a named variable, whose type is the type of the element of the sequence represented by range_expression, or a reference to that type. Often uses the auto specifier for automatic type deduction

Actually I don't know why your question got downvoted; I find your assumption quite OK; just the C++ syntax has decided to define it in another way.

Sign up to request clarification or add additional context in comments.

Comments

35

The range-based for is specifically intended to replace loops akin to the following (this is a somewhat simplistic case; range-based for, especially the C++17 version, is more general than the example):

for (auto it = range.begin(), end = range.end(); it != end; ++it) {
   use(*it);
}

In the majority of cases won't use the values at the different locations but will rather use the element at the location itself:

  1. When mutating the elements in the sequence a value doesn't really help.
  2. In most cases copying the values is expensive and keeping a reference is more effective.
  3. There are even cases where objects can't be copied to start with.

As a result the designers of range-based for decided that references absolutely have to be supported. At the same time, it was intended to use a reasonably simplistic rewrite-rule for a range-based for. The rule which is codified in the standard is this:

for (<range-decl>: <range>) { <body> }

is equivalent to

{
    auto&& range = <range>;        // keep the range alive!
    auto   it    = begin(range);   // actually, reality is bit more complicated
    auto   end   = end(range);     // actually, reality is a bit more complicated
    for (; it != end; ++it) {
        <range-decl> = *it;        // this is the rewrite causing your issue
        <body>
    }
}

In particular, the implication is that <range-decl> is a declaration rather than just naming a variable. The reason for this requirement is that typically the entity used in front of the : is a reference. However, references cannot be rebound. However, in each iteration of a loop a new reference can be used.

In principle the rewrite rule could work with using assignments if the <range-decl> isn’t a declaration but rather an lvalue. That would yield its own share of odd behaviors:

  • There would be a difference between for (T const& x: range) and T const& x = 0; for (x: range): the former works while the latter is an error.
  • If the lvalue is a reference to an object located somewhere (T& x = get_reference(); for (x: range) {...}) the loop would automatically assign all the values in a range to an object located somewhere. Normally the objects are either located on the stack or in the source range (when the variable is declared as a reference).

It was consider more reasonable to only allow initialisations than supporting initialisation or assignments depending on how the variable is declared. Looking at the revision history of the proposals (N2930 and predecessors) doesn’t yield a discussion but I vaguely recall that ths point was discussed.

2 Comments

This doesn't explain why <range-decl> couldn't be optionally a declaration or an lvalue. If we go by simple text replacement rule, this wouldn't be a problem.
@Ruslan: fair comment. I have tried to address this point, too. I didn’t find a corresponding discussion in the papers (but I also didin’t look too closely) and I don’t think there were records at meetings kept at that point in time.
1

When you are using range-based loops you need a declaration after opening parentheses, not only a variable. The correct syntax is:

 for ( declaration : range ) statement;

You can see this link for more information.

In your example: when you declare i before your while loop then you can use it in all of the main function and the scope of it is the main function. You can use it in that for body. When you are using the i variable in your for range then you aren't declaring it, because you already declared it above, so it will give you an error and it's not correct with C++ syntax.

But when you type int before the i in your for parenthesis then you are declaring another variable with the name of i, but only for your for loop and then it is OK with C++ syntax.

Comments

0

The rationale is most likely because this would invoke copy-assignments on the variable, which would become a potential source of great inefficiency and in practice almost never be the intention... if the type supports copy-assignment at all.
So they probably figured it's best to prohibit this.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.