Видимость псевдонима шаблона в вложенном классе

Рассмотрим следующее:

template<typename X>
struct Z {};

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

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

Это компилируется отлично. Ницца. Но теперь добавьте еще один параметр в 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'
    };
};

Хорошо, может быть, имеет смысл, что определение псевдонима шаблона Z в классе A отображается при выводе вложенного класса B, но не внутри его тела, вызывая ошибку, поскольку глобальное определение Z имеет два параметра.

Но почему - это поведение, отличное в первом случае, когда Z является просто псевдонимом типа в A?

Наконец, сделайте A шаблон:

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>;
    };
};

Теперь ошибка исчезла. Почему?

(Проверено на Clang 3.6 и GCC 4.9.2)

Ответ 1

Вкратце: в результате специализации Z вводится введенное имя класса ::Z, которое находится перед шаблоном псевдонимов. Однако если A является шаблоном, имя введенного класса больше не найдено, потому что базовый класс B зависит.


Рассмотрим [temp.local]/1:

Как обычные (не шаблонные) классы, шаблоны классов имеют injected-class-name (пункт 9). Можно использовать имя впрыскиваемого класса в качестве имени шаблона или имени типа.

И [basic.lookup]/3:

Введенное имя класса класса (раздел 9) также считается членом этого класса для целей поиска [...] lookup.

Z рассматривается как неквалифицированное имя; [Basic.lookup.unqual]/7:

введите описание изображения здесь

Таким образом, имя введенного класса Z найдено как член базового класса Z<B, int> и используется как имя-шаблон, что делает вашу вторую программу плохо сформированной. Фактически, ваш первый фрагмент использует имя введенного класса, а следующий фрагмент не будет компилироваться:

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

Наконец, если A сделан шаблон, обратите внимание, что B является зависимым типом в соответствии с [temp.dep.type]/(9.3) 1 поэтому Z<B> зависимый тип в соответствии с [temp.dep.type]/(9.7), поэтому базовый класс Z<B> не рассматривается во время поиска для неквалифицированного-id Z в соответствии с [temp.dep ]/3:

В определении класса [..] область действия зависимый базовый класс (14.6.2.1) не рассматривается при неквалифицированных поиск имени либо в точке определения шаблона класса, либо член или во время создания шаблона или члена класса.

Следовательно, имя введенного класса не будет найдено.


1B - это "вложенный класс [..], который является зависимым членом текущего экземпляра" (выделение мое), поскольку

Имя является зависимым членом текущего экземпляра, если оно является член текущего экземпляра, который, когда он смотрит вверх, ссылается на хотя бы один член класса, который является текущим экземпляром.