Почему ключевое слово "typename" необходимо перед квалифицированными зависимыми именами, а не перед квалифицированными независимыми именами?
class A
{
static int iterator;
class iterator
{
[...]
};
[...]
};
Я (думаю, я) понимаю причину typename
здесь:
template <class T>
void foo() {
typename T::iterator* iter;
[...]
}
но я не понимаю, почему typename
здесь не нужен:
void foo() {
A::iterator* iter;
[...]
}
Может ли кто-нибудь объяснить?
EDIT:
Причина, по которой у компилятора нет проблемы с последним, я нашел ответ в комментариях:
в случае A::iterator
Я не понимаю, почему компилятор не путал бы его с static int iterator
? - xcrypt
@xcrypt, потому что он знает, что оба A::iterator
есть и могут выбрать, какой из них зависит от того, как он используется - Seth Carnegie
Причина, по которой компилятор нуждается в typename
перед квалифицированными зависимыми именами, на мой взгляд, очень хорошо ответил на принятый ответ Kerrek SB. Не забудьте также прочитать комментарии к этому ответу, особенно этому iammilind:
"T:: A * x; это выражение может быть истинным для обоих случаев, где T:: A - тип, а T:: A - значение. Если A - тип, то это приведет к объявлению указателя, если A - значение, то это приведет к умножению. Таким образом, один шаблон будет иметь разное значение для двух разных типов, что неприемлемо."
Ответы
Ответ 1
Имя в С++ может относиться к трем различным уровням сущностей: значениям, типам и шаблонам.
struct Foo
{
typedef int A;
static double B;
template <typename T> struct C;
};
Три имени Foo::A
, Foo::B
и Foo::C
являются примерами всех трех разных уровней.
В приведенном выше примере Foo
является полным типом, поэтому компилятор уже знает, что означает Foo::A
и т.д. Но теперь представьте это:
template <typename T> struct Bar
{
T::A x;
};
Теперь мы в беде: что такое T::A
? если T = Foo
, то T::A = int
, который является типом, и все хорошо. Но когда T = struct { char A; };
, тогда T::A
является значением, что не имеет смысла.
Поэтому компилятор требует, чтобы вы сказали, что должны быть T::A
и T::B
и T::C
. Если вы ничего не говорите, предполагается, что это значение. Если вы скажете typename
, это имя типа, и если вы скажете template
, это шаблон:
template <typename T> struct Bar
{
typename T::A x; // ah, good, decreed typename
void foo()
{
int a = T::B; // assumed value, OK
T::template C<int> z; // decreed template
z.gobble(a * x);
}
};
Вторичные проверки, такие как T::B
можно конвертировать в int
, можно ли умножить a
и x
, и действительно ли C<int>
имеет функцию-член gobble
, пока вы фактически не создадите экземпляр шаблон. Но спецификация того, обозначает ли имя значение, тип или шаблон, имеет основополагающее значение для синтаксической корректности кода и должна быть предоставлена прямо там во время определения шаблона.
Ответ 2
Потому что в nontemplate foo вы выполняете действительную операцию умножения (предполагая, что вы объявили iter
до этого использования). Попробуйте опустить звезду, и вы получите ошибку компилятора. Класс int скрывает класс.
Ключевое слово typename не мешает этому сокрытию. Только gcc неправильно выполняет это. Поэтому, если вы попытаетесь создать экземпляр своего шаблона функции с помощью A
в качестве типа, тогда вы получите ошибку компиляции, так как имя, указанное afteer typenake, будет ссылаться на nontype на любом стандартном компиляторе соответствия.
Ответ 3
Поскольку компилятор не знает, что T::iterator
до момента создания шаблона, он не знает, является ли iterator
переменной класса, типом, функцией или чем-то. Вы должны сказать, что это тип, используя typename
.