Где и почему я должен поместить ключевые слова "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 ответов


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

t * f;

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

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

как компилятор найдет какое имя!--12--> относится к, если 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 существует для имен, обозначающих шаблоны, на что намекает вводный текст.

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

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

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

зависимости

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

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

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

  • зависимые типы (e.g: параметр шаблона типа T)
  • зависимые от значений выражения (e.g: a параметр шаблона не-типа N)
  • зависимые от типа выражения (e.g: приведение к параметру шаблона типа (T)0)

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

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

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

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

имя-это использование идентификатора (2.11), оператор-функция-id (13.5), преобразование-функция-id (12.3.2) или шаблон-id (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> is еще тип-зависимого имени (ведь T также зависит от типа). Но!--26--> известен как тип-компилятор будет "волшебно" выглядеть в этот вид зависимых типов, чтобы понять это.

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) и его is текущий экземпляр и имя не были найдены в нем или его зависимых базовых классах, а также зависимые базовые классы.

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

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

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

правила 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 to A::f во время создания экземпляра и все в порядке. Однако C++11 немедленно связывает его с B::f и требует двойной проверки при создании экземпляра, проверяя, соответствует ли поиск по-прежнему. Однако при создании C<A>::g, the Правило Доминирования применяется и поиск будет найти 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", который является шаблон-id зависит от SomeTrait<T> и
    • "типа" в конце (F) зависит NestedTrait, от которой зависит SomeTrait<T> и
  • G)
    • "сведения", который выглядит как функции-члена шаблона, это косвенно зависимый-именем С типом фу зависит от экземпляра SomeTrait<T>.

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

РЕШЕНИЕ

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

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

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



МОГУ Я ПРОСТО ВСТАВИТЬ ключевые слова ПЕРЕД ЛЮБЫМ ИМЕНЕМ?

"могу я просто придерживаться typename и template перед любым именем? Я не хочу беспокоиться о контексте, в котором они появляются...


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


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


общее правило для нанесения 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++ подлежат "двухфазному поиску имен". Когда шаблон изначально анализируется (до создания какого-либо экземпляра), компилятор ищет не зависимые имена. Когда происходит конкретный экземпляр шаблона, параметры шаблона к тому времени известны, и компилятор выглядит up зависимые имена.

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


резюме

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