【问题标题】:Strange compilation behaviour when calling a template method from another template object从另一个模板对象调用模板方法时出现奇怪的编译行为
【发布时间】:2012-12-01 15:41:57
【问题描述】:

有人能解释一下为什么下面的 c++ 代码没有按预期运行吗:

struct Object {   
  template< int i >
  void foo(){ } 
};

template<int counter>
struct Container {
  Object v[counter];

  void test(){
    // this works as expected
    Object a; a.foo<1>();

    // This works as well:
    Object *b = new Object(); b->foo<1>();

    // now try the same thing with the array:  
    v[0] = Object(); // that's fine (just testing access to the array)

# if defined BUG1
    v[0].foo<1>();   // compilation fails 
# elif defined BUG2
    (v[0]).foo<1>(); // compilation fails
# elif defined BUG3
    auto &o = v[0];
    o.foo<1>();      // compilation fails
# else
    Object &o = v[0];
    o.foo<1>();      // works
# endif
  }
};

int main(){
  Container<10> container;
}

上面的代码在没有标志的情况下编译得很好。如果设置了标志 BUG1 到 BUG3 中的一个,则使用 GCC 4.6 或 4.7 和 clang 3.2 编译会失败(这似乎表明它不是 GCC 错误)。

第 21 到 29 行在语义上做了完全相同的事情(即调用 Object 数组的第一个元素的方法),但只有最后一个版本可以编译。只有当我尝试从模板对象调用模板化方法时,才会出现问题。

BUG1 只是编写调用的“正常”方式。

BUG2 也是一样,但数组访问受括号保护,以防出现优先级问题(但不应该有)。

BUG3 显示类型推断也不起作用(需要使用 c++11 支持编译)。

上一个版本运行良好,但我不明白为什么使用临时变量来存储引用可以解决问题。

我很想知道为什么其他三个无效。

谢谢

【问题讨论】:

  • 试试v[0].template foo&lt;1&gt;();
  • @KerrekSB:有趣的是为什么这是必要的。看起来 v[0] 不是依赖表达式,所以编译器在解析模板时应该能够解析它的类型。
  • @VaughnCato:我还没有测试过,我也不确定。这只是一个建议......毕竟,OP 并没有告诉我们错误是什么,而且只有这么多猜测可以做。
  • 啊,看起来v 是一个依赖于值的名称(参见14.6.2)。
  • @Thibaut:是的,我认为这实际上是一个非常简单的论点:使用auto,类型被推断出来,它被推断为依赖类型(即Object[counter]()[0] 的类型)。在另一个分支上,您明确地说Object &amp;,所以没有任何推论。如果您更改了 Object 类型的名称,auto 代码将继续工作,而显式代码将中断。明确表示你知道编译器不知道如何使用模板。

标签: c++ templates compilation compiler-errors


【解决方案1】:

你必须使用template作为:

v[0].template foo<1>();  

auto &o = v[0];
o.template foo<1>();     

因为v 的声明依赖于模板参数,这使得v 成为依赖名称。

这里的template 关键字告诉编译器后面的内容是一个模板(在你的例子中,foo 确实是一个模板)。如果foo 不是模板,那么template 关键字就不是必需的(实际上会出错)。

问题在于o.foo&lt;1&gt;() 可以通过两种方式进行解析/解释:一种是正如您所期望的那样(函数调用),另一种方式是这样的:

(o.foo) < 1  //partially parsed

也就是说,foo 是一个成员数据(不是函数),您已经将它与1 进行了比较。所以告诉编译器&lt;不是用来比较o.foo1,而是用来将模板参数1传递给函数模板,你需要使用template关键字。

【讨论】:

  • 但是,如果 foo 不是模板方法,为什么它会起作用?问题似乎不是来自 v。
  • 谢谢,非常清楚的解释。知道为什么类型推断在第三种情况下也会失败吗?我认为 auto 关键字会以与手动声明类型相同的方式消除歧义。
  • @Thibaut:将auto 视为模板参数T。这些值取决于上下文,这里你在 dependent 上下文下工作:依赖于模板参数counter
【解决方案2】:

在模板中,表达式可以是类型相关的值相关的。从 14.6.2 开始:

类型和表达式可能取决于模板参数的类型和/或值

在您的情况下,counter 是一个模板参数,v 的声明依赖于它,使 v[0] 成为一个 值相关 表达式。因此名称foo 是一个从属名称,您必须通过以下方式消除它作为模板名称的歧义:

v[0].template foo<1>();

【讨论】:

  • 是的!数组大小与值有关,使数组类型与类型有关,使名称v 与值有关,使表达式v[0] 与值有关。
猜你喜欢
  • 1970-01-01
  • 2012-07-05
  • 2014-07-06
  • 2019-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-08
  • 1970-01-01
相关资源
最近更新 更多