【问题标题】:Using C++11 auto keyword to declare two (or more) variables使用 C++11 auto 关键字声明两个(或更多)变量
【发布时间】:2016-11-28 10:16:42
【问题描述】:

我有这样的代码:

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 本身应该是 @987654330 @, 也。错误信息也有点奇怪。

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

附:看起来使用模板函数是这里的关键部分,将循环从模板中分解出来不会产生错误。那么,更像是编译器中的一个错误?

Live demo - minimal code

【问题讨论】:

  • minimal reproducible example,拜托。你写道modelListQList,但你的编译器显然不同意,所以我想知道哪个是错的。如果没有看到编译器所做的相同代码,我就无法做到这一点。
  • 这至少是一个奇怪的用途。 Auto 是指基于其初始化表达式的单个参数自动确定,多个参数可以派生为不同的类型。
  • coliru.stacked-crooked.com/a/dc1ca01afe50201a 可以在 C++11 中正常工作,确定 modelListQList?
  • @dascandy 它errors.
  • 啊,既然有完整的代码,那就说得通了。编译器无法推断出list-&gt;size() 的类型,因为list 依赖于模板参数。请记住,QList&lt;ListItem&gt; 可能是特化的,因此 list-&gt;size() 可能返回 int 以外的类型。

标签: c++ c++11 templates auto


【解决方案1】:

这是 GCC 中的一个错误。

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

autodecltype(auto) 类型说明符用于指定 占位符类型,稍后将通过从 初始化器。 [...]

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

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

根据 [temp.res]/8:

知道哪些名称是类型名称允许每个模板的语法 被检查。该程序格式错误,不需要诊断,如果:

[...这里没有适用的案例的长列表...]

否则,不应为模板发出诊断 可以生成有效的专业化。 [ 注意: 如果模板是 实例化,错误将根据中的其他规则进行诊断 本标准。准确地诊断出这些错误是 实施问题。 ——结束注释 ]

(强调我的)

GCC 在分析模板printList 的定义时发出该错误是错误的,因为可以生成明显有效的模板特化。事实上,如果QList 没有任何特化,size() 没有返回 int 以外的值,那么 ij 的声明将在 printList 的所有实例化中有效。


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


更新:GCC 6 / 7 中的Confirmed as a regression。感谢T.C. 的错误报告。

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

【讨论】:

  • 我不明白这个问题和你最后的报价之间的关系。什么不是 gcc 报告?
  • @Peregring-lk GCC 在分析函数模板printList(或问题末尾添加的最小示例中的foo)时发出错误。最后一个引号表明这是不正确的:例如,实例化printList&lt;std::string&gt;(或foo&lt;int&gt;)会导致ij 的声明完全有效且一致,因此GCC 不应发出该错误模板定义。尝试实例化 foo&lt;long&gt; 应该会触发错误,但对于该专业化;模板foo本身的定义是有效的。
  • @bogdan 好的,虽然我不知道为什么,但我仍然没有完全理解。我认为简而言之就是如此明显(如果它有效,则无需报告任何内容),我仍在寻找其他东西;毕竟,这是对模板实例化标准的引用。这不可能是显而易见的。
  • @T.C.,你能看看这个问题中讨论的问题是否也是一个错误:Why compilation fails when a template parameter name matches an inner class name?。我无法在 gcc 中注册帐户,因此引发了此错误。看看你能不能。谢谢。
【解决方案2】:

正如我在对your answer 的评论中提到的,我同意您提出的分析。
问题的最简单形式 (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 占位符类型推演

4如果占位符是自动类型说明符,则使用模板参数推导规则确定推导的类型 T 替换 T。。通过将出现的 auto 替换为新发明的任意一种来从 T 中获得 P 类型模板参数U

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


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

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

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

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

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

【讨论】:

  • 这实际上是我关心的问题:第一阶段不应该就是这样,基本语法检查吗? &lt;type&gt; &lt;variable&gt; &lt;=&gt; &lt;variable&gt; &lt;,&gt; &lt;variable&gt; &lt;=&gt; &lt;literal&gt; &lt;;&gt; 完全有效。
  • 在实例化之前解析模板时,标准中的什么决定了编译器在实际翻译中应该或可以做多少?
  • @TheVee,我不确定。你的观点也是有效的。考虑到这一点,我已经更新了我的答案。
  • @TheVee 编译器可以对不依赖于模板参数的语句进行完整的语义检查。例如,GCC 和 Clang 将在第一阶段诊断 int x = "string literal";
  • @AikenDrum 它还破坏了名称查找规则,这意味着编译器浪费时间重新实例化在给定模板的所有实例中不变的代码。什么时候推迟不可避免的错误是个好主意?
【解决方案3】:

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

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

【讨论】:

  • 问题仍然存在,这是编译器错误、标准缺陷还是符合行为。 GCC 4、GCC 5 和所有 Clang 版本都可以愉快地编译代码而不会出错。只有 GCC >= 6 受到影响。
  • 我同意这个分析。如果 GCC >= 6 没有问题,那么这个答案似乎是合乎逻辑的。
  • 在哪个宇宙中这不是编译器错误?当类型依赖时,没有什么可推断的。
  • auto 不是类型,我认为 gcc 报告auto 意味着 gcc 还没有推断出类型。
猜你喜欢
  • 1970-01-01
  • 2013-11-06
  • 1970-01-01
  • 1970-01-01
  • 2014-10-08
  • 1970-01-01
  • 2022-06-14
  • 1970-01-01
  • 2011-10-17
相关资源
最近更新 更多