c++c++11language-lawyernested-classtemplate-aliases

Template alias visibility in nested class


Consider the following:

template<typename X>
struct Z {};

struct A
{
    using Z = ::Z<int>;

    struct B : Z
    {
        using C = Z;
    };
};

This compiles fine. Nice. But now add another parameter in Z:

template<typename X, typename Y>
struct Z {};

struct A
{
    template<typename X>
    using Z = ::Z<X, int>;

    struct B : Z<B>
    {
        using C = Z<B>;  // error: too few template arguments for class template 'Z'
    };
};

Ok, maybe it makes sense that the definition of template alias Z in class A is visible when deriving nested class B, but not inside its body, triggering the error since the global definition of Z has two parameters.

But why is the behavior different in the first case, when Z is just a type alias in A?

Finally, make A a template:

template<typename X, typename Y>
struct Z {};

template<typename T>
struct A
{
    template<typename X>
    using Z = ::Z<X, int>;

    struct B : Z<B>
    {
        using C = Z<B>;
    };
};

Now the error is gone. Why?

(Tested on Clang 3.6 and GCC 4.9.2)


Solution

  • In short: Deriving from a specialization of Z introduces the injected-class-name of ::Z, which is found before the alias template. If A is a template, however, the injected-class-name is not found anymore because the base class of B is dependent.


    Consider [temp.local]/1:

    Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injected-class-name can be used as a template-name or a type-name.

    And [basic.lookup]/3:

    The injected-class-name of a class (Clause 9) is also considered to be a member of that class for the purposes of name […] lookup.

    Z is looked up as an unqualified name; [basic.lookup.unqual]/7:

    enter image description here

    Thus, the injected-class-name Z is found as a member of the base class Z<B, int>, and used as a template-name, which renders your second program ill-formed. In fact, your first snippet uses the injected-class-name as well - the following snippet won't compile:

    struct A
    {
        using Z = ::Z<float>;
        struct B : ::Z<int>
        {
            static_assert( std::is_same<Z, ::Z<float>>{}, "" );
        };
    };
    

    Finally, if A is made a template, note that B is a dependent type as per [temp.dep.type]/(9.3)1, thus Z<B> is a dependent type as per [temp.dep.type]/(9.7), thus the base class Z<B> is not examined during lookup for the unqualified-id Z according to [temp.dep]/3:

    In the definition of a class [..], 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.

    Hence the injected-class-name won't be found.


    1 B is a "nested class [..] that is a dependent member of the current instantiation" (emphasis mine), since

    A name is a dependent member of the current instantiation if it is a member of the current instantiation that, when looked up, refers to at least one member of a class that is the current instantiation.