14
struct Bar {
    template<typename>
    void baz() {
    }
};

template<typename>
struct Foo {
    Bar bar;

    Foo() {
        bar.baz<int>();
    }
};

int main() {
    return 0;
}

这段代码编译得很好(在 GCC 4.7 中),但如果我在调用前bar.baz<int>()加上 with this->baz就会变成一个需要用 消除歧义的依赖名称template

bar.baz<int>(); // OK
this->bar.baz<int>(); // error
this->bar.template baz<int>(); // OK

肯定this->bar只能指Bar bar,谁的成员baz明明是模板?为什么添加this->会使此代码对编译器产生歧义?

ps 最初,bar它是需要用 消除歧义的基类模板的数据成员this->,但为了解决这个问题,我已经简化了示例。

4

3 回答 3

4
this->bar.baz<int>(); // error

上面的语句,在 的定义中template<typename T> Foo<T>::Foo(),格式正确,如果启用了 C++11 模式或 C++1y 模式,则应该接受。但根据 C++03,它在技术上是错误的。

两个标准都同意这this是一个依赖于类型的表达式:

C++03 14.6.2.1/1;N3690 14.6.2.1/8:

一个类型是依赖的,如果它是

  • 模板参数,

  • ...

  • 一个 [ simple- ]模板 ID,其中模板名称是模板参数或任何模板参数是依赖类型或依赖于类型或值的表达式,

[T是依赖类型, 也是Foo<T>。]

C++03/N3690 14.6.2.2/2:

this如果封闭成员函数的类类型是依赖的,则依赖于类型。

[由于Foo<T>是依赖类型,this其成员定义中的表达式是依赖类型的。]

这两个标准都从 14.6.2.2 开始:

除非下面描述,如果任何子表达式是类型相关的,则表达式是类型相关的。

C++03 只有三种简单的表达式,描述更准确:

  • 主要表达式(this和查找名称)

  • 指定自己类型的表达式(如强制转换和新表达式)

  • 具有常量类型的表达式(如文字和sizeof)。

第一类在 C++03 14.6.2.2/3 中定义:

如果id 表达式包含以下内容,则它是类型相关的:

  • 使用依赖类型声明的标识符

  • 一个依赖的模板ID

  • 指定依赖类型的转换函数 ID

  • 一个嵌套名称说明符,其中包含一个命名依赖类型的类名

所以唯一的表达式bar是不依赖的:它是一个标识符和一个id-expression,但以上都不适用。

But this->baris 不是id-expression,或者在任何其他 C++03 异常中,所以我们必须遵循子表达式规则。由于子表达式this是类型相关的,所以包含的表达式this->bar也是类型相关的。

但实际上,正如您所注意到的,this->bar在解析模板定义时可以知道 的类型,而无需实例化任何模板参数。它被声明为主模板的成员,因此名称必须绑定到该成员声明。模板特化可能会Foo<T>::bar以不同的方式未声明或声明,但在这种情况下,根本不会使用主模板,并且该特化的当前定义将Foo()被忽略。这就是为什么 C++11 定义了“当前实例化”的概念并将其用于类型相关表达式的传染性的进一步例外。

N3690 14.6.2.1/1:

名称指的是当前实例化,如果它是

  • 在类模板的定义中,类模板的嵌套类,类模板的成员,或类模板的嵌套类的成员,类模板或嵌套类的注入类名

  • 在主类模板或主类模板成员的定义中,类模板的名称后跟包含在<>(或等效模板别名特化)中的主模板的模板参数列表(如下所述),

  • ...

[第一个项目符号Foo是当前实例化。第二个说Foo<T>是当前的实例化。在此示例中,两者都命名相同的类型。]

14.6.2.1/4:

一个名称是当前实例化的成员,如果它是

  • 一个非限定名称,当查找时,它指的是作为当前实例化的类的至少一个成员或其非依赖基类。

  • 一个合格的ID,其中...

  • id-expression表示类成员访问表达式中的成员,其对象表达式的类型是当前实例化,并且id -expression在查找时引用作为当前实例化的类的至少一个成员或其非依赖基类。

[第一个项目符号bar单独说是当前实例化的成员。第三个项目符号说this->bar是当前实例化的成员。]

