23

我有一些代码,就这个问题而言,归结为

template<typename T>
class TemplateClass : public T {
 public:
  void method() {}
  template<typename U>
  static void static_method(U u) { u.TemplateClass::method(); }
};

class EmptyClass {};

int main() {
  TemplateClass<TemplateClass<EmptyClass> > c;
  TemplateClass<EmptyClass>::static_method(c);
}

我试图用两个编译器的几个版本来编译它。GCC 4.2、4.4、4.6 毫无怨言地接受它。截至 11 月 14 日的 Clang 2.9 和 SVN 主干拒绝它,并显示以下错误消息:

example.cc:6:38: error: lookup of 'TemplateClass' in member access expression is
      ambiguous
  static void static_method(U u) { u.TemplateClass::method(); }
                                     ^
example.cc:13:3: note: in instantiation of function template specialization
      'TemplateClass<EmptyClass>::static_method<TemplateClass<TemplateClass<EmptyClass>
      > >' requested here
  TemplateClass<EmptyClass>::static_method(c);
  ^
example.cc:2:7: note: lookup in the object type
      'TemplateClass<TemplateClass<EmptyClass> >' refers here
class TemplateClass : public T {
      ^
example.cc:2:7: note: lookup from the current scope refers here
1 error generated.

哪一个是错的?我可以通过更改来解决 Clang

  static void static_method(U u) { u.TemplateClass::method(); }

