Где и почему я должен поставить ключевые слова «template» и «typename»?


1127

В шаблонах, где и почему я должен поставить typenameи templateна зависимых именах?
Что именно являются зависимыми именами в любом случае?

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

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

Проблема у меня в typedef Tail::inUnion<U> dummyочереди. Я вполне уверен, что inUnionэто зависимое имя, и VC ++ вполне прав, подавляя его.
Я также знаю, что я должен быть в состоянии добавить templateкуда-нибудь, чтобы сообщить компилятору, что inUnion - это идентификатор шаблона. Но где именно? И следует ли тогда предполагать, что inUnion является шаблоном класса, то есть именует inUnion<U>тип, а не функцию?


1
Раздражающий вопрос: почему бы не повысить :: Variant?
Ассаф Лави

58
Политическая чувствительность, мобильность.
MSalters

5
Я сделал ваш фактический вопрос («Куда поместить шаблон / имя типа?») Лучше, поставив последний вопрос и код в начале, и укоротил код по горизонтали до размера экрана 1024x.
Йоханнес Шауб - лит

7
Удалены «зависимые имена» из заголовка, потому что кажется, что большинство людей, которые интересуются «typename» и «template», не знают, что такое «зависимые имена». Это должно быть менее запутанным для них таким образом.
Йоханнес Шауб - лит

2
@MSalters: повышение вполне переносимо. Я бы сказал, что только политика является основной причиной, по которой повышение часто не принимается. Единственная веская причина, которую я знаю, это увеличение времени сборки. Иначе это все о потере тысяч долларов, изобретающих велосипед.
v.oddou

Ответы:


1165

(Смотрите также мой ответ на C ++ 11 )

Чтобы проанализировать программу на C ++, компилятор должен знать, являются ли определенные имена типами или нет. Следующий пример демонстрирует это:

t * f;

Как это должно быть проанализировано? Для многих языков компилятору не нужно знать значение имени для анализа и, в основном, знать, какое действие выполняет строка кода. Однако в C ++ вышеприведенное может дать совершенно разные интерпретации в зависимости от того, что tозначает. Если это тип, то это будет объявление указателя f. Однако, если это не тип, это будет умножение. Таким образом, стандарт C ++ говорит в пункте (3/7):

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

Как компилятор узнает, на что t::xссылается имя , если tссылается на параметр типа шаблона? xэто может быть статический элемент данных типа int, который может быть умножен, или в равной степени это может быть вложенный класс или typedef, который может уступить объявлению. Если имя имеет это свойство - его нельзя найти, пока не будут известны фактические аргументы шаблона, - тогда оно называется зависимым именем (оно «зависит» от параметров шаблона).

Вы можете порекомендовать просто подождать, пока пользователь не создаст экземпляр шаблона:

Давайте подождем, пока пользователь не создаст экземпляр шаблона, а затем выясним настоящее значение t::x * f;.

Это будет работать и фактически допускается Стандартом как возможный подход к реализации. Эти компиляторы в основном копируют текст шаблона во внутренний буфер, и только когда требуется создание экземпляра, они анализируют шаблон и, возможно, обнаруживают ошибки в определении. Но вместо того, чтобы беспокоить пользователей шаблона (бедных коллег!) Из-за ошибок, допущенных автором шаблона, другие реализации предпочитают проверять шаблоны на ранних этапах и сообщают об ошибках в определении как можно скорее, прежде чем даже произойдет создание экземпляра.

Поэтому должен быть способ сообщить компилятору, что определенные имена являются типами, а определенные имена - нет.

Ключевое слово "typename"

Ответ таков: мы решаем, как компилятор должен это проанализировать. Если t::xэто зависимое имя, то нам нужно добавить префикс к нему, typenameчтобы сказать компилятору, что он должен разобрать его определенным образом. Стандарт говорит в (14,6 / 2):

Предполагается, что имя, используемое в объявлении или определении шаблона и которое зависит от параметра-шаблона, не будет называть тип, если только применимый поиск имени не найдет имя типа или имя не будет квалифицировано ключевым словом typename.

