Подтвердить что ты не робот

Почему использование определений базового класса в не-выводимом контексте не разрешено и как обойти это?

У меня есть следующий код:

#include <iostream>

template <typename T>
struct Base
{
    using Type = int;
};

template <typename T>
struct Derived : Base<T>
{
    //uncommmenting the below cause compiler error
    //using Alias = Type;
};

int main()
{
    Derived<void>::Type b = 1;
    std::cout << b << std::endl;

    return 0;
}

Теперь имя Type доступно для Derived, если оно находится в выведенном контексте, как показано в действительном объявлении b. Однако, если я попытаюсь ссылаться на Type внутри объявления самого Derived, то я получаю сообщение о компиляторе, сообщающее мне, что Type не указывает тип (например, если определение Alias раскоментировано),

Я думаю, что это связано с тем, что компилятор не может проверить, может ли быть извлечен Type из базового класса, когда он анализирует определение Derived вне контекста конкретного экземпляра параметр T. В этом случае это расстраивает, так как Base всегда определяет Type независимо от T. Поэтому мой вопрос двоякий:

1). Почему на Земле это происходит? Под этим я подразумеваю, почему компилятор беспокоит синтаксический анализ Derived вообще вне контекста инстанцирования (я предполагаю, что не выведенный контекст), когда это не делается, избегайте этих ошибок "компилятора" фига? Возможно, для этого есть веская причина. Каково правило в стандарте, которое утверждает, что это должно произойти?

2). Что является хорошим обходным решением для такого рода проблем? У меня есть реальный случай, когда мне нужно использовать типы базового класса в определении производного класса, но я не могу этого сделать. Я предполагаю, что я ищу какое-то "спрятанное за невыделенным контекстом" решение, где я предотвращаю "первый проход" этого компилятора, помещая необходимые определения /typedef за шаблонные классы или что-то в этих строках.

РЕДАКТИРОВАТЬ: Как следует из некоторых ответов ниже, я могу использовать using Alias = typename Base<T>::Type. Я должен был сказать с самого начала, я знаю, что это работает. Однако это не совсем удовлетворительно по двум причинам: 1) он вообще не использует иерархию наследования (Derived не нужно выводить из Base, чтобы это работало), и я точно пытаюсь использовать типы, определенные в моей иерархии базового класса, и 2) реальный случай фактически имеет несколько уровней наследования. Если бы я хотел извлечь что-то из нескольких слоев, это станет действительно довольно уродливым (мне нужно либо обратиться к непрямому предку, либо повторить using на каждом уровне, пока не достигнет того, в котором я нуждаюсь)

4b9b3361

Ответ 1

Поскольку type находится в "зависимой области", вы можете получить к нему доступ следующим образом:

typename Base<T>::Type

Затем ваш Alias должен быть определен следующим образом:

using Alias = typename Base<T>::Type;

Обратите внимание, что на данный момент компилятор не знает, если Base<T>::type описывает переменную-член или вложенный тип, поэтому требуется ключевое слово typename.

Слои

Вам не нужно повторять определение на каждом уровне, вот пример, ссылка:

template <typename T>
struct intermediate : Base<T>
{
    // using Type = typename Base<T>::Type; // Not needed
};

template <typename T>
struct Derived : intermediate<T>
{
    using Type = typename intermediate<T>::Type;
};

Обновление

Вы также можете использовать класс сам, это зависит от использования неизвестных специализаций.

template <typename T>
struct Derived : Base<T>
{
    using Type = typename Derived::Type; // <T> not required here.
};

Ответ 2

Проблема заключается в том, что Base<T> является зависимым базовым классом, и для него могут быть специализации, в которых Type больше не определен. Скажем, например, у вас есть специализация вроде

template<>
class Base<int>
{}; // there no more Type here

Компилятор не может знать это заранее (технически он не может знать до создания экземпляра шаблона), особенно если специализация определена в другой единицы перевода. Таким образом, разработчики языка выбрали легкий путь: всякий раз, когда вы ссылаетесь на что-то зависимое, вам нужно явно указать это, как в вашем случае

using Alias = typename Base<T>::Type;

Ответ 3

Я думаю, что это связано с тем, что компилятор не может проверить, может ли тип быть втянут из базового класса, когда он анализирует определение Derived вне контекста конкретного экземпляра параметра T.

Да.


В этом случае это расстраивает, так как Base всегда определяет Type независимо от T.

