是否应该在类成员访问表达式中为依赖类/名称空间名称延迟名称查找?

时间:2013-08-13 20:19:35

标签: c++ templates language-lawyer

以下代码被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 ++ Working Draft N3337的相关引文:

  

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

     

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

     

如果id-expression是一个转换函数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的名称查找找不到该成员   当前实例化或其非依赖基类;或

     

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

     

[...]

     

类型取决于

     

- 未知专业化的成员

1 个答案:

答案 0 :(得分:1)

1

以下是我认为您的第一个案例t.Dependent::f的工作原理。首先,我相信(意思是,我不完全确定)14.6.2.1p5应该说“unqualified-id”而不是“id-expression”。但独立于此,您的名称Dependent::f实际上由两个名称组成(在标准中,每个嵌套的嵌套名称说明符后跟成员名称称为“qualified-id”,即使在语法上,这些都是不是qualified-id制作。所以名称foo::bar::baz是一个限定id,但也包含另外一个“qualified-id”。

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

Dependent因此是非依赖的,虽然需要在依赖类型中查找,但必须在定义时找到它。我个人认为我们应该有一个子句说“当查找限定符依赖于类型的限定符时,名称查找会产生一个空结果。”,要优雅地处理这些“强制名称查找立即执行” 。所以无论如何,最后,我认为你的第一个案例是由于没有找到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会有不同的绑定(因为我们首先要告知{{1} }的类,然后进入周围的范围)。根据我目前对模板规则的理解,上面总是指周围范围的t,所以上面的代码是“安全的”,关于这个陷阱。