Почему ключевое слово "typename" необходимо перед квалифицированными зависимыми именами, а не перед квалифицированными независимыми именами? - PullRequest
17 голосов
/ 21 декабря 2011
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 s, и может выбрать, какой из них в зависимости от того, как он используется - Сет Карнеги


Причина, по которой компилятору нужно typename перед квалифицированными зависимыми именами, на мой взгляд, очень хорошо дана в принятом ответе Kerrek SB. Не забудьте также прочитать комментарии к этому ответу, особенно этот от iammilind:

"T :: A * x ;, это выражение может быть истинным для обоих случаев, когда T :: A является типом, а T :: A является значением. Если A является типом, то это приведет к объявлению указателя ; если A является значением, то это приведет к умножению. Таким образом, один шаблон будет иметь разное значение для двух разных типов, что недопустимо. "

Ответы [ 3 ]

41 голосов
/ 21 декабря 2011

A name в C ++ может относиться к трем различным уровням сущностей: значениям, типам и шаблонам.

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 голосов
/ 21 декабря 2011

Поскольку в нешаблонном foo вы выполняете допустимую операцию умножения (при условии, что вы объявили iter перед этим использованием). Попробуйте опустить звездочку, и вы получите ошибку компилятора. Int скрывает класс.

Ключевое слово typename не предотвращает это сокрытие. Только gcc делает это неправильно, чтобы сделать это. Так что если вы попытаетесь создать экземпляр шаблона вашей функции с A в качестве типа, то вы получите ошибку компиляции, потому что имя, указанное после typenake , будет ссылаться на нетип любого стандартный совместимый компилятор.

0 голосов
/ 21 декабря 2011

Поскольку компилятор не знает, что такое T::iterator, пока не будет создан экземпляр шаблона, он не знает, является ли iterator переменной класса, типом, функцией или чем-то еще.Вы должны сказать ему, что это тип, используя typename.

...