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

1

34

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.

Mantling answered 13/1, 2015 at 19:56 Comment(21)
Seems like a regression in gcc4.9. clangs compiles your code successfully too.Heliotaxis
It doesn't compile for me even in g++ 4.8.1 (same message as in 4.9.1). Everything's fine with clang 3.5 though.Brufsky
@Jiří: Hmm, you're right. My real code (from which this is derived) works with g++ 4.8.1, but not this specific example. Weird!Mantling
As a way to fix it, the explicit version seems to work fine: i.Inner::template hi<T>();. But that doesn't really explain why this doesn't work, and I think it should work. Maybe file a bug report with GCC and see what they think of it.Funk
@Mikael: Ah, I like that workaround, thanks. I didn't know you could do that :-) I'll wait a bit before filing a bug to see if anyone better versed in the standard than I can figure out what should be happening.Mantling
You also found a bug in Clang: Clang doesn't require the template keyword, though Inner is clearly a dependent type according to [temp.dep.type]/(8.3) (and the template keyword is required through [temp.dep.expr]/(3.1) and [temp.names]/4).Synchronous
@Columbo: That's debatably a feature, I suppose, even if it's technically incorrect :-) VS has done the same for years (VS2013 also compiles this code both with and without the template). It would be nice if all the compilers agreed though, of course.Mantling
Just another data point: EDG rejects the code. The error message doesn't seem to make much sense, though.Roundtheclock
@Dietmar: Ah, thanks for that. That's really interesting, actually, as Visual Studio doesn't generate any intellisense errors with this code (VS uses an EDG-based front-end for its onb-the-fly error highlighting).Mantling
If you do the Name Lookup dance for your code, ordinary lookup will only find the two class templates hi and bye. Because Foo itself is a class template, the name lookup for the Inner member function template hi and member function bye are done at the point of instantiation. Somehow, gcc and clang resolve this differently. ` [basic.lookup.unqual]/3` has something to say about different syntactic forms and name lookup, but not in the same context as your code. You'd best submit a DR to gcc.Fervent
@Synchronous commenting out the two outer class templates, clang does require the template keyword. The name lookup for this code is rather intricate and I'm not sure which compiler gets it right (though my money would be on clang).Fervent
@TemplateRex: Hmm, this looks an awful lot like a duplicate of this bug report (resolved as 'invalid'). I don't quite follow the reasoning of why it's supposedly correct behaviour, though. And what exactly is a DR? ("Defect Report"?)Mantling
I think the gcc bug report is identical to your code. I am very confused however about why the bug was closed as invalid, as Anthony Williams' post seems to indicate the opposite. Perhaps @JonathanWakely can chime in. (and btw, DR is a Defect Report for the C++ Standard, I misused that term in my earlier comment).Fervent
@Synchronous hmm, are you sure it's not covered by the "current instantiation" rules?Ru
@Ru Yes, quite. Inner is "— a nested class or enumeration that is a member of the current instantiation," ([temp.dep.type]/(8.3)), thus a dependent type. So i is "an identifier associated by name lookup with one or more declarations declared with a dependent type, " ([temp.dep.expr]/(3.1))Synchronous
@Ru I mean, Inner can be explicitly specialized later, can't it?Synchronous
@Synchronous Good point.Ru
Another way to fix it is to define the struct hi after struct Foo definition.Pyxidium
@MikaelPersson, your comment is perfectly valid for an answer. It suggests to file a bug as well as a possible resolution.Kitchenmaid
If function 'hi' does not use type 'U', why does it receive it?Westlund
@barej: This is a contrived example just to show the type of code that causes the error. Real code actually uses the template parameters, of course :-)Mantling
S
8

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.

Sopor answered 19/1, 2015 at 9:29 Comment(2)
Thank you for this answer! Very in-depth. I have a few questions, though: §3.4.5.1 says the "identifier is first looked up in the class of the object expression. If the identifier is not found [...]" -- doesn't this say that the global scope should only be checked if a local identifier in the class isn't found? Second, if the lookup is done in global scope, wouldn't that replace the local lookup entirely instead of augmenting its candidate matches? (The recent activity on the bug report, by the way, was caused by my comment about VS accepting the code, and doesn't seem to be going anywhere.)Mantling
You are absolutely correct. I will update my answer. I had misread the standard earlier because it was around 7AM and I hadn't slept yet. To confirm, a full name lookup is triggered first in the class context (and includes template name lookup in the standard way), and if it is not found then it jumps to the context of the entire postfix-expression and from there only templates may match.Sopor

© 2022 - 2024 — McMap. All rights reserved.