Существует множество имен, для которых typenameнет необходимости, потому что компилятор может при помощи поиска подходящего имени в определении шаблона выяснить, как анализировать саму конструкцию - например, с помощью параметра T *f;when, когда Tесть параметр шаблона типа. Но t::x * f;чтобы быть декларацией, она должна быть написана как typename t::x *f;. Если вы опускаете ключевое слово, а имя принимается как нетиповое, но когда экземпляр находит, что оно обозначает тип, компилятор выдает обычные сообщения об ошибках. Иногда ошибка, следовательно, дается во время определения:

// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;

Синтаксис допускает typenameтолько перед квалифицированными именами - поэтому считается, что неквалифицированные имена всегда относятся к типам, если они это делают.

Аналогичная ошибка существует для имен, которые обозначают шаблоны, на что намекает вводный текст.

Ключевое слово "template"

Помните первоначальную цитату выше, и как стандарт требует специальной обработки для шаблонов? Давайте возьмем следующий невинно выглядящий пример:

boost::function< int() > f;

Это может выглядеть очевидным для читателя. Не так для компилятора. Представьте себе следующее произвольное определение boost::functionи f:

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

Это действительно правильное выражение ! Он использует оператор меньше чем для сравнения boost::functionс нулем ( int()), а затем использует оператор больше чем для сравнения результата boolс f. Однако, как вы, возможно, знаете, boost::function в реальной жизни это шаблон, поэтому компилятор знает (14.2 / 3):

После того, как name lookup (3.4) обнаружит, что имя является именем шаблона, если за этим именем следует символ <, то <всегда берется как начало списка аргументов шаблона, а не как имя, за которым следует меньше чем оператор.

Теперь мы вернулись к той же проблеме, что и с typename. Что если мы еще не можем знать, является ли имя шаблоном при разборе кода? Нам нужно будет вставить templateнепосредственно перед именем шаблона, как указано 14.2/4. Это выглядит так:

t::template f<int>(); // call a function template

Имена шаблонов могут появляться не только после, ::но и после ->или .в доступе члена класса. Вам также нужно вставить ключевое слово:

this->template f<int>(); // call a function template

зависимости

Для людей, у которых на полке толстые стандартные книги и которые хотят знать, о чем именно я говорил, я немного расскажу о том, как это указано в Стандарте.

В объявлениях шаблона некоторые конструкции имеют разные значения в зависимости от того, какие аргументы шаблона вы используете для создания экземпляра шаблона: выражения могут иметь разные типы или значения, переменные могут иметь разные типы или вызовы функций могут в конечном итоге вызывать разные функции. Обычно говорят, что такие конструкции зависят от параметров шаблона.

Стандарт точно определяет правила в зависимости от того, является ли конструкция зависимой или нет. Он разделяет их на логически разные группы: одна ловит типы, другая ловит выражения. Выражения могут зависеть от их значения и / или типа. Итак, мы добавили типичные примеры:

  • Зависимые типы (например, параметр шаблона типа T)
  • Выражения, зависящие от значения (например, нетипичный параметр шаблона N)
  • Типозависимые выражения (например, приведение к параметру шаблона типа (T)0)

Большинство правил интуитивно понятны и создаются рекурсивно: например, тип, сконструированный как, T[N]является зависимым типом, если Nявляется выражением, зависящим от значения, или Tявляется зависимым типом. Подробности этого можно прочитать в разделе (14.6.2/1) для зависимых типов, (14.6.2.2)для зависимых от типа выражений и (14.6.2.3)для зависимых от значения выражений.

Зависимые имена

Стандарт немного неясен относительно того , что именно является зависимым именем . При простом чтении (вы знаете, принцип наименьшего удивления) все, что он определяет как зависимое имя, является особым случаем для имен функций ниже. Но поскольку ясно, что T::xтакже необходимо искать в контексте реализации, оно также должно быть зависимым именем (к счастью, с середины C ++ 14 комитет начал искать способы исправления этого запутанного определения).

Чтобы избежать этой проблемы, я прибег к простой интерпретации стандартного текста. Из всех конструкций, которые обозначают зависимые типы или выражения, их подмножество представляет имена. Поэтому эти имена являются «зависимыми именами». Название может принимать разные формы - Стандарт гласит:

Имя - это использование идентификатора (2.11), идентификатора оператора-функции (13.5), идентификатора функции преобразования (12.3.2) или идентификатора шаблона (14.2), который обозначает объект или метку (6.6.4, 6.1)

