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


в шаблонах, где и почему нужно ставить 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> имя типа, а не функция?

6 926

6 ответов:

чтобы проанализировать программу на 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;, когда 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 только перед полными именами - поэтому считается само собой разумеющимся, что неквалифицированные имена всегда относятся к типам, если они это делают.

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

ключевое слово "шаблон"

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

boost::function< int() > f;

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

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

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

после поиска имени (3.4) находит, что имя является именем шаблона, если за этим именем следует

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

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

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

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

зависимости

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

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

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

  • зависимые типы (например: параметр шаблона типа T)
  • выражения, зависящие от значения (например: a параметр шаблона без типа 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
     };
    

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();
};

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

текущего экземпляра

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

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

ключевые слова typename и template теперь не требуется, если квалификатор является членом текущего экземпляра. Ключевой момент здесь, чтобы помнить, что 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 не является членом текущего экземпляра. Вместо этого язык отмечает его как a членом неизвестной специализации. В частности, это всегда так, когда вы делаете 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 вместо.

предисловие

этот пост должен быть легко читаемый альтернатива litb это!--42-->.

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


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

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

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

мы ссылаемся на такие названия, где может быть двусмысленность в толковании, как;"зависимые имена".

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


ФРАГМЕНТ ГОВОРИТ БОЛЕЕ 1000 СЛОВ

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

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    
}

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

  • E)
    • "типа" зависит от экземпляра SomeTrait<T>, включая T, и;
  • F)
    • "NestedTrait", который является template-id зависит от SomeTrait<T> и
    • "типа" в конце (F) зависит NestedTrait, от которой зависит SomeTrait<T> и
  • G)
    • "сведения", который выглядит как функции-члена шаблона, косвенно a зависимый-именем С типом фу зависит от экземпляра SomeTrait<T>.

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

РЕШЕНИЕ

сделать g_tmpl есть допустимое определение мы должны явно сказать компилятору, что мы ожидаем тип в (E), а template-id и тип in (F), и template-id in (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
}

каждый раз a имя обозначает тип, все имена должна быть type-names или пространства имен, имея это в виду, это довольно легко увидеть, что мы применяем 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 are явно запрещены:

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

    каждое имя, написанное в производном классе base-specifier-list уже рассматривается как type-name, явно указать typename и плохо сформировано, и избыточный.

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


  • когда template-id это тот, на который ссылаются в производном классе using-directive

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    
typedef typename Tail::inUnion<U> dummy;

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

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: взгляните на typelists, особенно у Андрея Александреску книги: Современное проектирование на С++

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


общее правило для нанесения 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.

Я ставлю 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 только в объявлениях и определениях шаблонов, если у вас есть квалифицированный имя, которое относится к типу и зависит от параметра шаблона.