当我尝试编译此代码时
// 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)'已经被声明。当我取消注释最顶层的行时,代码编译BTW,但两个声明都在实例化之前,所以这应该没关系。
其次,错误信息本身似乎与我相矛盾。它说“在实例化时没有发现声明”,即foo.cc:10:16,它说它在foo.cc:8:6被宣布为“后来”。对于我所知道的关于数字和英语的所有知识,我会称之为“之前”而不是“之后”。
那么,这是gcc中的错误还是我弄错了?因为在我看来这是一种常见的使用模式,但我不能完全相信。
BTW:当我在MSDN(http://msdn.microsoft.com/en-us/library/dx2zs2ee.aspx)上使用g ++尝试第二个“依赖类型的名称解析”示例时,结果与vc ++不同,后者(通常不是,但在这种特定情况下)会破坏这是g ++中的一个错误。
答案 0 :(得分:10)
tl; dr Foo<int>
没有调用任何ADL,但Foo<X>
会(X
是类类型)。
首先,在此代码中,foobar
是一个从属名称,因为(C ++ 14 / N3936)[temp.dep]/1
表格形式:
postfix-expression ( expression-list opt )
其中postfix-expression是非限定id,unqualified-id表示依赖名称,如果[...]
- 表达式列表中的任何表达式都是依赖于类型的表达式(14.6.2.2),或
和t
是一个从属名称,因为它是声明T t
的一部分,其中T
是模板参数,因此是从属类型。
转移到依赖名称解析,有[temp.dep.res]/1
引入了这样的事实:名称可以在定义上下文和实例化上下文中查找,并定义实例化上下文的位置。为简洁起见,我省略了,但在本例中,template class Foo<int>;
是实例化的重点。
下一位是[temp.dep.candidate]/1
:
对于 postfix-expression 是从属名称的函数调用,使用通常的查找规则(3.4.1,3.4.2)找到候选函数,但:
- 对于使用非限定名称查找(3.4.1)的查找部分,只能找到模板定义上下文中的函数声明。
- 对于使用关联命名空间(3.4.2)的查找部分,只能找到模板定义上下文或模板实例化上下文中找到的函数声明。
最后两部分是&#34;两个阶段&#34;两阶段查找(注意 - 此部分的措辞从C ++ 11更改为C ++ 14,但效果相同)。
在第一阶段3.4.1中,找不到foobar
的名称。
所以我们进入第二阶段。查找名称的实际位置,如3.4.2中所述。文本很长,但这里有两条相关规则:
如果T是基本类型,则其关联的命名空间和类集都是空的。
如果T是类类型(包括联合),则其关联的类是:类本身;它所属的成员,如果有的话;及其直接和间接基类。其关联的命名空间是其关联类的最内部封闭命名空间。 [...]
因此,当您实例化Foo<int>
时,第二阶段的查找不会引入任何其他命名空间来进行搜索。
但是,如果您将示例更改为struct X {};
,然后将int
更改为X
,则代码会编译。这是因为后一个要点:类类型参数的ADL会搜索该类的封闭命名空间(现在是全局命名空间),但是内置类型参数的ADL不会搜索全局命名空间。
答案 1 :(得分:6)
第二阶段查找仅包括不能在第一阶段ADL中应用的名称查找规则。两阶段查找就是这样 - 在第一阶段查找某些名称,在第二阶段查找一些名称。这种特殊类型的名称是第一阶段名称,因为编译器完全能够在第一阶段在函数的名称空间中查找foobar
。
Visual C ++不实现两阶段名称查找。
答案 2 :(得分:1)
对我来说是正确的。虽然重载分辨率仅在阶段2中完成,但在阶段1中,您必须知道foobar(t)
是函数调用表达式。如果foobar
为某个类型命名,则t
甚至不会是一个从属名称。