Идентификатор просто обычная последовательность символов / цифр, в то время как рядом два являются operator +и operator typeформой. Последняя форма есть template-name <argument list>. Все это имена, и при стандартном использовании в Стандарте имя может также включать квалификаторы, которые говорят, в каком пространстве имен или классе следует искать имя.

Зависимое от значения выражение 1 + N- это не имя, а имя N. Подмножество всех зависимых конструкций, которые являются именами, называется зависимым именем . Однако имена функций могут иметь разное значение в разных экземплярах шаблона, но, к сожалению, это общее правило не учитывается.

Имена зависимых функций

Не в первую очередь проблема этой статьи, но все же стоит упомянуть: Имена функций являются исключением, которые обрабатываются отдельно. Имя функции идентификатора зависит не само по себе, а от зависимых от типа выражений аргументов, используемых в вызове. В примере f((T)0), fявляется зависимым именем. В стандарте это указано в (14.6.2/1).

Дополнительные заметки и примеры

В достаточном количестве случаев нам нужны как typenameи template. Ваш код должен выглядеть следующим образом

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

Ключевое слово templateне всегда должно появляться в последней части имени. Он может появляться в середине перед именем класса, которое используется в качестве области видимости, как в следующем примере

typename t::template iterator<int>::value_type v;

В некоторых случаях ключевые слова запрещены, как описано ниже

  • На имя зависимого базового класса вам запрещено писать typename. Предполагается, что данное имя является именем типа класса. Это верно как для имен в списке базового класса, так и в списке инициализатора конструктора:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
  • В объявлениях об использовании нельзя использовать templateпосле последнего ::, и комитет C ++ сказал, что не работает над решением.

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };

22
Этот ответ был скопирован из моей более ранней записи FAQ, которую я удалил, потому что я обнаружил, что мне лучше использовать существующие подобные вопросы, а не создавать новые «псевдо-вопросы» только для того, чтобы отвечать на них. Спасибо @Prasoon , который отредактировал идеи последней части (случаи, когда имя / шаблон запрещено) в ответ.
Йоханнес Шауб - лит

1
Можете ли вы помочь мне, когда я должен использовать этот синтаксис? this-> template f <int> (); Я получаю эту ошибку «шаблон» (как устранение неоднозначности) допускается только в шаблонах, но без ключевого слова шаблона, он работает нормально.
Балки

1
Сегодня я задал похожий вопрос, который вскоре был отмечен как дубликат: stackoverflow.com/questions/27923722/… . Мне было поручено возродить этот вопрос вместо создания нового. Должен сказать, я не согласен с тем, что они дублируют друг друга, но кто я, верно? Итак, есть ли какая-либо причина, которая typenameприменяется, даже когда синтаксис не допускает альтернативных интерпретаций, кроме имен типов на данном этапе?
JorenHeit

1
@ Пабло, ты ничего не пропустил. Но все же требуется написать неоднозначность, даже если полная строка больше не будет неоднозначной.
Йоханнес Шауб -

1
Цель @Pablo - упростить язык и компиляторы. Существуют предложения, позволяющие большему количеству ситуаций автоматически выяснять ситуацию, так что вам нужно ключевое слово реже. Обратите внимание , что в вашем примере, маркер является неоднозначным , и только после того, как вы видели «>» после того, как двойной, вы можете неоднозначность его как угол шаблона кронштейн. Для получения дополнительной информации я не тот человек, которого нужно спрашивать, потому что у меня нет опыта в реализации синтаксического анализатора компиляторов C ++.
Йоханнес Шауб -

136

C ++ 11

проблема

Хотя правила в C ++ 03 о том, когда вам нужно typenameи templateв значительной степени разумные, есть один досадный недостаток в его формулировке

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Как можно видеть, нам нужно ключевое слово устранения неоднозначности, даже если компилятор может сам определить, что A::result_typeможет быть только int(и, следовательно, является типом), и this->gможет быть только шаблоном члена, gобъявленным позже (даже если Aгде-то явно специализируется, это будет не влияет на код в этом шаблоне, поэтому его значение не может быть затронуто последующей специализацией A!).

Текущий экземпляр

