Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Name lookup with template class inheriting over namespaces

Tags:

c++

templates

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?

like image 631
Meteorhead Avatar asked Dec 09 '25 15:12

Meteorhead


1 Answers

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.

like image 104
Barry Avatar answered Dec 11 '25 20:12

Barry