Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

When the compiler tries to resolve i.template hi<T>(); it finds hi in the global namespace instead of the method hi on i (ideone). Why?

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

I'm using g++ 4.9.1/4.9.2 (-std=c++11). The exact error message:

prog.cpp: In member function 'void Foo<T>::sayStuff()': prog.cpp:19:5: error: invalid use of 'struct hi<T>'    i.template hi<T>();      ^ 

This code works fine with Clang and VS2013, but generates an error in g++ and EDG. But which compilers are right?

Is there any way to resolve this besides changing the name of the member? In my real code, the conflict arises when a type from the std namespace (that's been imported via using namespace std, say) has the same name as one of my member functions. Obviously I'd like my implementation code to be robust and not cause random name clashes in user code.

like image 560
Cameron Avatar asked Jan 13 '15 19:01

Cameron


People also ask

What is argument dependent lookup why it is useful?

Argument-dependent lookup, also known as ADL, or Koenig lookup, is the set of rules for looking up the unqualified function names in function-call expressions, including implicit function calls to overloaded operators.

What is dependent name?

A dependent name is a name that depends on the type or the value of a template parameter. For example: template<class T> class U : A<T> { typename T::B x; void f(A<T>& y) { *y++; } }; The dependent names in this example are the base class A<T> , the type name T::B , and the variable y .


1 Answers

To the best of my knowledge here's what's going on.

DR228 says:

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

In a way this makes perfect sense: X is found to be a template using ordinary lookup even though p has a dependent type. However, I think this makes the use of the template prefix even harder to teach.

Was this intentionally outlawed?

Proposed Resolution (4/02):

Elide the first use of the word "member" in 14.2 [temp.names] paragraph 5 so that its first sentence reads:

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

However, in the most current publicly available draft of the C++ standard N4296 the following wording appears in §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]

This wording sounded similar, but different enough to go digging. I found that in the N3126 draft the wording was changed to this version.

I was able to link this change back to this 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.

Essentially, the very subtle change of DR228 was lost during a subsequent revision; however, because no similar restriction was placed the intent of DR228 probably still holds unless there's been another revision in the standard. This means that template lookup has to occur globally in this case, even though it's a dependent type.

Let's look at our name lookup rules §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.

This seems to explicitly state that in baz.foo->template bar<T>(); We will first look in the class context, this includes standard template lookup. After that is done and if nothing is found, if the form of the expression is correct we jump to the context of the entire expression. In essence, it has been promoted and the lookup for that name must perform the same way if the line just read template bar<T>(); Really though we already knew this from DR228. I just wanted to double check and confirm. The real question is which template ought to get priority, the one in the global scope or the one in the class scope.

For that we now need to ask unqualified name lookup, because now bar is being considered in the same context as foo, so it's no longer following member lookup rules, it's following normal, unqualified template lookup rules, which, naturally, prefer the local version.

So in summation, it seems that Clang and MSVC exhibit correct behavior, and GCC and EDG do not in this instance.

My best guess as to why GCC has it wrong is choosing the wrong context to assign to the expression after the rule is triggered. Instead of placing the context at the same level as the postfix-expression it may be just placing it at in the global level on accident? Maybe it simply skips the first lookup step? (But this is merely speculation, I'd have to actually figure out where to look in the GCC source to say.) This would also explain why @Mikael Persson's solution of changing the lookup to a qualified one caused the compile to start again.

The linked bug report from the asker has people talking about why the global scope must be considered, and it ought to be, but it seems pretty straight forward that a local scope match must be given higher priority than the global one. It also seems like there's been a bit of activity there recently.

like image 157
OmnipotentEntity Avatar answered Oct 12 '22 09:10

OmnipotentEntity