在模板实例化中查找从属名称C++

Lookup of dependent names in C++ template instantiation

本文关键字:C++ 查找 实例化      更新时间:2023-10-16

当我尝试编译此代码时

// void foobar(int); 
template <class T>
struct Foo {
  void bar(T t) { foobar(t); };
};
void foobar(int);
template class Foo<int>;

使用 g++ 4.8.2 我收到以下错误消息

foo.cc: In instantiation of ‘void Foo<T>::bar(T) [with T = int]’:
foo.cc:10:16:   required from here
foo.cc:5:27: error: ‘foobar’ was not declared in this scope, and no 
             declarations were found by argument-dependent lookup at 
               the point of instantiation [-fpermissive]
   void bar(T t) { foobar(t); };
                           ^
foo.cc:8:6: note: ‘void foobar(int)’ declared here, later in the translation unit
 void foobar(int);
      ^

(对于 clang 3.4,它几乎相同)。

首先,我认为代码是正确的,应该编译,因为 foobar 是模板声明中的依赖名称,只有在模板实例化时才应在第二阶段查找。在最后一行完成此操作时,已经声明了"foobar(int)"。顺便说一句,当我取消注释最上面的行时,代码会编译,但两个声明都在实例化之前,所以这无关紧要。

其次,错误消息本身对我来说似乎是矛盾的。它说"在instiiation点没有找到声明",这是foo.cc:10:16,它说它在foo.cc:8:6上声明"稍后"。对于我对数字和英语的了解,我会称之为"之前"而不是"以后"。

那么,这是 gcc 中的错误还是我弄错了什么?然而,由于在我看来这是一种常见的使用模式,我不太相信。

顺便说一句:当我使用 g++ 在 MSDN (http://msdn.microsoft.com/en-us/library/dx2zs2ee.aspx) 上尝试第二个"依赖类型的名称解析"示例时,结果与 vc++ 不同,后者(不是一般的,但在这种特定情况下)会破坏这是 g++ 中的一个错误。

tl;dr Foo<int> 不会调用任何 ADL,但Foo<X>会(其中 X 是一种类类型)。

<小时 />

首先,在此代码中,由于 (C++14/N3936) [temp.dep]/1foobar是一个依赖名称

在以下形式的表达式中:

postfix-expression ( expression-list opt )

其中后缀表达式是非限定 id,非限定 id 表示从属名称,如果 [...]

    表达式
  • 列表中的任何表达式都是与类型相关的表达式 (14.6.2.2),或者

t 是依赖名称,因为它是声明T t的一部分,其中 T 是模板参数,因此是依赖类型。

转到依赖名称解析,[temp.dep.res]/1引入了一个事实,即可以在定义上下文和实例化上下文中查找名称,并定义实例化上下文的位置。为了简洁起见,我省略了这一点,但在此示例中,实例化template class Foo<int>;点。

下一位是[temp.dep.candidate]/1

对于后缀表达式是依赖名称的函数调用,可以使用通常的查找规则(3.4.1、3.4.2)找到候选函数,但以下情况除外:

  • 对于使用非限定名称查找 (3.4.1) 的查找部分,仅找到模板定义上下文中的函数声明。
  • 对于使用关联命名空间 (3.4.2) 的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明。

最后两部分是两阶段查找的"两个阶段"。 (注意 - 本节的措辞从 C++11 更改为 C++14,但效果相同)。

在第一阶段 3.4.1 中,找不到 foobar 的名称。

<小时 />

因此,我们进入第二阶段。查找名称的实际位置,如 3.4.2 中所述。 文本很长,但这里有两个相关规则:

  • 如果 T 是基本类型,则其关联的命名空间和类集均为空。

  • 如果 T 是一个类类型(包括联合),则其关联的类是:类本身;它是其成员的类(如果有的话);及其直接和间接基类。其关联的命名空间是其关联类的最内层封闭命名空间。[...]

因此,当您实例化Foo<int> 时,查找的第二阶段不会引入任何其他命名空间进行搜索。

但是,如果将示例更改为具有struct X {};,然后将int更改为 X 所有位置,则代码确实可以编译。这是因为后一个要点:类类型参数的 ADL 会搜索该类的封闭命名空间(现在是全局命名空间),但是内置类型参数的 ADL 不会搜索全局命名空间。

第二阶段查找仅包括无法在第一阶段(例如 ADL)中应用的名称查找规则。两阶段查找正是这样 - 一些名称在第一阶段查找,一些名称在第二阶段查找。这种特殊类型的名称是第一阶段的名称,因为编译器完全能够在第一阶段在函数的命名空间中查找foobar

可视C++不实现两阶段名称查找。

对我来说

看起来是正确的。虽然重载解析仅在阶段 2 中完成,但在阶段 1 中,您已经必须知道 foobar(t) 是一个函数调用表达式。如果foobar命名一个类型,t甚至不会是一个依赖名称。