Чтобы улучшить ситуацию, в C ++ 11 язык отслеживает, когда тип ссылается на включающий шаблон. Чтобы знать , что тип должен быть сформирован с использованием определенной формы имени, которая является его собственным именем (в приведенном выше A, A<T>, ::A<T>). Тип, на который ссылается такое имя, известен как текущий экземпляр . Может быть несколько типов, которые являются текущими экземплярами, если тип, из которого сформировано имя, является классом-членом / вложенным (тогда A::NestedClassи Aоба являются текущими экземплярами).

На основе этого понятия, язык говорит , что CurrentInstantiation::Foo, Fooи CurrentInstantiationTyped->Foo(например A *a = this; a->Foo), все члены текущего экземпляра , если они будут признаны членами класса , который является текущей конкретизацией или один из его не-зависимых базовых классов (просто делая поиск имени сразу).

Ключевые слова typenameи templateтеперь больше не требуются, если классификатор является членом текущего экземпляра. Keypoint здесь помнить, что A<T>это до сих пор имя типа зависит ( в конце концов T, также зависит от типа). Но A<T>::result_typeизвестно, что это тип - компилятор «волшебным образом» изучит этот тип зависимых типов, чтобы выяснить это.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

Это впечатляет, но мы можем сделать лучше? Язык даже идет дальше и требует , чтобы реализация снова смотрела D::result_typeпри создании экземпляра D::f(даже если она нашла свое значение уже во время определения). Если теперь результат поиска отличается или приводит к неоднозначности, программа имеет неправильную форму и должна быть предоставлена ​​диагностика. Представьте, что произойдет, если мы определили Cтак

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Компилятор обязан отлавливать ошибку при создании экземпляра D<int>::f. Таким образом, вы получаете лучшее из двух миров: «задержанный» поиск, защищающий вас, если у вас могут возникнуть проблемы с зависимыми базовыми классами, а также «немедленный» поиск, который освобождает вас от typenameи template.

Неизвестные специализации

В коде Dимя typename D::questionable_typeне является членом текущего экземпляра. Вместо этого язык помечает его как члена неизвестной специализации . В частности, это всегда бывает , когда вы делаете DependentTypeName::Fooили DependentTypedName->Fooи либо зависимый тип не ток конкретизации (в этом случае компилятор может отказаться и сказать : «мы рассмотрим позже , что Fooесть) , или оно является текущей конкретизацией и имя не было найдено ни в нем, ни в его независимых классах, и существуют также зависимые базовые классы.

Представьте, что произойдет, если у нас будет функция-член hв указанном выше Aшаблоне класса

void h() {
  typename A<T>::questionable_type x;
}

В C ++ 03 язык позволял отлавливать эту ошибку, потому что никогда не было правильного способа создания экземпляра A<T>::h(независимо от того, какой аргумент вы указали T). В C ++ 11 у языка теперь есть дополнительная проверка, чтобы дать больше оснований для компиляторов реализовать это правило. Поскольку Aне имеет зависимых базовых классов и не Aобъявляет ни одного члена questionable_type, имя не A<T>::questionable_typeявляется ни членом текущей реализации, ничлен неизвестной специализации. В этом случае не должно быть способа, чтобы этот код мог корректно компилироваться во время создания экземпляра, поэтому язык запрещает имени, в котором спецификатор является текущим экземпляром, не быть ни членом неизвестной специализации, ни членом текущего экземпляра (однако это нарушение до сих пор не требуется для диагностики).

Примеры и мелочи

Вы можете попробовать эти знания в этом ответе и посмотреть, имеют ли смысл приведенные выше определения для вас на примере из реальной жизни (они повторяются чуть менее подробно в этом ответе).

Правила C ++ 11 делают следующий действительный код C ++ 03 неправильно сформированным (который не был задуман комитетом C ++, но, вероятно, не будет исправлен)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Этот действительный код C ++ 03 связывался this->fбы A::fво время создания экземпляра, и все в порядке. Однако C ++ 11 немедленно связывает его B::fи требует двойной проверки при создании экземпляра, проверяя, совпадает ли поиск. Однако при C<A>::gсоздании экземпляра применяется правило доминирования, и A::fвместо него выполняется поиск .


fyi - на этот ответ ссылаются здесь: stackoverflow.com/questions/56411114/… Большая часть кода в этом ответе не компилируется на различных компиляторах.
Адам Ракис

