为什么这个从属名称查找find一个全局标识符而不是方法?

当编译器试图parsingi.template hi<T>(); 它发现hi在全球命名空间,而不是在i ( ideone )的方法hi 。 为什么?

 #include <cstdio> // Define 'hi' and 'bye' in the global namespace; these should *not* be used template<typename T> struct hi { }; template<typename T> struct bye { }; // Foo needs to be templated for Foo::Inner to be a dependent type (I think) template<typename T> struct Foo { struct Inner { // This is a plain-old templated member function of Inner, yes? template<typename U> void hi() { std::printf("hi!\n"); } // This is a plain-old member function of Inner void bye() { std::printf("bye!\n"); } }; void sayStuff() { Inner i; i.template hi<T>(); // Fails to compile -- finds global hi instead of member i.bye(); // Compiles fine, finds member } }; int main() { Foo<int> f; f.sayStuff(); return 0; } 

我使用的是g ++ 4.9.1 / 4.9.2( -std=c++11 )。 确切的错误信息:

 prog.cpp: In member function 'void Foo<T>::sayStuff()': prog.cpp:19:5: error: invalid use of 'struct hi<T>' i.template hi<T>(); ^ 

这段代码和Clang和VS2013一起工作良好,但在g ++和EDG中产生错误。 但是哪些编译器是正确的?

除了改变成员的名字,还有什么办法可以解决吗? 在我的真实代码中,当std命名空间(通过using namespace std导入的)与我的成员函数之一具有相同的名称时,会出现冲突。 很显然,我想我的实现代码是健壮的,不会导致用户代码中的随机名称冲突。

据我所知,这是怎么回事。

DR228说:

[在2003年4月的会议上投票到WP]

考虑下面的例子:

 template<class T> struct X { virtual void f(); }; template<class T> struct Y { void g(X<T> *p) { p->template X<T>::f(); } }; 

这是一个错误,因为X不是成员模板; 14.2 [temp.names]第5段说:

如果以关键字模板为前缀的名称不是成员模板的名称,则该程序是格式不正确的。

从某种意义上说,这是非常有意义的:尽pipep有一个依赖types,X仍然是普通的查找模板。 但是,我认为这使得使用模板前缀更难以教授。

这是故意被禁止的吗?

build议的决议(4/02):

在14.2 [temp.names]第5段中首先使用“member”这个词,这样它的第一句话就是:

如果以关键字模板为前缀的名称不是模板的名称,则该程序是格式不正确的。

但是,在C ++标准N4296的最新公开草案中,在§14.2.5中出现了以下措词:

以关键字模板为前缀的名称应该是模板标识,或者该名称应该指类模板。 [ 注意 :关键字template可能不适用于类模板的非模板成员。 – 注意 ] [ 注意 :与typename前缀的情况一样,如果不是绝对必要的,则允许使用template前缀。 即,当嵌套名称说明符->或左侧的expression式. 不依赖于模板参数 ,或者使用不会出现在模板的范围内。 – 注意 ]

[ 例如

 template <class T> struct A { void f(int); template <class U> void f(U); }; template <class T> void f(T t) { A<T> a; a.template f<>(t); // OK: calls template a.template f(t); // error: not a template-id } template <class T> struct B { template <class T2> struct C { }; }; // OK: T::template C names a class template: template <class T, template <class X> class TT = T::template C> struct D { }; D<B<int> > db; 

– 例子 ]

这个措辞听起来很相似,但不同的是要挖掘。 我发现在N3126草案中,措辞改为这个版本。

我能够将这个变化链接回这个DR96 :

以下是14.2 [temp.names]段落4和5中的措辞,讨论了以下使用“template”关键字。 或 – >和合格的名称。

{}剪断

这个特性的全部意思是说,需要使用“template”关键字来指示“<”在特定的上下文中开始模板参数列表。 第5段中的限制可以对某些案件进行辩论。

首先,我认为应该更清楚的是,在这些上下文中使用“模板”关键字时,模板名称必须跟有模板参数列表。 如果我们没有说清楚,我们就不得不增加几个语义澄清。 例如,如果你说“p-> template f()”,并且“f”是包含模板和非模板的重载集:a)这是否有效? b)忽略过载集合中的非模板? 如果用户被迫写“p-> template f <>()”,那么显然这是有效的,并且同样清楚的是,忽略超载集合中的非模板。 由于此function纯粹是为了提供语法指导而添加的,因此我认为重要的是否则它没有语义影响。

本质上,DR228的微妙变化在随后的修订中丢失了; 但是,由于没有类似的限制,DR228的意图可能仍然存在,除非在标准中另有修订。 这意味着在这种情况下,模板查找必须在全局范围内进行,即使它是依赖types。

让我们来看看我们的名字查找规则§3.4.5.1:

在类成员访问expression式(5.2.5)中,如果是.->标记之后紧跟着一个标识符,后跟一个< ,则必须查找标识符以确定<是否是模板参数列表(14.2)或小于运算符的开始。 标识符首先在对象expression式的类中查找。 如果找不到标识符,则在整个后缀expression式的上下文中查找标识符,并命名类模板。

这似乎明确指出,在baz.foo->template bar<T>(); 我们将首先查看类的上下文,这包括标准模板查找。 之后完成,如果没有发现,如果expression式的forms是正确的,我们跳转到整个expression式的上下文。 从本质上讲,它已经被提升,并且如果该行刚刚读取template bar<T>();则该名称的查找必须以相同的方式执行template bar<T>(); 真的,虽然我们已经知道这个从DR228。 我只是想仔细检查和确认。 真正的问题是哪个模板应该得到优先考虑,哪个模板应该在全局范围内,哪个模板在类范围内。

为此,我们现在需要询问非限定名称查找,因为现在bar在与foo相同的上下文中被考虑,所以它不再遵循成员查找规则,它遵循正常的,不合格的模板查找规则,当然,它们更喜欢本地版本。

所以总而言之,Clang和MSVC显示出正确的行为,而GCC和EDG不在这种情况下。

我最好的猜测,为什么海湾合作委员会有错误的是select错误的上下文分配给规则触发后的expression式。 与其将上下文置于与后缀expression式相同的级别上,不如将其置于全局级别的意外事件中? 也许它只是跳过第一个查找步骤? (但是这只是推测,我不得不实际弄清楚在GCC源代码中的位置)。这也解释了为什么@Mikael Persson将查找改为合格的解决scheme导致编译再次开始。

来自提问者的链接错误报告让人们在谈论为什么必须考虑全球范围,而且应该是这样,但是看起来相当直接的是,本地范围匹配必须比全球范围匹配更重要。 最近似乎也有一些活动。