【问题标题】:Should name lookup be deferred for a dependent class/namespace-name in a class-member-access expression?是否应该为类成员访问表达式中的依赖类/命名空间名称延迟名称查找?
【发布时间】:2013-08-15 14:57:10
【问题描述】:

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::f 之类的构造的评估,其中 X 可以是命名空间或类型名称,这会使他们的生活更轻松。我不清楚这是当前措辞的有意还是无意的副作用。

来自 C++ 工作草案 N3337 的相关引述:

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

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

如果id-expression是一个conversion-function-id,它的conversion-type-id是首先在 对象表达式和名称(如果找到)将被使用。否则在整个上下文中查找 后缀表达式。在这些查找中的每一个中,只有表示其特化的类型或模板的名称 是类型被考虑

14.6.2 从属名称 [temp.dep]

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

[...]

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

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

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

[...]

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

——对象表达式的类型是当前实例化,当前实例化至少有 一个依赖基类,并且 id-expression 的名称查找未找到 当前实例化或其非依赖基类;或

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

[...]

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

未知专业的成员

【问题讨论】:

  • 在第一行中,Dependent 是一个类型或命名空间名称。在第二行中,Dependent 是一个类型名称。
  • 我有理由确定 Dependent 是一个非依赖名称。
  • @willj:在第一行中,您认为t.Namespace::ft.Type::f 是什么意思?
  • @dribeas:添加了模板的实例化,以澄清(并证明至少有一个有效的实例化)
  • 我不确定这是否相关,但之前的规则 (C++03) 不允许 t.operator Dependent*();。变化见DR1111的分辨率

标签: c++ templates language-lawyer


【解决方案1】:

1

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

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

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

2

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

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

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

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 的类,然后查看周围范围)。根据我目前对模板规则的理解,上面总是指周围范围的Dependent,所以上面的代码是“安全的”,关于那个陷阱。

【讨论】:

  • 我已经编辑了示例以添加一个 typedef(如果启用),它将根据您的结论使代码符合要求。我还问了一个后续问题。
  • 不确定,但 3.4.3.1/1 声称 conversion-function-id 的 conversion-type-id 的查找方式与a class member access then 3.4.5/7 如果id-expression是conversion-function-id,首先在object expression的class和name中查找它的conversion-type-id ,如果找到,则使用。 -- 这似乎表明在operator Dependent 中查找Dependent 应该从decltype(t) 内部开始。虽然我看不到一个声明它是依赖的子句,但这似乎是一个强烈的暗示,它是/应该是。 [...]
  • [...] 注意operator X是一个名字很奇怪的成员函数,所以我不认为它应该被认为与t.c_str()不同
猜你喜欢
  • 1970-01-01
  • 2013-08-14
  • 2015-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多