不明确的成员访问表达式:Clang 是否拒绝有效代码?

发布于 2024-12-15 05:40:27 字数 1774 浏览 0 评论 0 原文

我有一些代码,出于这个问题的目的,归结为

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 trunk 拒绝它并显示以下错误消息:

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

I have some code that, for the purposes of this question, boils down to

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

I've tried to compile it with several versions of two compilers. GCC 4.2, 4.4, 4.6 accept it without complaint. Clang 2.9 and SVN trunk as of November 14 reject it with the following error message:

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.

Which one is wrong? I can work around Clang by changing

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

to

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

but I'd like be confident in my understanding of when it's OK to elide the template parameters.


EDIT: I had thought that the ambiguity was between the two instantiations of TemplateClass. The following code compiles with GCC and Clang, calling that hypothesis into doubt:

class E {};

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

少女净妖师 2024-12-22 05:40:27

我相信 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 范围内,由于类名注入,名称 TemplateClass 引用了 TemplateClass。这就是静态方法可以使用 TemplateClass::method 而不是更显式的 TemplateClass::method 的原因。

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

相关部分是3.4.5/4:

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

类名或命名空间名::...

[...]

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

[...]
.-> 运算符后面的 class-name-or-namespace-name
整个后缀表达式并且在对象表达式的类的范围内。

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

因此在此范围内,名称指的是 TemplateClass

“对象表达式”是 . 左边的部分,在我们的例子中是 cc 的类是 TemplateClass,在该类的范围内,TemplateClass 引用 TemplateClass代码>.

因此,根据查找所使用的范围,该名称引用不同的实体。

现在标准说:

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

我们的程序中并非如此。该程序格式错误,需要编译器给出诊断消息。

如果将 B 替换为 TemplateClass(如问题中所用),歧义性保持不变。

I believe that clang is correctly rejecting this code.

The ambiguity that clang finds can be reproduced with a less complicated example:

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

Here the inheritance in the template is omitted and two independent classes are used for the instantiations. The error produced by clang remains the same.

First of all, in the scope of TemplateClass<T> the name TemplateClass refers to TemplateClass<T>, due to class name injection. This is the reason that the static method can use TemplateClass::method instead of a more explicit TemplateClass<T>::method.

The name lookup used to interpret u.TemplateClass::method in the static method is defined in "3.4.5 Class member access [base.lookup.classref]" of the C++11 and C++98 standards.

The relevant part is 3.4.5/4:

If the id-expression in a class member access is a qualified-id of the form

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

[...]

This is the case here. The id-expression is the part to the right of the . and in our case this is the qualified name TemplateClass::method.

[...]
the class-name-or-namespace-name following the . or -> operator is looked up both in the context of the
entire postfix-expression and in the scope of the class of the object expression.

The "scope of the entire postfix-expression" is the body of the static function, and in this static function TemplateClass refers to TemplateClass<B>, since the function is a member of that class (we called TemplateClass<B>::static_method).

So in this scope the name refers to TemplateClass<B>.

The "object expression" is the part left of ., in our case c. The class of c is TemplateClass<A> and in the scope of this class, TemplateClass refers to TemplateClass<A>.

So, depending on the scope used for the lookup, the name refers to a different entity.

The standard now says:

If the name is found in both contexts, the class-name-or-namespace-name shall refer to the same entity.

This is not the case in our program. The program is ill-formed, and the compiler is required to give a diagnostic message.

The ambiguity stays the same if you replace B with TemplateClass<A>, as used in the question.

半﹌身腐败 2024-12-22 05:40:27

在 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::method(); 如果 Clang 在一种情况下给出错误,而在另一种情况下编译干净,那么它就是一个 Clang 错误。

