Краткий ответ: чтобы сделать x
зависимым именем, чтобы поиск откладывался до тех пор, пока не будет известен параметр шаблона.
Длинный ответ: когда компилятор видит шаблон, он должен выполнитьопределенные проверки немедленно, не видя параметра шаблона.Другие откладываются до тех пор, пока параметр не станет известен.Это называется двухфазной компиляцией, и MSVC этого не делает, но это требуется стандартом и реализуется другими основными компиляторами.Если вам нравится, компилятор должен скомпилировать шаблон, как только он его увидит (для какого-то внутреннего представления дерева разбора), и отложить компиляцию экземпляра до более поздней версии.
Проверки, которые выполняются на самом шаблоневместо того, чтобы выполнять его конкретные экземпляры, требуется, чтобы компилятор мог разрешать грамматику кода в шаблоне.
В C ++ (и C), чтобы разрешить грамматику кода, иногданужно знать, что-то типа или нет.Например:
#if WANT_POINTER
typedef int A;
#else
int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }
, если A является типом, который объявляет указатель (без эффекта, кроме как для затенения глобального x
).Если A - это объект, это умножение (и запрет на перегрузку некоторых операторов недопустимо, присваивая значение r).Если это не так, эта ошибка должна быть диагностирована в фазе 1 , согласно стандарту она определяется как ошибка в шаблоне , а не в какой-то конкретной ее реализации.Даже если шаблон никогда не создается, если A - int
, то приведенный выше код некорректен и должен быть диагностирован, как если бы foo
был не шаблоном вообще, а простой функцией.
Теперь в стандарте говорится, что имена, которые не не зависят от параметров шаблона, должны быть разрешены на этапе 1. A
здесь это не зависимое имя, оно относится к одному и тому женезависимо от типа T
.Поэтому его необходимо определить до того, как шаблон будет определен, чтобы его можно было найти и проверить на этапе 1.
T::A
- это имя, которое зависит от T. На этапе 1 мы не можем знать, является лиэто тип или нет.Тип, который в конечном итоге будет использоваться как T
в экземпляре, вполне вероятно, еще даже не определен, и даже если бы это было так, мы не знаем, какой тип (типы) будет использоваться в качестве нашего параметра шаблона.Но мы должны разрешить грамматику, чтобы выполнить наши драгоценные проверки фазы 1 для плохо сформированных шаблонов.Таким образом, в стандарте есть правило для зависимых имен - компилятор должен предполагать, что они не являются типами, если только он не квалифицирован как typename
, чтобы указать, что они являются типами или используются в определенных однозначных контекстах.Например, в template <typename T> struct Foo : T::A {};
, T::A
используется в качестве базового класса и, следовательно, однозначно является типом.Если Foo
создается с каким-то типом, имеющим элемент данных A
вместо вложенного типа A, то это ошибка в коде, выполняющем создание экземпляра (фаза 2), а не ошибка в шаблоне (фаза 1).
А как насчет шаблона класса с зависимым базовым классом?
template <typename T>
struct Foo : Bar<T> {
Foo() { A *x = 0; }
};
Является ли зависимое имя или нет?С базовыми классами, любое имя может появиться в базовом классе.Таким образом, мы могли бы сказать, что A является зависимым именем, и рассматривать его как нетиповое.Это имело бы нежелательный эффект, что каждое имя в Foo зависит, и, следовательно, каждый тип , используемый в Foo (кроме встроенных типов), должен быть квалифицирован.Внутри Foo вы должны написать:
typename std::string s = "hello, world";
, потому что std::string
будет зависимым именем и, следовательно, предполагается, что он не является типом, если не указано иное.Ой!
Вторая проблема с разрешением предпочитаемого вами кода (return x;
) состоит в том, что даже если Bar
определено до Foo
, а x
не является членом этого определения, кто-то может позже определить специализациюBar
для некоторого типа Baz
, такого что Bar<Baz>
имеет элемент данных x
, а затем создать экземпляр Foo<Baz>
.Таким образом, в этом случае ваш шаблон будет возвращать элемент данных, а не глобальный x
.Или наоборот, если определение базового шаблона Bar
имеет x
, они могут определить специализацию без него, и ваш шаблон будет искать глобальный x
для возврата в Foo<Baz>
.Я думаю, что это было так же удивительно и печально, как и проблема, с которой вы столкнулись, но это тихо удивительно, в отличие от выдачи неожиданной ошибки.
Чтобы избежать этих проблем, стандартпо сути, говорит, что зависимые базовые классы шаблонов классов просто не ищут имена, если только имена не являются зависимыми по какой-либо другой причине.Это останавливает все от зависимости только потому, что это может быть найдено в зависимой базе.Это также имеет нежелательный эффект, который вы видите - вы должны квалифицировать материал из базового класса, или он не найден.Существует три распространенных способа сделать A
зависимыми:
using Bar<T>::A;
в классе - A
теперь ссылается на что-то в Bar<T>
, следовательно, зависимое. Bar<T>::A *x = 0;
в точке использования - Опять же, A
определенно в Bar<T>
.Это умножение, поскольку typename
не использовалось, поэтому, возможно, это плохой пример, но нам придется подождать до создания экземпляра, чтобы выяснить, возвращает ли operator*(Bar<T>::A, x)
значение r.Кто знает, может быть, это так ... this->A;
в точке использования - A
является членом, поэтому, если он не входит в Foo
, он должен быть в базовом классе, опять же в стандартеговорит, что это делает его зависимым.
Двухфазная компиляция сложна и сложна, и вводит некоторые неожиданные требования для дополнительной словесности в вашем коде.Но, скорее, как и демократия, это, пожалуй, худший из возможных способов ведения дел, помимо всех остальных.
Вы можете обоснованно утверждать, что в вашем примере return x;
не имеет смысла, если x
является вложеннымвведите базовый класс, поэтому язык должен (а) сказать, что это зависимое имя и (2) трактовать его как нетип, и ваш код будет работать без this->
.В какой-то степени вы стали жертвой сопутствующего ущерба от решения проблемы, которая не применима в вашем случае, но все еще существует проблема с вашим базовым классом, потенциально представляющим имена под вами, которые являются теневыми глобалами, или отсутствие имен, о которых вы думалиони имели, и вместо этого был найден глобальный.
Можно также утверждать, что значение по умолчанию должно быть противоположным для зависимых имен (предполагается, что тип, если как-то не определено как объект), или что значение по умолчанию должно быть большеконтекстно-зависимый (в std::string s = "";
, std::string
может быть прочитан как тип, поскольку ничто иное не имеет грамматического смысла, даже если std::string *s = 0;
неоднозначно).Опять же, я не знаю, как были согласованы правила.Я предполагаю, что количество страниц текста, которое может потребоваться, сведено к минимуму с созданием множества конкретных правил, для которых контексты принимают тип, а какой - нетип.