Да.

Но в целом было бы совершенно невозможно определить, было ли это правдой, и крайне запутанно, если семантика языка изменилась, когда это было правдой.


Возможно, для этого есть веская причина.

С++ - это язык программирования общего назначения, а не оптимизированный для программы язык программирования Smeeheey-on-work-on-today.:)


Какое правило в стандарте указано, что это должно произойти?

Это:

[C++14: 14.6.2/3]: В определении шаблона класса или класса, если базовый класс зависит от параметра-шаблона, область базового класса не рассматривается при поиске неквалифицированного имени либо в точке определения шаблона класса, либо члена или во время создания шаблона или члена класса. [..]


Какое хорошее обходное решение для такого типа проблемы?

Вы уже знаете — квалификация:

using Alias = typename Base<T>::Type;

Ответ 4

При определении шаблона иногда вещи должны быть немного более явными:

using Alias = typename Base<T>::Type;

Rougly speaking: шаблон - это своего рода проект. Ничего не существует до тех пор, пока шаблон не будет создан.

Выполняя то же самое, но в контексте, отличном от шаблона, компилятор С++ попытается выяснить, что такое Type. Он попытается найти его в базовых классах и перейти оттуда. Потому что все уже объявлено, и все в значительной степени вырезано и сухо.

Здесь базовый класс не существует до тех пор, пока шаблон не будет создан. Если вы уже знаете о специализации шаблонов, вы должны понимать, что базовый класс может не иметь член Type, когда шаблон получает экземпляр, если есть специализация для базового класса, определенного позже по дороге, что происходит чтобы переопределить все это, и повернуть его внутрь и наружу.

Таким образом, при столкновении с простым, старым, Type в этом контексте компилятор не может делать много предположений. Он не может предположить, что он может выглядеть в любых определенных базовых классах шаблонов, потому что эти базовые классы могут не выглядеть так, как компилятор думает, что они будут выглядеть, когда все начнет затвердевать; поэтому у вас есть заклинание, явно для компилятора, и расскажите компилятору, что именно вы пытаетесь сделать здесь.

Ответ 5

Вы не можете использовать типы базового класса в невыводимом контексте. С++ отказывается считать, что несвязанные имена относятся к вещам в вашем базовом классе.

template <typename T>
struct Base {
  using Type = int;
};
template<>
struct Base<int> {};

using Type=std::string;

template <typename T>
struct Derived : Base<T> {
  using Alias = Type;
};

Теперь давайте посмотрим, что здесь происходит. Type отображается в Derived - глобальном. Должно ли Derived использовать это или нет?

В соответствии с правилом "разобрать ничего до экземпляра" мы используем глобальный Type тогда и только тогда, когда T является int, из-за специализации Base, которая удаляет из него Type.

Следуя этому правилу, мы сталкиваемся с проблемой, что мы можем диагностировать в основном отсутствие ошибок в шаблоне до того, как он будет создан, потому что базовый класс может заменить смысл почти любого! Вызовите abs? Может быть членом родителя! Упомяните тип? Может быть от родителя!

Это заставило бы шаблоны в основном быть макросами; никакого существенного анализа не может быть сделано до их создания. Незначительные опечатки могут привести к массовому поведению. Почти любая некорректность в шаблоне невозможна для диагностики без создания тестовых экземпляров.

Наличие шаблонов, которые можно проверить на правильность, означает, что, когда вы хотите использовать типы родительских классов и членов, вы должны сказать, что делаете это.

Ответ 6

Как частичный ответ о точке

[T] его расстраивает, так как Base всегда определяет Type независимо от T.

Я бы сказал: нет, это не так.

Пожалуйста, рассмотрите следующий пример, отличный от вашего, только с помощью определения одной строки Base<void> и определения Alias:

#include <iostream>

template <typename T>
struct Base
{
    using Type = int;
};

template <typename T>
struct Derived : Base<T>
{
    using Alias = typename Base<T>::Type; // error: no type named 'Type' in 'struct Base<void>'
};

template<> struct Base<void> {};

int main()
{
    Derived<void>::Type b = 1;
    std::cout << b << std::endl;

    return 0;
}

В контексте template <typename T> struct Derived : Base<T> нет гарантии, что существует Type. Вы должны явно сообщить своему компилятору, что Base<T>::Type - это тип (с typename), и если вы когда-либо закончите этот контракт, вы получите ошибку компиляции.