@AdamRackis, если предположить, что спецификация C ++ не изменилась с 2013 года (дата, когда я написал этот ответ), тогда компиляторы, с которыми вы пробовали свой код, просто еще не реализовали эту особенность C ++ 11+.
Йоханнес Шауб -

100

ПРЕДИСЛОВИЕ

Этот пост предназначен для удобной для чтения альтернативы сообщению Литба .

Основная цель та же; объяснение "Когда?" и почему?" typenameи templateдолжен быть применен.


Какова цель typenameи template?

typenameи templateмогут быть использованы в обстоятельствах, отличных от объявления шаблона.

В C ++ существуют определенные контексты, в которых компилятору нужно явно указывать, как обрабатывать имя, и все эти контексты имеют одну общую черту; они зависят как минимум от одного шаблона-параметра .

Мы ссылаемся на такие имена, где может быть неоднозначность в интерпретации, как; « зависимые имена ».

В этом посте будет дано объяснение связи между именами зависимых и двумя ключевыми словами.


СНИПЕТТ ГОВОРИТ БОЛЕЕ 1000 СЛОВ

Попытайтесь объяснить, что происходит в следующем шаблоне функции , для себя, друга или, возможно, вашей кошки; что происходит в выражении ( A )?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


Это может быть не так просто, как кажется, более конкретно, результат оценки ( A ) сильно зависит от определения типа, переданного как параметр-шаблон T.

Различные Ts могут кардинально изменить семантику.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


Два разных сценария :

  • Если мы создадим экземпляр функции-шаблона с типом X , как в ( C ), у нас будет объявление указателя на int с именем x , но;

  • если мы создадим шаблон с типом Y , как в ( D ), вместо этого ( A ) будет состоять из выражения, которое вычисляет произведение 123, умноженное на некоторую уже объявленную переменную x .



ОБОСНОВАНИЕ

Стандарт C ++ заботится о нашей безопасности и благополучии, по крайней мере, в этом случае.

Чтобы предотвратить потенциальную реализацию реализации неприятных сюрпризов, Стандарт обязывает нас разобраться в неоднозначности зависимого имени , явно указав намерение везде, где мы хотим, чтобы имя воспринималось либо как имя типа , либо как шаблон. идентификатор .

Если ничего не указано, зависимое имя будет считаться либо переменной, либо функцией.



КАК РАБОТАТЬ С ЗАВИСИМЫМИ ИМЕНАМИ ?

Если бы это был голливудский фильм, зависимые имена были бы болезнью, которая распространяется через телесный контакт, мгновенно воздействуя на своего хозяина, чтобы запутать его. Путаница, которая может, возможно, привести к плохо сформированной персо-, эээ ... программе.

Имя-зависимое это любое имя , которое прямо или косвенно зависит от шаблона-параметра .

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

У нас есть четыре зависимых имени в приведенном выше фрагменте:

  • Д )
    • «тип» зависит от экземпляров SomeTrait<T>, которые включают Tи;
  • F )
    • «NestedTrait» , который является идентификатором шаблона , зависит от SomeTrait<T>, и;
    • «тип» в конце ( F ) зависит от NestedTrait , который зависит от SomeTrait<T>, и;
  • Г )
    • «data» , который выглядит как шаблон функции-члена , является косвенно зависимым именем, так как тип foo зависит от создания экземпляра SomeTrait<T>.

Ни одно из утверждений ( E ), ( F ) или ( G ) недопустимо, если компилятор интерпретирует зависимые имена как переменные / функции (что, как было сказано ранее, происходит, если мы явно не говорим иначе).

РЕШЕНИЕ

Чтобы g_tmplиметь правильное определение, мы должны явно указать компилятору, что мы ожидаем тип в ( E ), идентификатор шаблона и тип в ( F ), а также идентификатор шаблона в ( G ).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

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

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



Могу ли я просто вставить ключевые слова перед любым именем?

« Могу ли я просто придерживаться typenameи templateперед любым именем , я не хочу , чтобы беспокоиться о том контексте , в котором они появляются ...? » -Some C++ Developer

