4

以下代码被 clang 和 gcc 拒绝

template<typename T>
void f(T t)
{
    t.Dependent::f(); // clang accepts, gcc rejects
    t.operator Dependent*(); // both reject
}

struct Dependent
{
     void f();
};

struct A : Dependent
{
     operator Dependent*();
};

template void f<A>(A);

我对标准的阅读表明这两种表达方式都应该被接受。

在这两种情况下,Dependent只能是类型名称。

在这两种情况下,名称Dependent都将“在对象表达式的类中查找” t。作为t一个依赖于类型的表达式,查找应该推迟到模板被实例化。

有什么我想念的吗?

编辑:如果打算这样的名称不依赖,那么这个决定的理由是什么?我可以看到,如果实现者不必推迟对诸如命名空间或类型名称之类的构造的评估,那么它会使实现者的生活更t.operator X::Dependent*轻松。我不清楚这是当前措辞的有意还是无意的副作用。t.X::Dependent::fX

C++ 工作草案 N3337 的相关引用:

3.4.5 类成员访问 [basic.lookup.classref]

如果类成员访问中的 id-expression 是 class-name-or-namespace-name::... 形式的限定 id,则 . 或 -> 运算符首先在对象表达式的类中查找,如果找到,则使用名称。否则在整个后缀表达式的上下文中查找它。[注意:见 3.4.3,它描述了在 :: 之前查找名称,它只会找到类型或命名空间名称。——尾注]

如果 id-expression 是转换函数 ID,则首先在对象表达式的类中查找其转换类型 ID ,如果找到,则使用名称。否则在整个后缀表达式的上下文中查找它。在这些查找中的每一个中,只考虑表示类型或特化为类型的模板的名称

14.6.2 从属名称 [temp.dep]

在模板内部,一些结构的语义可能因一个实例化而异于另一个实例化。这样的构造取决于模板参数。特别是,类型和表达式可能取决于模板参数的类型和/或值(由模板参数确定),这决定了某些名称的名称查找上下文。表达式可以是类型相关的(取决于模板参数的类型)或值相关的(取决于非类型模板参数的值)。

[...]

此类名称是未绑定的,并且在模板定义的上下文和实例化点的上下文中都在模板实例化点 (14.6.4.1) 处查找。

14.6.2.1 依赖类型 [temp.dep.type]

一个名字是一个未知专业化的成员,如果它是

[...]

一个 id 表达式,表示类成员访问表达式(5.2.5) 中的成员,其中任一

——对象表达式的类型为当前实例化,当前实例化至少有一个依赖基类,id-expression的名称查找没有找到当前实例化的成员或其非依赖基类;或者

对象表达式的类型是依赖的,不是当前的实例化

[...]

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

未知专业的成员

4

1 回答 1

1

1

这是我认为您的第一个案例的t.Dependent::f工作方式。首先,我相信(意思是,我不完全确定)14.6.2.1p5 应该说“unqualified-id”而不是“id-expression”。但除此之外,你的名字Dependent::f实际上是由两个名字组成的(在标准中,每个嵌套的nested-name-specifier 后跟一个成员名称称为“qualified-id”,即使在语法上,这些不是qualified-id 产品.所以一个名字foo::bar::baz是一个合格的ID,但也包含其他1个“合格的ID”)。

DependentDependent::f。前者不是“表示类成员访问表达式中成员的 id 表达式”,因此您不能简单地将适用于的规则应用于Dependent::f也适用于Dependent.

Dependent因此是非依赖的,尽管它需要在依赖类型中查找,但必须在定义时找到。我个人认为我们应该有一个子句说“当查找限定符与类型相关的限定ID时,名称查找会产生一个空结果。”,以优雅地处理这些“强制名称查找立即完成” . 所以无论如何,最后,我认为你的第一个案例是不正确的,因为没有找到Dependent(第 3.4 条不能仅在第 14 条的开头自行决定名称实际上是依赖的)。

2

对于您的其他情况operator Dependent,事情会更容易。你又有两个名字,Dependentoperator Dependent。同样,我在这里没有发现任何说这Dependent是一个从属名称(我不确定这是否是错误的。这超出了我的范围)。

运算符函数名称的名称查找比较(例如,名称查找哈希表的相等函数)是“它们是由相同类型形成的转换函数 ID”(3.8)。这意味着,为了形成名称本身(还没有进行名称查找!),您不仅需要像标识符一样提供词汇拼写,而且还必须提供类型标识,这需要由Dependent.

延迟查找依赖的 id 表达式t.operator Dependent*仅意味着延迟了语义类型比较。试试这个,应该可以的

struct Dependent; // forward decl at global scope
t.operator Dependent*(); // in function template

你的跟进

如果打算这样一个名称不依赖,那么这个决定的理由是什么?我可以看到,如果实现者不必推迟对 t.operator X::Dependent* 或 tX::Dependent::f 之类的构造的评估,其中 X 可以是命名空间或类型名称,这会使实现者的生活更轻松.

我不知道理由,但我认为你已经给出了一个很好的观点。这与查找非限定名称时跳过依赖基类的规则非常相似。我认为适用于该案的理由也适用于该案。它使程序员更容易推理函数模板,尤其是。

struct Dependent;

template<typename T>
void f(T t)
{
    t.Dependent::f();
    t.operator Dependent*();
}

代码看起来不错,但如果T碰巧有一个Dependent成员,突然Dependent就会有不同的绑定(因为首先我们被告知要查看t's 类,然后查看周围的范围)。根据我目前对模板规则的理解,上面总是指周围的范围Dependent,所以上面的代码是“安全的”,关于那个陷阱。

于 2013-08-13T22:53:20.327 回答