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
The auto keyword directs the compiler to use the initialization expression of a declared variable, or lambda expression parameter, to deduce its type.
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 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.
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.
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?
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:
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.
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.
This is a bug in GCC.
According to [dcl.spec.auto]/1:
The
auto
anddecltype(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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With