25

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

Oktalist
  • 13,098
  • 1
  • 38
  • 56
drongo
  • 279
  • 2
  • 6
  • 10
    [mcve], please. You write that `modelList` is a `QList`, but your compiler obviously disagrees, so I'd like to be able to tell which one is wrong. Which I can't do without seeing the same code the compiler does. – Angew is no longer proud of SO Nov 28 '16 at 10:20
  • It's a curious use at least. Auto is meant for a single parameter auto-determination based on its initialization expression, and multiple parameters could derive to different types. – dascandy Nov 28 '16 at 10:22
  • 1
    http://coliru.stacked-crooked.com/a/dc1ca01afe50201a working fine with C++11, sure `modelList` is a `QList`? – Hatted Rooster Nov 28 '16 at 10:24
  • @dascandy, It's entirely legal. And moreover, `auto val = make_val(), *pval = &val;` is quite natural. – ach Nov 28 '16 at 10:25
  • @AndreyChernyakhovskiy What does it do then when they have subtly different types, such as size_t and int? Or possibly-the-same types, such as uint32_t and unsigned long? – dascandy Nov 28 '16 at 10:27
  • 1
    @dascandy it [errors](http://coliru.stacked-crooked.com/a/a478963372dc6f98). – Hatted Rooster Nov 28 '16 at 10:28
  • @dascandy, The requirement is that `auto` must be deduced to the same type. – ach Nov 28 '16 at 10:28
  • The error implies that the deduced types for `i` and `j` don't match, hence `size()` does not return `int`... But `QList::size` does return an `int`. There's something fishy here – Rerito Nov 28 '16 at 10:37
  • Yep, and trhis 'fishy' is using a template function. Do I just edit original post and provide this 'minimal' and 'complete' code sample? – drongo Nov 28 '16 at 10:55
  • Does it work if you change from `static void printList(QList* list)` to `static void printList(QList* list)`? – Tommy Andersen Nov 28 '16 at 11:11
  • 3
    Ah, now that there's the full code, it makes sense. The compiler cannot deduce the type of `list->size()` because `list` depends on template arguments. Remember, `QList` may be specialized so that `list->size()` may return a type other than `int`. – ach Nov 28 '16 at 11:15
  • @Tommy Andersen: Well, yes. If I also change the call in main to `printList(&list);`, since ListItem becomes 'orphan' (eg completely unused) and compiler can't deduce its type anymore. – drongo Nov 28 '16 at 11:19
  • It is due to compiletime determination of the type then, but it puzzles me since `size()` of `QList` is (at least in the source I found) written so it always returns `int`. – Tommy Andersen Nov 28 '16 at 11:22
  • @AndreyChernyakhovskiy: Template is instantiated at the point of using it, isn't it? `QList`'s method `size()` is not item type dependent, declared to always return `int`. – drongo Nov 28 '16 at 11:24
  • @drongo, Instantiated, yes. But some restrictions are checked generically, before instantiation. Try removing all uses of your `printList`: the diagnostic should remain. – ach Nov 28 '16 at 11:29
  • @drongo: Please specify: Whether function `printList` is called from anywhere in code or just the definition is specified in code? – sameerkn Dec 06 '16 at 07:19
  • @sameerkn I edited the question to remove irrelevant parts. In the original, `printList` is called with an argument of type `QList`. – Oktalist Dec 06 '16 at 15:53
  • The bug was fixed for version 6.4. It also helped uncover more issues (see my update below) so bringing this up was an excellent idea. cc @Oktalist – bogdan Jan 18 '17 at 19:40

3 Answers3

13

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.

bogdan
  • 8,744
  • 2
  • 28
  • 43
  • I didn't understand the relationship between the question and your last quote. What is what isn't gcc reporting? – Peregring-lk Dec 06 '16 at 15:29
  • 1
    @Peregring-lk GCC issues an error when analyzing the function template `printList` (or `foo` in the minimal example added at the end of the question). The last quote indicates that this is incorrect: instantiating, for example, `printList` (or `foo`), results in a perfectly valid and consistent declaration for `i` and `j`, so GCC shouldn't issue that error on the template definition. Trying to instantiate `foo` should trigger an error, but for that specialization; the definition of the template `foo` itself is valid. – bogdan Dec 06 '16 at 17:35
  • @bogdan Ok, though I don't know why but I'm still not fully getting it. I think is just something so obvious in short (if it works, nothing must be reported), that I'm still searching for something else; after all, it's a quote of the standard about template instantiations. It cannot be something obvious. – Peregring-lk Dec 06 '16 at 18:29
  • @T.C., can you see if the problem discussed in this question also a bug: [Why compilation fails when a template parameter name matches an inner class name?](http://stackoverflow.com/q/41280133/514235). I am unable to register an account in gcc and hence raise this bug. See if you can. Thanks. – iammilind Dec 22 '16 at 10:00
4

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

4 If 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.

Community
  • 1
  • 1
iammilind
  • 62,239
  • 27
  • 150
  • 297
  • That would be my concern, actually: isn't the first stage supposed to be just that, **basic syntax check**? ` <=> <=> ` is perfectly valid. – The Vee Dec 06 '16 at 10:27
  • What in the standard determines how much the compiler should or can do as far as actual translating goes, when parsing the template prior to instantiation? – The Vee Dec 06 '16 at 10:30
  • @TheVee, I am not sure. Your point is valid as well. I have updated my answer considering that. – iammilind Dec 06 '16 at 10:40
  • @TheVee the compiler can do full semantic checking of statements that don't depend on template parameters. GCC and Clang will diagnose `int x = "string literal";` in the first stage, for example. – Oktalist Dec 06 '16 at 15:47
  • I think this is one of those rare cases where MS made a good call in wandering away from the herd. They really don't parse the contents of the templated function at all until it's instantiated. That gets around all sorts of warnings and errors, leaving just those that actually apply where it's instantiated. The downside, of course, is that failing to instantiate the function anywhere (e.g. in your test suite) means that a function which is compile-time broken may go unnoticed until, say, your middleware API is released and someone in the wild tries to use it. – Aiken Drum Dec 09 '16 at 13:38
  • 1
    @AikenDrum It also breaks the name lookup rules and it means that the compiler wastes time reinstantiating code that is invariant across all instantiations of a given template. When is it ever a good idea to postpone inevitable errors? – Oktalist Dec 10 '16 at 23:27
1

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.

drongo
  • 279
  • 2
  • 6
  • 5
    The question remains, whether this is a compiler bug, defect in the standard, or conformant behaviour. GCC 4, GCC 5, and all Clang versions happily compile the code without error. Only GCC >= 6 is affected. – Oktalist Nov 29 '16 at 16:55
  • I agree with the analysis. If GCC >= 6 is not buggy, then this answer seems logical. – iammilind Dec 06 '16 at 06:16
  • 3
    In what universe is this not a compiler bug? There's nothing to deduce when the type is dependent. – T.C. Dec 06 '16 at 08:47
  • 2
    `auto` is not a type, I think rather gcc reporting `auto` means that gcc has not deduced the type yet. – dyp Dec 06 '16 at 10:25