  static void static_method(U u) { u.TemplateClass<T>::method(); }

但我想对何时可以省略模板参数的理解充满信心。


编辑:我曾认为歧义是在TemplateClass. 以下代码使用 GCC 和 Clang 编译,质疑该假设:

class E {};

template<typename T>
class A : public T {
 public:
  void method() {}
};

int main() {
  A<A<E> > a;
  a.A::method();
}
4

6 回答 6

11

我相信 clang 正确地拒绝了这个代码。

clang 发现的歧义可以用一个不太复杂的例子来重现:

template<typename T>
class TemplateClass {
 public:
  void method() {}
  template<typename U>
  static void static_method(U u) { u.TemplateClass::method(); }                                  
};

struct A {};
struct B {};

int main() {
  TemplateClass<A> c;
  TemplateClass<B>::static_method(c);
}

这里省略了模板中的继承,并使用两个独立的类进行实例化。clang 产生的错误还是一样的。

首先,在TemplateClass<T>名称TemplateClass所指的范围内TemplateClass<T>,由于类名注入。这就是可以使用静态方法TemplateClass::method而不是更显式的TemplateClass<T>::method.

u.TemplateClass::method用于在静态方法中解释的名称查找在 C++11 和 C++98 标准的“3.4.5 类成员访问 [base.lookup.classref]”中定义。

相关部分是3.4.5/4:

如果类成员访问中的id-expression是以下形式的限定 id

class-name-or-namespace-name::...

[...]

这里就是这种情况。id-expression是右边的部分,.在我们的例子中是限定名TemplateClass::method

[...] or运算符后面
类名或命名空间名会在整个后缀表达式的上下文和对象表达式的类范围内进行查找。.->

“整个后缀表达式的范围”是静态函数的主体,在这个静态函数TemplateClass中指的是TemplateClass<B>,因为该函数是该类的成员(我们称为TemplateClass<B>::static_method)。

所以在这个范围内,名称指的是TemplateClass<B>.

.在我们的例子中,“对象表达式”是 的左边部分c。的类cisTemplateClass<A>并且在该类的范围内,TemplateClass指的是TemplateClass<A>.

因此,根据用于查找的范围,名称指的是不同的实体。

该标准现在说:

如果在两个上下文中都找到该名称,则类名或命名空间名应指同一实体。

在我们的程序中不是这种情况。程序格式错误,编译器需要给出诊断信息。

如果您替换为问题中使用的 ,则歧义保持B不变TemplateClass<A>

于 2011-12-10T00:23:08.247 回答
7

在 ISO/IEC 14882:2011(E) 中,“14.6.1 本地声明的名称 [temp.local]”,[#5] 说:

当使用模板的正常名称(即,来自封闭范围的名称,而不是注入的类名)时,它总是指类模板本身,而不是模板的特化。[ 示例:

template<class T> class X {
    X* p;    // meaning X<T>
    X<T>* p2;
    X<int>* p3;
    ::X* p4;    // error: missing template argument list
                // ::X does not refer to the injected-class-name
};
— end example ]

这使我相信,在您的示例u.TemplateClass::method();u.TemplateClass<T>::method();,如果 Clang 在一种情况下给出错误并在另一种情况下干净地编译,那么它就是 Clang 错误。

于 2011-11-14T14:29:10.327 回答
4

当我们调用这两行时:

TemplateClass<TemplateClass<EmptyClass> > c;
TemplateClass<std::string>::static_method(c);

那么类型参数 U 是对象 c 的类型:

TemplateClass<TemplateClass<EmptyClass> >

让我们离开static_method,做一个实验:

#include <iostream>
#include <typeinfo.h>

using namespace std;

template<typename T>
class TemplateClass : public T {
public:
  void method(int i) {
    cout << i << ": ";
    cout << typeid(*this).name() << endl; 
  }
};

class EmptyClass { };

void main() {
  TemplateClass<TemplateClass<EmptyClass> > u;
  u.method(1);
  u.TemplateClass::method(2);
  u.TemplateClass<EmptyClass>::method(3);
  u.TemplateClass<TemplateClass<EmptyClass> >::method(4);
}

输出是:

1: class TemplateClass<class TemplateClass<class EmptyClass> >
2: class TemplateClass<class TemplateClass<class EmptyClass> >
3: class TemplateClass<class EmptyClass>
4: class TemplateClass<class TemplateClass<class EmptyClass> >

在所有四种情况(和内部static_method)中,我们调用,并且在和TemplateClass<T>::method之间给出的类型名称将给出实际的类型 T:u.::

  • Case #1 是默认值,这里的 T 由 u 的声明给出。
  • 案例#4 也是微不足道的。
  • 案例#2 看起来编译器应该已经猜到了 TemplateClass 的类型参数,这在 u 的声明中很简单。
  • Case #3 is very interesting. I guess function type casting happened here, from TemplateClass<TemplateClass<EmptyClass> >::method to TemplateClass<EmptyClass>::method.

I don't know whether this behavior is part of the C++ standard.

EDIT:

Actually case #3 is not casting, these are qualified names. So in conclusion, Clang is not aware of this qualification syntax, while both GCC and Visual C++ 2010 are.

于 2011-11-14T15:30:24.717 回答
2

没有答案,

只是我的小贡献:

删除模板,但保留相同的名称:

结构 A {
    结构模板类 {
        无效方法(){}
    };
};
结构 B {
    结构模板类 {
        无效方法(){}

        静态无效静态方法(A::TemplateClass u){
            u.TemplateClass::method();
        }
    };
};

int main() {
    A::模板类 c;
    B::TemplateClass::static_method(c);
}

用于 ONLINE_EVALUATION_BETA2 的 Comeau C/C++ 4.3.10.1(2008 年 10 月 6 日 11:28:09)
版权所有 1988-2008 Comeau Computing。版权所有。
模式:严格错误 C++ C++0x_extensions

“ComeauTest.c”,第 11 行:错误:不明确的类成员引用 -- 类型
          “B::TemplateClass::TemplateClass”(在第 7 行声明)用于
          偏好键入“A::TemplateClass::TemplateClass”(声明于
          第 2 行)
            u.TemplateClass::method();
              ^

“ComeauTest.c”,第 11 行:错误:限定名称不是类的成员
          “A::TemplateClass”或其基类
            u.TemplateClass::method();
              ^

在“ComeauTest.c”的编译中检测到 2 个错误。

从 N3242

本地声明的名称 [temp.local]

像普通(非模板)类一样,类模板有一个注入类名(第 9 条)。注入的类名可以与模板参数列表一起使用,也可以不与模板参数列表一起使用。当它在没有模板参数列表的情况下使用时,它相当于注入的类名后跟 <> 中包含的类模板的模板参数。

(...)

在类模板特化或部分特化的范围内,当注入的类名后面不带<时,相当于注入的类名后跟类模板特化或部分特化的模板参数括在 <> 中。

(...)

在某些情况下,查找注入类名 (10.2) 的查找可能会导致歧义

于 2011-12-10T04:09:08.600 回答
1

从来没有用过 Clang,我对这个问题很感兴趣。(具有讽刺意味,是的,我知道。)

Clang C++ 兼容性表明其他编译器(特别是 GCC)处理的模板有几件事情会抱怨。这些是标准中定义较弱的东西(“好吧,你不应该允许这个......但你可以”);几乎所有这些都涉及模板。这些都不像你的问题,但它们足够接近以提供信息——当然值得一读。

所以,看起来 Clang 并没有坏掉——只是 Clang 比其他的更挑剔。

于 2011-11-16T19:37:28.640 回答
0

我认为模棱两可是因为TemplateClass在继承中是两次TemplateClass : (TemplateClass : EmptyClass)

u.TemplateClass::method();u.TemplateClass<TemplateClass<EmptyClass> >::method();还是?u.TemplateClass<EmptyClass> >::method();

也许 GCC 有标准的权利,但无论如何你都应该添加<T>.

于 2011-11-11T22:07:08.313 回答