为什么这个依赖名称查找找到的是全局标识符而不是方法?

Why does this dependent name lookup find a global identifier instead of the method?

当编译器尝试解析 i.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 说:

[Voted into WP at April 2003 meeting.]

Consider the following example:

template<class T>
struct X {
   virtual void f();
};

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

This is an error because X is not a member template; 14.2 [temp.names] paragraph 5 says:

If a name prefixed by the keyword template is not the name of a member template, the program is ill-formed.

在某种程度上,这是完全有道理的:即使 p 具有依赖类型,X 也被发现是使用普通查找的模板。但是,我认为这使得模板前缀的使用更加难以教授。

这是故意取缔的吗?

提议的决议 (4/02):

删除第 14.2 [temp.names] 段第 5 段中首次使用的单词 "member",使其第一句为:

If a name prefixed by the keyword template is not the name of a template, the program is ill-formed.

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

A name prefixed by the keyword template shall be a template-id or the name shall refer to a class template. [Note: The keyword template may not be applied to non-template members of class templates. —end note] [Note: As is the case with the typename prefix, the template prefix is allowed in cases where it is not strictly necessary; i.e., when the nested-name-specifier or the expression on the left of the -> or . is not dependent on a template-parameter, or the use does not appear in the scope of a template. —end note]

[Example:

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;

—end example]

这个措辞听起来很相似,但差异很大,值得深入挖掘。我发现 N3126 草案中的措辞已更改为此版本。

我能够 link 将此更改回此 DR96:

The following is the wording from 14.2 [temp.names] paragraphs 4 and 5 that discusses the use of the "template" keyword following . or -> and in qualified names.

{snip}

The whole point of this feature is to say that the "template" keyword is needed to indicate that a "<" begins a template parameter list in certain contexts. The constraints in paragraph 5 leave open to debate certain cases.

First, I think it should be made more clear that the template name must be followed by a template argument list when the "template" keyword is used in these contexts. If we don't make this clear, we would have to add several semantic clarifications instead. For example, if you say "p->template f()", and "f" is an overload set containing both templates and nontemplates: a) is this valid? b) are the nontemplates in the overload set ignored? If the user is forced to write "p->template f<>()" it is clear that this is valid, and it is equally clear that nontemplates in the overload set are ignored. As this feature was added purely to provide syntactic guidance, I think it is important that it otherwise have no semantic implications.

从本质上讲,DR228 的细微变化在随后的修订中丢失了;但是,由于没有类似的限制,DR228 的意图可能仍然有效,除非标准中有另一次修订。这意味着在这种情况下模板查找必须全局发生,即使它是一个依赖类型。

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

In a class member access expression (5.2.5), if the . or -> token is immediately followed by an identifier followed by a <, the identifier must be looked up to determine whether the < is the beginning of a template argument list (14.2) or a less-than operator. The identifier is first looked up in the class of the object expression. If the identifier is not found, it is then looked up in the context of the entire postfix-expression and shall name a class template.

这似乎明确指出在 baz.foo->template bar<T>(); 中我们将首先查看 class 上下文,这包括标准模板查找。完成之后,如果没有找到任何内容,如果表达式的形式正确,我们将跳转到整个表达式的上下文。本质上,它已被提升,并且如果该行只是读取 template bar<T>(); ,那么对该名称的查找必须以相同的方式执行,尽管我们已经从 DR228 中知道了这一点。我只是想仔细检查并确认。真正的问题是哪个模板应该获得优先权,全局范围内的还是 class 范围内的。

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

总而言之,似乎 Clang 和 MSVC 表现出正确的行为,而 GCC 和 EDG 在这种情况下没有。

我对 GCC 错误原因的最佳猜测是在触发规则后选择了错误的上下文分配给表达式。不是将上下文放在与后缀表达式相同的级别,而是可能只是偶然地将它放在全局级别?也许它只是跳过了第一个查找步骤? (但这仅仅是猜测,我必须真正找出在 GCC 源代码中查找的位置。)这也可以解释为什么@Mikael Persson 将查找更改为合格查找的解决方案导致编译重新开始。

提问者的 linked 错误报告让人们谈论为什么必须考虑全局范围,而且应该考虑,但看起来很简单,必须给出更高的本地范围匹配优先级高于全局。最近似乎也有点activity。