Search code examples
c++c++11for-loopauto

Why does C++ not allow multiple types in one auto statement?


The 2011 C++ standard introduced the new keyword auto, which can be used for defining variables instead of a type, i.e.

auto p=make_pair(1,2.5);                   // pair<int,double>
auto i=std::begin(c), end=std::end(c);     // decltype(std::begin(c))

In the second line, i and end are of the same type, referred to as auto. The standard does not allow

auto i=std::begin(container), e=std::end(container), x=*i;

when x would be of different type. My question: why does the standard not allow this last line? It could be allowed by interpreting auto not as representing some to-be-decuded type, but as indicating that the type of any variable declared auto shall be deduced from its assigned value. Is there any good reason for the C++11 standard to not follow this approach?

There is actually a use case for this, namely in the initialisation statement of for loops:

for(auto i=std::begin(c), end=std::end(c), x=*i;  i!=end;  ++i, x+=*i)
{ ... }

when the scope of the variables i, end, and x is limited to the for loop. AFAIK, this cannot be achieved in C++ unless those variables have a common type. Is this correct? (ugly tricks of putting all types inside a struct excluded)

There may also be use cases in some variadic template applications.


Solution

  • I think it's just a matter of consistency with non-auto declarations.

    This:

    auto n = 42, *p = &n;
    

    is equivalent to:

    int n = 42, *p = &n;
    

    where the types int and int* are derived from the initializers. In both cases, even though int and int* are different types, they're permitted to be in the same declaration because of their close syntactic relation. (By the "declaration follows use" rule that C and C++ declarations almost follow, you're defining both n and *p as being of type int.)

    It would have been possible to permit unrelated types in the same declaration:

    auto n = 42, x = 1.5;
    

    but the above would have to be equivalent to two separate declarations:

    int n = 42; double x = 1.5;
    

    I think the idea when adding auto was to make a minimal change to the language, permitting the type to be inferred from an initializer but not changing the kinds of declarations that are possible.

    Even without auto, you could define an int and an int* in a for loop header:

    for (int n = 42, *p = &n; expr1; expr2) { /* ... / }
    

    but you couldn't declare an int and a double together. The addition of auto simply didn't change that.

    Outside the context of a for loop, it's generally much better to use separate declarations anyway. Shoving a lot of different declarations into a for loop is arguably a bad idea in most cases. And for the (probably rare) cases where you want a lot of declarations, you can just put them above the loop, something like this:

    auto i=std::begin(c), end=std::end(c),
    for( x=*i;  i!=end;  ++i, x+=*i) {
        // ...
    }
    

    adding another set of { } around the whole thing if you want to limit the scope. (In this case, you'd probably want end to be const anyway.)