34

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

#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)与我的成员函数之一具有相同的名称时,就会出现冲突。显然,我希望我的实现代码是健壮的,并且不会导致用户代码中的随机名称冲突。

4

1 回答 1

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();
  }
};

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

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

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

这是故意取缔的吗?

提议的决议(4/02):

省略 14.2 [temp.names] 第 5 段中“成员”一词的首次使用,使其第一句为:

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

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

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

[示例

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 段中讨论在 . 或 -> 和限定名称。

{剪辑}

此功能的全部意义在于,需要“模板”关键字来指示“<”在某些上下文中开始模板参数列表。第 5 段中的限制为某些案例的辩论留出了余地。

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

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

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

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

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

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

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

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

来自提问者的链接错误报告让人们谈论为什么必须考虑全局范围,并且应该考虑,但似乎很简单,必须给予局部范围匹配比全局匹配更高的优先级。最近那里似乎也有一些活动。

于 2015-01-19T09:29:08.130 回答