Template alias visibility in nested class
Asked Answered
B

1

12

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)

Blackfellow answered 17/11, 2015 at 0:0 Comment(0)
M
9

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.

Mulhouse answered 17/11, 2015 at 1:23 Comment(4)
Wow. This is quite clear, thanks. The error actually appeared when A stopped being a template, which I thought would simplify the code a lot. However, I am now forced to use two different names for the two Z's, which only makes the code uglier. If there is any better workaround, please let me know.Blackfellow
@Blackfellow What about using C = Z;? (Won't work for A being a template)Mulhouse
Now that's impressive :-) Yes, it works on this simplified code here, but not on my original one (unknown type name 'Z'). I'll have to check where the difference is. A is not a template any more, and I intend to keep it that way.Blackfellow
Ah, it was because I had renamed the outer Z (say, to Z1). Now it's working and really simplified because I don't need using C ... at all. Great, thanks again!Blackfellow

© 2022 - 2024 — McMap. All rights reserved.