使用 C++11 auto 关键字声明两个(或更多)变量

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

我有这样的代码:

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;
    }
}

当我用 g++ 6.2.1 编译它时,我得到以下编译器输出:

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) {
       ^~~~

我会理解这一点,如果变量有不同的类型,如 auto i = 0.0, j = 0;,但在这种情况下,列表是指向 QList 及其 size() 方法的指针 returns int-1 本身也应该是 int。报错信息也有点奇怪。

变量ij只在这个循环中需要,我想将它们声明为循环参数。键入 int 而不是 auto 并不难,但我想知道: auto 不应该用于一次声明多个变量,或者我在这里遗漏了一些东西,它真的是错误代码,还是编译器的bug?

P.S。看起来使用模板函数是这里的关键部分,将循环排除在模板之外不会产生错误。所以,更像是编译器中的错误?

Live demo - minimal code

然后我将总结收到的关于该主题的信息。

示例代码中的问题在于使用模板函数。 编译器首先对模板进行通用检查而不实例化它,这意味着作为模板参数的类型(以及依赖于它们的类型,就像其他模板一样)是未知的,并且 auto 如果它依赖于那些未知的types 再次推导为 auto (或不推导为某种具体类型)。在我看来,即使在扣除后 auto 仍然可以是 auto。现在原始编译器错误文本非常有意义:变量 j 被推导为类型 int,但变量 i 推导后仍为 auto。由于 autoint 是不同的类型,编译器会生成错误。

正如我在 的评论中所提到的,我同意您的分析。
问题的最简单形式 (demo):

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

在模板的情况下,编译器在其第一阶段检查基本语法而不实例化它。在我们的例子中,无论如何我们永远不会调用 foo()

现在,在上面的示例中,idecltype(auto) 仍然是 auto,因为依赖类型 T 未知。但是,j肯定是int。 因此,编译器错误是有道理的。目前的行为 (G++ >= 6),可能是也可能不是错误。这取决于我们对编译器的期望。 :-)

但是,这个错误不能谴责。这是来自 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

C++14 standard 中存在与 7.1.6.4 / 7 相同的内容。


为什么第一次模板检查本身会报告此错误?

我们可能会正确地争论,为什么编译器在第一个语法检查中如此 "pedantic"。既然,我们不是实例化,那岂不是应该可以吧!即使我们实例化,它不应该只对有问题的调用给出错误吗?
这就是 g++-5 所做的。为什么他们费心去改变它?

我认为,这是一个有效的论点。使用 g++-5,如果我调用:

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

ij 是不同类型时,编译器会正确报告错误及其层次结构。

这是 GCC 中的错误。

根据[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. [...]

模板参数推导规则永远不会将类型推导为 auto。这种情况下推导的目的其实是用推导的类型代替auto

例子中,list有一个依赖类型(它依赖于模板参数ListItem),所以表达式list->size() - 1也有一个依赖类型,这使得类型i 的也依赖,这意味着它只会在函数模板 printList 实例化时解析。只有这样才能检查与该声明相关的其他语义约束。

根据[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 ]

(强调我的)

GCC 在分析模板 printList 的定义时发出该错误是错误的,因为可以生成明确有效的模板特化。事实上,如果 QList 没有 size() returns 除了 int 之外的任何特化,ij 的声明将在 printList.

的所有实例化中有效

所有引用都来自 N4606,(几乎)当前的工作草案,但上面引用的相关部分自 C++14 以来没有改变。


更新:Confirmed as a regression in GCC 6 / 7. Thanks to T.C. 错误报告。

更新:原始错误(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