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

25

我有这样的代码:

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;,我可以理解这个问题。但在这种情况下,list是指向QList的指针,其size()方法返回int-1本身也应该是int。错误消息也有点奇怪。

变量ij仅在此循环中需要,我想将它们声明为循环参数。输入int而不是auto并不难,但我想知道:是否不应该使用auto来一次性声明多个变量,或者我在这里漏掉了什么,这真的是错误的代码,还是编译器的错误?

P.S. 看起来使用模板函数是关键部分,在模板之外分离循环不会产生错误。因此,更像是编译器的一个错误?

实时演示 - 最小代码


10
[mcve],请。你写道modelList是一个QList,但你的编译器显然不同意,所以我想知道到底哪一方出了问题。但我无法看到编译器看到的代码,因此希望能看到相同的代码。 - Angew is no longer proud of SO
1
http://coliru.stacked-crooked.com/a/dc1ca01afe50201a 在C++11下运行良好,确定modelList是一个QList吗? - Hatted Rooster
1
@dascandy,它出现了错误 - Hatted Rooster
@dascandy,要求是auto必须被推导为相同的类型。 - ach
3
现在有了完整的代码,就很清楚了。编译器无法推断list->size()的类型,因为list依赖于模板参数。请记住,QList<ListItem>可以被特化以使list->size()返回除int之外的其他类型。 - ach
显示剩余13条评论
3个回答

13

这是GCC的一个错误。

根据[dcl.spec.auto]/1:

autodecltype(auto)类型说明符用于指定将在初始化后通过推导替换的占位符类型。[...]

模板参数推导规则永远不会推导出类型为auto的类型。在这种情况下,推导的目的实际上是将auto替换为一个推断出的类型。

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

根据[temp.res]/8:

了解哪些名称是类型名称允许检查每个模板的语法。如果程序不良形成,则无需诊断,如果:[...长列表中的情况都不适用于此...]否则,对于可以生成有效专业化的模板,不应发出诊断。[注意:如果实例化模板,则将根据此标准中的其他规则诊断错误。这些错误何时被诊断是实现问题的质量。-结束注释](我强调)。GCC在分析模板printList的定义时发出错误,因为显然可以生成模板的有效专业化。实际上,如果QList没有任何特殊化,其中size()返回除int以外的其他内容,则i和j的声明将在printList的所有实例化中有效。
所有引用都来自N4606,这是(几乎)当前的工作草案,但上述引用的相关部分自C++14以来并未更改。

更新:在GCC 6/7中确认为回归(已确认为回归)。感谢T.C.的错误报告。

更新:原始错误(78693)已在即将发布的6.4和7.0版本中修复。它还揭示了GCC处理此类结构的其他问题,导致另外两个错误报告:7900979013


我不理解问题和你最后一句引用之间的关系。GCC报告了什么?还是没有报告什么? - ABu
1
@Peregring-lk 当分析函数模板printList(或问题末尾添加的最小示例中的foo)时,GCC会发出错误。最后的引号表明这是不正确的:例如实例化printList<std::string>(或foo<int>)会导致ij的声明完全有效和一致,因此GCC不应在模板定义上发出该错误。尝试实例化foo<long>应该触发一个错误,但是针对该特化;模板foo本身的定义是有效的。 - bogdan
@bogdan 好的,虽然我不知道为什么,但我还是没有完全理解。我认为这只是一些很明显的东西(如果它有效,就不需要报告任何内容),所以我仍在寻找其他东西;毕竟,这是关于模板实例化的标准引用。它不能是一些显而易见的东西。 - ABu
@T.C.,你能否看一下这个问题讨论中提到的是否也是一个bug:为什么当模板参数名称与内部类名称匹配时编译失败?。我无法在gcc上注册账户,因此无法报告此bug。请看看你能否帮忙。谢谢。 - iammilind

4

如我在对你的回答的评论中所提到的,我同意你提出的分析。
问题的最简形式(演示):

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)可能是一个bug,也可能不是。这取决于我们对编译器的期望。:-)
然而,这个错误不能被谴责。以下是来自C++17草案的支持标准引用:
7.1.7.4.1 占位符类型推导 如果占位符是auto类型说明符,则使用模板参数推导规则确定替换T的推导类型T。通过将auto出现的位置替换为新的虚构类型模板参数U来从T获取P C++14标准中也有相同的内容,即7.1.6.4 / 7。
为什么这个错误会在第一个模板检查中报告呢?
我们可以说,为什么编译器在第一次语法检查时如此“追求完美”。既然我们没有实例化,那么不应该没问题吗!即使我们实例化,也只应该对有问题的调用给出错误。g++-5就是这样做的。他们为什么要改变它呢?
我认为这是一个合理的论点。使用g++-5,如果我调用:
foo(1);  // ok
foo(1.0); // error reported inside `foo()`, referencing this line

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


这正是我的担忧:第一阶段难道不应该只是进行基本语法检查吗?<type> <variable> <=> <variable> <,> <variable> <=> <literal> <;>是完全有效的。 - The Vee
在标准中,解析模板并进行实例化之前,编译器在实际翻译方面应该或可以做多少取决于什么? - The Vee
@TheVee,我不确定。你的观点也是有道理的。我已经考虑到了并更新了我的答案。 - iammilind
@TheVee 编译器可以对不依赖于模板参数的语句进行完整的语义检查。例如,GCC和Clang将在第一阶段诊断int x =“字符串文字”; - Oktalist
我认为这是微软在远离群体的时候做出的明智选择中的罕见例子之一。他们真的不会解析模板函数的内容,直到它被实例化。这样可以避免各种警告和错误,只在实例化时应用适用的警告和错误。当然,缺点是如果没有在任何地方实例化该函数(例如,在您的测试套件中),那么一个在编译时出错的函数可能会被忽视,直到比如说你的中间件 API 发布并且有人在野外试图使用它。 - Aiken Drum
1
@AikenDrum 它还违反了名称查找规则,这意味着编译器浪费时间重新实例化在给定模板的所有实例化中不变的代码。什么时候推迟不可避免的错误才是一个好主意? - Oktalist

1
我将总结一下关于这个主题收到的信息。
在示例代码中的问题在于使用了模板函数。编译器首先进行通用的模板检查而不实例化它,这意味着作为模板参数的类型(以及依赖于它们的类型,如其他模板)是未知的,如果auto取决于这些未知类型,则会再次推导为auto(或者不推导为具体类型)。对我来说,即使在推导后auto仍然可以是auto,也从未出现过。现在原始编译器错误文本变得完全合理:变量j被推导为int类型,但变量i在推导后仍然是auto。由于auto和int是不同的类型,因此编译器生成错误。

5
问题在于,这是编译器 bug、标准中的缺陷,还是符合规范的行为。GCC 4、GCC 5和所有版本的Clang都能够愉快地编译代码而不出错。只有GCC >= 6受到影响。 - Oktalist
我同意这个分析。如果GCC >= 6没有错误,那么这个答案似乎是合理的。 - iammilind
3
这不是编译器的错误吗?当类型是依赖性的时候,就没有什么可以推导的了。 - T.C.
2
auto 不是一种类型,我认为gcc报告 auto 意味着gcc尚未推断出类型。 - dyp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接