Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using C++11 auto keyword to declare two (or more) variables

I have code like this:

template<class ListItem>
static void printList(QList<ListItem>* list)
{
    for (auto i = list->size() - 1, j = -1; i >= 0; --i) {
        std::cout << i << ", " << j << ": " << list->at(i) << std::endl;
    }
}

When I compile it with g++ 6.2.1 I get the following compiler output:

test.cpp: In function ‘void printList(QList<T>*)’:
test.cpp:10:7: error: inconsistent deduction for ‘auto’: ‘auto’ and then ‘int’
  for (auto i = list->size() - 1, j = -1; i >= 0; --i) {
       ^~~~

I'd understand this, if variables had different types like auto i = 0.0, j = 0;, but in this case list is a pointer to QList and its size() method returns int, -1 on its own should be int, too. The error message is a bit strange as well.

Variables i and j are only needed in this loop and I'd like to declare them as loop parameters. It's not hard to type int instead of auto, but I'd like to know: is auto not supposed to be used for declaring multiple variables in one go, or I am missing something here and it really is erroneous code, or maybe it is the compiler's bug?

P.S. Looks like using a template function is the critical part here, factoring the loop out of the template does not produce errors. So, more like a bug in the compiler?

Live demo - minimal code

like image 684
drongo Avatar asked Nov 28 '16 10:11

drongo


People also ask

What is auto keyword in C++11?

The auto keyword directs the compiler to use the initialization expression of a declared variable, or lambda expression parameter, to deduce its type.

Does C++11 have auto?

C++11 introduces the keyword auto as a new type specifier. auto acts as a placeholder for a type to be deduced from the initializer expression of a variable. With auto type deduction enabled, you no longer need to specify a type while declaring a variable.

What does the auto type specifier do in this line of code since C++11?

What does auto type specifier do in this line of code (since C++11)? auto x = 4000.22; It specifies that the type of x will be deduced from the initializer - in this case, double. It specifies that the type of x is automatic meaning that if can be assigned different types of data throughout the program.

What is the use of auto keyword in C?

Auto is a storage class/ keyword in C Programming language which is used to declare a local variable. A local variable is a variable which is accessed only within a function, memory is allocated to the variable automatically on entering the function and is freed on leaving the function.

When to use the auto keyword in C?

Really, there is no reason to use the auto keyword in C. The only place where we can use it is when we create a variable within a function but… all variables created there are already auto. Why the C auto exists?

Is it possible to assign variables with the auto keyword?

So not only variables but all identifiers can also be assigned with the auto keyword. Let's try to list out the advantages/benefits of using the auto keyword. 1. Easy to use: Just add keyword auto in-front of the variable/identifier and it will do its magic. 2. Works with all data types even with class pointers:

What is an auto variable in C++?

C++11 auto variables An overview of the C++ auto keyword for deducing type information in variable declarations. The C++11 standard introduced the autokeyword for type deduction. One of its main uses is to make variable declarations more concise. It can also improve the maintainability of code if used correctly as it can reduce redundancy.

Can a variable be anything other than the keyword?

It can be anything other than the keyword. For example, 1, int is a data type, and a is a variable name. In the second example, we have declared three variables, a, b, and c. After variables are declared, the space for those variables has been assigned as it will be used for the program.


3 Answers

This is a bug in GCC.

According to [dcl.spec.auto]/1:

The auto and decltype(auto) type-specifiers are used to designate a placeholder type that will be replaced later by deduction from an initializer. [...]

The rules for template argument deduction never deduce a type to be auto. The purpose of deduction in this case is actually to replace auto with a deduced type.

In the example, list has a dependent type (it depends on the template parameter ListItem), so the expression list->size() - 1 also has a dependent type, which makes the type of i also dependent, which means it will only be resolved upon instantiation of the function template printList. Only then can the other semantic constraints related to that declaration be checked.

According to [temp.res]/8:

Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:

[... long list of cases of which none applies here ...]

Otherwise, no diagnostic shall be issued for a template for which a valid specialization can be generated. [ Note: If a template is instantiated, errors will be diagnosed according to the other rules in this Standard. Exactly when these errors are diagnosed is a quality of implementation issue. — end note ]

(emphasis mine)

GCC is wrong to issue that error when analyzing the definition of the template printList, since clearly valid specializations of the template can be generated. In fact, if QList doesn't have any specializations for which size() returns something else than int, the declaration for i and j will be valid in all instantiations of printList.


All quotes are from N4606, the (almost) current working draft, but the relevant parts of the quotes above haven't changed since C++14.


Update: Confirmed as a regression in GCC 6 / 7. Thanks to T.C. for the bug report.

Update: The original bug (78693) was fixed for the upcoming 6.4 and 7.0 releases. It also uncovered some other issues with the way GCC handles such constructs, resulting in two other bug reports: 79009 and 79013.

like image 97
bogdan Avatar answered Oct 21 '22 15:10

bogdan


As mentioned in my comment to your answer, I agree with the analysis you have presented.
Simplest form of the problem (demo):

template<class T>
void foo (T t) {
  auto i = t, j = 1; // error: inconsistent deduction for ‘auto’: ‘auto’ and then ‘int’
}    
int main () {}

In case of templates, compiler in its 1st stage, checks the basic syntax without instantiating it. In our case, we are never invoking foo() anyways.

Now, in above example, the decltype(auto) for i is still auto, because the dependent type T is not known. However, j is surely int. Hence, the compiler error makes sense. Present behavior (G++ >= 6), may or may not be a bug. It depends on what do we expect from the compiler. :-)

However, this error cannot be condemned. Here is the supporting standard quote from C++17 draft:

7.1.7.4.1 Placeholder type deduction

4If the placeholder is the auto type-specifier, the deduced type T replacing T is determined using the rules for template argument deduction. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U

The same thing is present in C++14 standard as 7.1.6.4 / 7.


Why is this error being reported in the first template check itself?

We may rightly argue that, why the compiler is being so "pedantic" in the first syntax check itself. Since, we are not instantiating, then shouldn't it be ok! Even if we instantiate, shouldn't it give error only for the problematic calls!
That's what g++-5 does. Why did they bother to change it?

I think, it's a valid argument. With g++-5, if I call:

foo(1);  // ok
foo(1.0); // error reported inside `foo()`, referencing this line

Then compiler correctly reports the error and its hierarchy when i and j are of different types.

like image 29
iammilind Avatar answered Oct 21 '22 17:10

iammilind


I'll summerise the information received on the topic then.

The issue in the example code is in using a template function. Compiler does a generic check of a template first without instantiating it, this means that types, that are template arguments (and types that depend on them, like other templates) are not known, and auto if it depends on those unknown types is deduced into auto again (or not deduced into some concrete type). It never appeared to me that even after deduction auto can still be auto. Now original compiler error text makes perfect sense: variable j is deduced to be of type int, but variable i is still auto after deduction. Since auto and int are different types, compiler generates the error.

like image 36
drongo Avatar answered Oct 21 '22 15:10

drongo