Meteorhead
Meteorhead

Reputation: 516

Name lookup with template class inheriting over namespaces

I finally managed to boil down my monolithic template library compilation failure to a simple test case. MSVC disagrees with Clang and GCC on the code being malformed, mostly due to name lookup. Unfortunately, I am not adept at reading the C++ specification, and I tend to believe Clang and GCC when it comes to conformance, but could you help me finding the relevant part of the specs so I may file a bug?

namespace A
{
    template <int _I, int _II>
    struct X {};
}

namespace B
{
    template <int _J>
    struct X {};

    // Clang: OK
    // GCC: OK
    // MSVC: OK
    template <int _K>
    struct Y
    {
        Y(const A::X<_K, _K>&,
          const X<_K>&) {}
    };

    // Clang: OK
    // GCC: OK
    // MSVC: ERROR
    template <int _K>
    struct Z : A::X<_K, _K>
    {
        Z(const A::X<_K, _K>&,
          const X<_K>&) {}
    };

    // Clang: ERROR
    // GCC: ERROR
    // MSVC: ERROR
    struct Q : A::X<1, 1>
    {
        Q(const A::X<1, 1>&,
          const X<1>&) {}
    };
}

int main()
{
    A::X<1, 1> ax;
    B::X<1> bx;

    B::Z<1> bz(ax, bx);

    return 0;
}

Three cases are:

  1. Y is nothing extraordinary. It compiles fine with both compilers.
  2. Compilation error stems from the second argument of Z's CTOR, X not having enough template arguments. It is up to name lookup to decide which X do I refer to. As far as I understood from reading cppreference, inheriting from a class should not bring in it's name into scope when it comes to unqualified name lookup.
  3. If the class Q itself is not a template , all compilers reject it. Now this is the part where I completely lose ground. Why does it matter if the class is a template or not?

What is going on here?

Upvotes: 3

Views: 109

Answers (2)

Barry
Barry

Reputation: 303027

The problem you're running into is the injected-class-name. From [class]:

The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name is treated as if it were a public member name.

With unqualified lookup in a class definition, the first stop is in the scope of the class and its members. From [basic.lookup.unqual]:

A name used in the definition of a class X outside of a member function body, default argument, exception-specification, brace-or-equal-initializer of a non-static data member, or nested class definition shall be declared in one of the following ways:
(7.1) — before its use in class X or be a member of a base class of X (10.2), or
(7.2) — [...]

We're looking for X. There is no Q::X (Q's own scope) but there is an A::X<1,1>::X (base class scope), so lookup stops there and we never consider the enclosing namespace of Q. That's why we find A::X<> instead of B::X<>.

With Z, the situation is a little bit different. Now the base class is a dependent class template, and unqualified lookup will not look in dependent base classes. From [temp.dep]:

In the definition of a class or class template, the scope of a dependent base class (14.6.2.1) is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

So while the name A::X<_K,_K>::X exists, it is not considered, so lookup continues into the enclosing namespace and B::X<> is found.

In both cases, gcc/clang are correct.

Upvotes: 3

Richard Hodges
Richard Hodges

Reputation: 69882

The gcc's error message provides the clue:

38 : error: wrong number of template arguments (1, should be 2)
const X<1>&) {}
^
4 : note: provided for 'template<int _I, int _II> struct A::X'
struct X {};
^

What is going on here?

A template class is not code. It's a recipe for creating code. two-phase lookup will handle the definition of Z<> when you use it (at which point there had better be an X<i> available).

Q is a concrete class, so there's no two-phase lookup involved. X<i> must exist at that point - and it doesn't.

As ever, clang and gcc are correct. MSVC is non-conforming.

Upvotes: 0

Related Questions