Правила Стандарта гласят, что вы можете применять ключевые слова до тех пор, пока вы имеете дело с квалифицированным именем ( K ), но если имя не является квалифицированным, приложение неправильно сформировано ( L ).

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Примечание : применение typenameили templateв контексте, где это не требуется, не считается хорошей практикой; только то, что ты можешь что-то сделать, не значит, что ты должен.


Кроме того , существуют контексты , где typenameи templateбудут явно запрещенные:

  • При указании оснований, которые наследует класс

    Каждое имя, записанное в списке базовых спецификаторов производного класса, уже обрабатывается как имя типа, поскольку явное указание typenameявляется как некорректным, так и избыточным.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };


  • Когда идентификатор шаблона упоминается в директиве using производного класса

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };

20
typedef typename Tail::inUnion<U> dummy;

Однако я не уверен, что вы правильно внедрили inUnion. Если я правильно понимаю, этот класс не должен создаваться, поэтому вкладка «сбой» никогда не приведет к сбою. Возможно, было бы лучше указать, находится ли тип в объединении или нет, с простым логическим значением.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: Посмотрите на Boost :: Variant

PS2: взгляните на списки типов , особенно в книге Андрея Александреску: современный дизайн C ++


inUnion <U> был бы создан, если бы вы, например, пытались вызвать Union <float, bool> :: operator = (U) с U == int. Он вызывает частный набор (U, inUnion <U> * = 0).
MSalters

И работа с result = true / false заключается в том, что мне нужен boost :: enable_if <>, который несовместим с нашей текущей цепочкой инструментов OSX. Хотя отдельный шаблон все еще хорошая идея.
MSalters

Люк означает typedef Tail :: inUnion <U> dummy; линия. это будет инстанцировать Хвост. но не в Союзе <U>. он создается, когда ему нужно полное определение. это происходит, например, если вы берете sizeof или получаете доступ к члену (используя :: foo). @MSalters в любом случае, у вас есть еще одна проблема:
Йоханнес Шауб - Lit

-sizeof (U) никогда не бывает отрицательным :), потому что size_t - это целочисленный тип без знака. Вы получите очень большое число. Вы, вероятно, хотите сделать sizeof (U)> = 1? -1: 1 или похожее :)
Йоханнес Шауб -

я бы просто оставил его неопределенным и только объявил бы его: template <typename U> struct inUnion; так что это, конечно, не может быть реализовано. я думаю, что, имея sizeof, компилятор также может выдавать ошибку, даже если вы не создаете его, потому что если знает, что sizeof (U) всегда> = 1 и ...
Йоханнес Шауб - litb

20

Этот ответ должен быть довольно коротким и приятным, чтобы ответить (частично) на названный вопрос. Если вы хотите получить более подробный ответ, объясняющий, почему вы должны поместить его туда, перейдите сюда .


Общее правило для размещения typenameключевого слова в основном, когда вы используете параметр шаблона и хотите получить доступ к вложенному typedefили с использованием псевдонима, например:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

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

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Общие правила добавления templateквалификатора в основном аналогичны, за исключением того, что они обычно включают шаблонные функции-члены (статические или иные) структуры / класса, который сам шаблонизирован, например:

Учитывая эту структуру и функцию:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Попытка получить доступ t.get<int>()изнутри функции приведет к ошибке:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Таким образом, в этом контексте вам нужно templateзаранее задать ключевое слово и назвать его так:

t.template get<int>()

Таким образом, компилятор будет анализировать это правильно, а не t.get < int.


2

Я помещаю превосходный ответ JLBorges на аналогичный дословный вопрос от cplusplus.com, так как это самое краткое объяснение, которое я читал по этому вопросу.

В шаблоне, который мы пишем, есть два вида имен, которые можно использовать - зависимые имена и независимые имена. Зависимое имя - это имя, которое зависит от параметра шаблона; независимое имя имеет одинаковое значение независимо от того, каковы параметры шаблона.

Например:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

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

На первом этапе анализатору необходимо знать, является ли зависимое имя именем типа или именем нетипа. По умолчанию, зависимое имя считается именем нетипового типа. Ключевое слово typename перед зависимым именем указывает, что это имя типа.


Резюме

Используйте ключевое слово typename только в объявлениях и определениях шаблонов, если у вас есть полное имя, которое относится к типу и зависит от параметра шаблона.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.