Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 987
Walter Avatar asked Oct 31 '13 12:10

Walter


2 Answers

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

like image 158
Keith Thompson Avatar answered Oct 11 '22 18:10

Keith Thompson


According to the final revision of the accepted proposal for this feature, N1737,a possible multi-declarator auto implementation is as follows: (from Section 6)

We believe it is possible to achieve both consistent form and consistent behavior. We do so by inserting (for purposes of exposition) an intermediate __Deduced_type definition, and applying this type consistently in the as-if expansion:

  // Listing 12
 typedef int __Deduced_type; // exposition only
  __Deduced_type a = 1;
 // decltype(a) is int
 __Deduced_type b = 3.14; // decltype(b) is int
  __Deduced_type * c = new float; // error; decltype(c) would be int *

Not only do we achieve consistency of both form and behavior via such a reconciled formulation, we also address more complicated situations. For example, when the leading declarator includes a ptr-operator:

 // Listing 13 
auto * a = new int(1), b = 3.14, * c = new float;

our formulation attaches semantics as-if declared:

// Listing 14 
 typedef int __Deduced_type; // exposition only
 __Deduced_type * a = new int(1); // decltype(a) is int *
 __Deduced_type b = 3.14; // decltype(b) is int
 __Deduced_type * c = new float; // error; decltype(c) would be int *

As shown by this possible implementation, changing the type would be invalid, and as such would cause an error.
This feature was implemented this way because otherwise it would be different than other types of multi-variable declarations.
I remember seeing a discussion about whether or not to allow changing types, but I cannot remember where. IIRC, they decided that it would be harder to implement, but another reason could be that it was dropped because they could not come to a consensus on when to choose different types (the new behavior) or when to implicitly convert to the deduced type of the first declared variable (the old behavior).

like image 42
JKor Avatar answered Oct 11 '22 18:10

JKor