最后,C++11 为类型相关表达式添加了第四类规则,用于成员访问。14.6.2.2/5:

如果表达式引用当前实例化的成员并且被引用成员的类型是依赖的,或者类成员访问表达式引用未知特化的成员,则类成员访问表达式是类型相关的。

this->bar确实引用了当前实例化的成员,但Bar被引用成员的类型不相关。所以 nowthis->bar不依赖于类型,在模板定义过程中查找bazin的名称作为非依赖名称。之前不需要关键字this->bar.baztemplatebaz

于 2014-07-06T00:55:51.137 回答
3

简短的摘要

这正是当前 C++11 规则的方式:this->bar.baz<int>()引入一个不在当前实例化上下文中的依赖名称,需要消除template关键字的歧义,即使很难想出一个改变的竞争解析的实际示例表达式的语义this->bar.baz<int>()

从尖括号中解析歧义

第一:为什么通常需要template?

当 C++ 编译器遇到表达式f<g>(0)时,它可以将其解释为“f为模板参数g和函数参数调用函数模板0并评估结果”,也可以意味着“(f<g)>(0)对名称fg常量进行比较0”。如果没有进一步的信息,它不能做出这个决定。这是为模板参数选择尖括号的不幸结果。

在许多(大多数)情况下,编译器确实有足够的上下文来决定是解析模板表达式还是比较比较。但是,当遇到所谓的依赖名称(本质上是显式或隐式依赖于当前范围的模板参数的名称)时,另一种语言的微妙之处就会发挥作用。

两阶段名称查找

因为当模板被实例化为具体类型时,依赖于模板的名称可能会改变其含义(例如,通过特化),所以依赖名称的名称查找分两个阶段完成(引自C++ Templates the Complete Guide):

在第一阶段,在使用普通查找规则和(如果适用)参数相关查找 (ADL) 规则解析模板的同时查找非依赖名称。 未限定的依赖名称(它们是依赖的,因为它们看起来像具有依赖参数的函数调用中的函数名称)也以这种方式查找,但在模板执行附加查找之前,查找的结果不被认为是完整的被实例化

在第二阶段,即在称为实例化点 (POI) 的点实例化模板时,会查找相关的限定名称(将模板参数替换为该特定实例化的模板参数),并且额外的 ADL 是对不合格的从属名称执行。

为什么this->让你的代码与众不同

在类模板中使用this->会引入依赖名称并触发两阶段名称查找。在这种情况下,@40two 引用的规则就会发挥作用。关键是第二阶段的 ADL 可以从显式特化中引入新名称,重新定义您的含义,bar并且baz可以想象将含义更改为this->bar.baz<int>(0)比较而不是函数模板调用。

当然,对于诸如此类的非类型模板参数,this->bar.another_baz<0>()它比类型模板参数更有可能。在这个相关的问答中,出现了一个类似的讨论,即是否可以找到一种改变this->f<int>()vs含义的有效语法形式this->template f<int>(0),但没有明确的结论。

请注意,template与 C++98 相比,C++11 已经放宽了消歧规则。在当前的规则下this->f<int>(),一个template<class> f()里面Foo就不需要template了,因为它是在所谓的当前实例化中。有关更多详细信息,请参阅此主题的规范问答中的此答案

于 2014-07-05T19:00:20.567 回答
2

根据标准§ 14.2/4 Names of template specializations [temp.names]

当成员模板特化的名称出现在后缀表达式之后.->之后,或者在限定 ID 中的嵌套名称说明符之后,并且后缀表达式的对象表达式是类型相关的或嵌套名称说明符时在qualified-id 中指的是依赖类型,但名称不是当前实例化的成员(14.6.2.1),成员模板名称必须以关键字为前缀template

编辑:

此外,根据标准§ 14.6.2.2/2 类型相关表达式 [temp.dep.expr]

this如果封闭成员函数的类类型是依赖的(14.6.2.1),则依赖于类型。

因此,为了bar.baz<int>()通过this您需要以关键字为前缀进行调用template

this->bar.template baz<int>();

LIVE DEMO

[原因: ] 编译器需要这种“redantant”template关键字使用,因为它无法确定标记<operator<模板参数列表还是模板参数列表的开头。

于 2014-07-05T16:16:40.283 回答