关于c ++:是否应该在class-member-access表达式中为依赖类/名称空间名称延迟名称查找?

Should name lookup be deferred for a dependent class/namespace-name in a class-member-access expression?

Clang和GCC均拒绝以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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++的工作草案N33 37的相关引文:

3.4.5 Class member access [basic.lookup.classref]

If the id-expression in a class member access is a qualified-id of the form
class-name-or-namespace-name::...
the class-name-or-namespace-name following the . or -> operator is first looked up in the class of the
object expression and the name, if found, is used. Otherwise it is looked up in the context of the entire
postfix-expression. [ Note: See 3.4.3, which describes the lookup of a name before ::, which will only find a
type or namespace name. —end note ]

If the id-expression is a conversion-function-id, its conversion-type-id is first looked up in the class of the
object expression and the name, if found, is used. Otherwise it is looked up in the context of the entire
postfix-expression. In each of these lookups, only names that denote types or templates whose specializations
are types are considered.

14.6.2 Dependent names [temp.dep]

Inside a template, some constructs have semantics which may differ from one instantiation to another. Such a
construct depends on the template parameters. In particular, types and expressions may depend on the type
and/or value of template parameters (as determined by the template arguments) and this determines the
context for name lookup for certain names. Expressions may be type-dependent (on the type of a template
parameter) or value-dependent (on the value of a non-type template parameter).

[...]

Such names are unbound and are looked up at the point of the template instantiation (14.6.4.1) in both the
context of the template definition and the context of the point of instantiation.

14.6.2.1 Dependent types [temp.dep.type]

A name is a member of an unknown specialization if it is

[...]

— An id-expression denoting the member in a class member access expression (5.2.5) in which either

— the type of the object expression is the current instantiation, the current instantiation has at least
one dependent base class, and name lookup of the id-expression does not find a member of the
current instantiation or a non-dependent base class thereof; or

— the type of the object expression is dependent and is not the current instantiation.

[...]

A type is dependent if it is

— a member of an unknown specialization,


这是我认为你的第一个案例,t.Dependent::f起作用。首先,我认为(意思是,我不完全确定)14.6.2.1P5应该说"不合格ID",而不是"ID表达式"。但与此无关,您的名称Dependent::f实际上由两个名称组成(在标准中,每个嵌套的名称说明符后跟一个成员名称,称为"限定ID",即使在语法上,这些都不是限定ID产生。因此,名称foo::bar::baz是一个限定ID,但也包含1个其他"限定ID"。

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

因此,Dependent是不依赖的,尽管它需要在定义时在依赖类型中查找。我个人认为,我们应该有这样一个子句:"当查找限定符依赖于类型的限定ID时,名称查找会产生一个空结果。"要优雅地处理这些"强制立即执行名称查找"。所以不管怎样,最后,我认为你的第一个案例是由于没有找到Dependent(第3.4条不能仅仅通过第14条的开头来决定这个名字实际上是从属的)。

对于你的另一个例子,operator Dependent,情况更容易。你还有两个名字,Dependentoperator Dependent。我又一次发现这里没有说Dependent是一个独立的名字(我不确定这是否是错误的)。那是我无法想象的。

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

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

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

你的追随者

If it is intended that such a name is not dependent, what is the rationale for this decision? I can see that it makes life easier for the implementor if they do not have to defer evaluation of a construct like t.operator X::Dependent* or t.X::Dependent::f where X could be either a namespace or a type name.

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

1
2
3
4
5
6
7
8
struct Dependent;

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

代码看起来很好,但是如果T碰巧有一个Dependent成员,那么Dependent会突然有不同的绑定(因为我们首先被告知查看T的类,然后查看周围的范围)。在我目前对模板化规则的理解下,上面的代码总是指周围范围的Dependent,因此对于这个陷阱,上面的代码是"安全的"。