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

Cam*_*ron 34 c++ gcc templates name-lookup c++11

当编译器尝试解析i.template hi<T>();它时hi,在全局命名空间中找到而不是hii(ideone)上找到方法.为什么?

#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;
}
Run Code Online (Sandbox Code Playgroud)

我正在使用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>();
     ^
Run Code Online (Sandbox Code Playgroud)

此代码适用于Clang和VS2013,但在g ++和EDG中生成错误.但哪些编译器是对的?

除了更改会员名称之外,还有什么方法可以解决这个问题吗?在我的真实代码中,当std命名空间中的类型(例如,通过其导入using namespace std)与我的一个成员函数具有相同的名称时,就会发生冲突.显然,我希望我的实现代码是健壮的,不会导致用户代码中的随机名称冲突.

Omn*_*ity 8

据我所知,这是正在发生的事情.

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();
  }
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

这是故意禁止的吗?

拟议决议(4/02):

在14.2 [temp.names]第5段中首次使用"成员"一词,以便其第一句如下:

如果以关键字模板为前缀的名称不是模板的名称,则该程序格式错误.

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

以关键字模板为前缀的名称应为template-id,或者名称应引用类模板.[ 注意:关键字template可能不适用于类模板的非模板成员.-end note ] [ 注意:与typename前缀的情况一样,在template不是绝对必要的情况下允许使用前缀; 即,当嵌套名称说明符或在左侧的表达->.上的不依赖模板的参数,或使用未出现在模板的范围.- 尾注 ]

[ 例如:

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;
Run Code Online (Sandbox Code Playgroud)

- 末端的例子 ]

这个措辞听起来很相似,但不同于挖掘.我发现在N3126草案中,措辞已改为此版本.

我能够将此更改链接回此DR96:

以下是14.2 [temp.names]第4和第5段中的措辞,其中讨论了以下"模板"关键字的使用.或 - >和合格的名称.

{}剪断

此功能的重点在于需要"template"关键字来指示"<"在某些上下文中开始模板参数列表.第5款中的限制可以对某些案件进行辩论.

首先,我认为应该更清楚的是,当在这些上下文中使用"template"关键字时,模板名称必须后跟模板参数列表.如果我们不明确这一点,我们将不得不添加几个语义澄清.例如,如果您说"p-> template f()",而"f"是包含模板和非模板的重载集:a)这是否有效?b)忽略过载集中的非模板?如果用户被迫写"p-> template f <>()",则很明显这是有效的,并且同样清楚的是忽略了重载集中的非模板.由于这个功能纯粹是为了提供句法指导,我认为重要的是它不会产生语义含义.

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

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

在类成员访问表达式(5.2.5)中,如果.->标记后面紧跟着标识符后跟a <,则必须查找标识符以确定它是否<模板参数列表的开头(14.2)或更少 - 运营商.首先在对象表达式的类中查找标识符.如果未找到标识符,则在整个postfix-expression的上下文中查找它,并命名一个类模板.

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

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

总而言之,似乎Clang和MSVC表现出正确的行为,而GCC和EDG在这种情况下并不存在.

我最好的猜测是为什么GCC有错误是在触发规则后选择错误的上下文来分配给表达式.而不是将上下文放在与postfix-expression相同的级别,而不是将其置于事故的全局级别?也许它只是跳过第一个查找步骤?(但这仅仅是猜测,我必须真正弄清楚在GCC源中要查看的位置.)这也可以解释为什么@Mikael Persson将查找更改为合格的解决方案导致编译重新开始.

来自提问者的链接错误报告让人们谈论为什么必须考虑全局范围,并且它应该是,但似乎很直接的是,本地范围匹配必须被赋予比全局范围更高的优先级.最近似乎还有一些活动.

  • 谢谢你的回答!非常深入.我有几个问题,但是:§3.4.5.1说"首先在对象表达式的类中查找标识符.如果没有找到标识符"...... - 这不是说全局只有在找不到类中的本地标识符时才应检查范围?其次,如果查找*是在全局范围内完成的,那么不会完全替换本地查找而不是增加其候选匹配吗?(顺便提一下,关于错误报告的最近活动是由于我对VS接受代码的评论引起的,而且似乎没有任何进展.) (3认同)