In ISO/IEC 14882:2011(E), "14.6.1 Locally declared names [temp.local]", [#5] says:

When the normal name of the template (i.e., the name from the enclosing scope, not the injected-class-name)
is used, it always refers to the class template itself and not a specialization of the template.[ Example:

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 ]

This leads me to believe that in your example u.TemplateClass::method(); is equivalent to u.TemplateClass<T>::method(); and if Clang gives an error in one case and compiles cleanly in the other case, then it's a Clang error.

幻梦 2024-12-22 05:40:27

当我们调用这两行时:

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::method,并且在 u.:: 之间给出的类型名称将给出实际类型 T:

  • Case #1 是默认值,这里 T 由 u 的声明给出。
  • 案例 #4 也很简单。
  • 情况 #2 看起来好像编译器应该猜测 TemplateClass 的类型参数,这就是 u 声明中给出的类型参数。
  • 案例#3 非常有趣。我猜函数类型转换发生在这里,来自 TemplateClass >::methodTemplateClass::method

我不知道这种行为是否是 C++ 标准的一部分。

编辑:

实际上情况 #3 不是强制转换,这些是限定名称。总之,Clang 不知道这种限定语法,而 GCC 和 Visual C++ 2010 都知道。

When we call these two lines:

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

then the type argument U is the type of the object c:

TemplateClass<TemplateClass<EmptyClass> >

Let's leave static_method, and do an experiment:

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

The output is:

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> >

In all four cases (and inside static_method) we call TemplateClass<T>::method, and the type name given between u. and :: will give the actual type T:

  • Case #1 is the default, here T is given by the declaration of u.
  • Case #4 is also trivial.
  • Case #2 looks as if the compiler should have guessed the type argument of TemplateClass, which is trivially the one given in the declaration of 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.

痴梦一场 2024-12-22 05:40:27

不是答案,

只是我的小贡献:

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

struct A {
    struct TemplateClass {
        void method() {}
    };
};
struct B {
    struct TemplateClass {
        void method() {}

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

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

给出

Comeau C/C++ 4.3.10.1 (Oct  6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing.  All rights reserved.
MODE:strict errors C++ C++0x_extensions

"ComeauTest.c", line 11: error: ambiguous class member reference -- type
          "B::TemplateClass::TemplateClass" (declared at line 7) used in
          preference to type "A::TemplateClass::TemplateClass" (declared at
          line 2)
            u.TemplateClass::method(); 
              ^

"ComeauTest.c", line 11: error: qualified name is not a member of class
          "A::TemplateClass" or its base classes
            u.TemplateClass::method(); 
              ^

2 errors detected in the compilation of "ComeauTest.c".

From N3242

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

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

(...)

在类模板特化或部分特化的范围内,当注入类名后面没有 < 时,相当于注入类名后面跟着类模板的模板参数特化或部分特化包含在 <> 中。

(...)

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

Not an answer,

just my small contribution:

Removing templates, but keeping the same names:

struct A {
    struct TemplateClass {
        void method() {}
    };
};
struct B {
    struct TemplateClass {
        void method() {}

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

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

gives

Comeau C/C++ 4.3.10.1 (Oct  6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing.  All rights reserved.
MODE:strict errors C++ C++0x_extensions

"ComeauTest.c", line 11: error: ambiguous class member reference -- type
          "B::TemplateClass::TemplateClass" (declared at line 7) used in
          preference to type "A::TemplateClass::TemplateClass" (declared at
          line 2)
            u.TemplateClass::method(); 
              ^

"ComeauTest.c", line 11: error: qualified name is not a member of class
          "A::TemplateClass" or its base classes
            u.TemplateClass::method(); 
              ^

2 errors detected in the compilation of "ComeauTest.c".

From N3242

Locally declared names [temp.local]

Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injected class-name can be used with or without a template-argument-list. When it is used without a template-argument-list, it is equivalent to the injected-class-name followed by the template-parameters of the class
template enclosed in <>.

(...)

Within the scope of a class template specialization or partial specialization, when the injected-class-name is not followed by a <, it is equivalent to the injected-class-name followed by the template-arguments of the class template specialization or partial specialization enclosed in <>.

(...)

A lookup that finds an injected-class-name (10.2) can result in an ambiguity in certain cases

慵挽 2024-12-22 05:40:27

由于从未使用过 Clang,我对这个问题非常感兴趣。 (具有讽刺意味的是,是的,我知道。)

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

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

Having never used Clang, I was quite interested in this problem. (Ironic, yes I know.)

Clang C++ Compatibility indicates that there are several things regarding templates that other compilers (notably GCC) process that it will complain about. These are things that are weakly defined in the standard ("well, you shouldn't allow this ... but you can"); nearly all of them involve templates. None of these exactly look like your problem, but they're close enough to be informative -- and certainly worth a read.

So, it doesn't look like Clang is broken -- it's just that Clang is pickier than the others.

佞臣 2024-12-22 05:40:27

我认为歧义是因为 TemplateClass 在继承中是两次 TemplateClass : (TemplateClass : EmptyClass)

是否 u.TemplateClass::method();意思是 u.TemplateClass >::method();u.TemplateClass; >::method();

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

I think the ambiguity is because TemplateClass is twice in the inheritance TemplateClass : (TemplateClass : EmptyClass)

Does u.TemplateClass::method(); mean u.TemplateClass<TemplateClass<EmptyClass> >::method(); or u.TemplateClass<EmptyClass> >::method(); ?

Perhaps GCC has the standard right, but whatever the case is you should add the <T>.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文