Некоторые недоразумения в шаблонах C ++ - PullRequest
4 голосов
/ 26 августа 2011

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

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

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

template <typename T> 
class Dozen { 
  public: 
    ArrayInClass<T,12> contents; 
};

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

Я ничего не могу понять.Я очень ценю любую помощь с простыми и понятными словами.

Редактировать:

Класс Array:

template <typename T, int N> 
class ArrayInClass { 
  public: 
    T array[N]; 
};

Ответы [ 6 ]

7 голосов
/ 26 августа 2011

В C ++ есть определенные выражения, которые необходимо знать во время компиляции. Например:

int someArray[30];

30 должен быть константой времени компиляции в C ++. Это могло быть:

int someArray[30 + 3];

Это нормально, потому что у компилятора есть вся информация, необходимая для вычисления во время компиляции, насколько велик массив. Однако:

void MyFunc(int numItems) {
  int someArray[30 + numItems];
}

Это , а не константа времени компиляции, поскольку пользователь может вызвать MyFunc с любым целочисленным значением. Компилятор не знает, насколько велик массив, поэтому это ошибка компилятора.

(примечание: C99 позволяет создавать массивы произвольных размеров, как это. C ++ не делает).

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

template<int ArrayLen> void MyFunc() {
  int someArray[30 + ArrayLen];
}

Это допустимый C ++, потому что каждое использование MyFunc должно указывать целое число времени компиляции: длину массива. Вы не можете просто позвонить MyFunc(), вам нужно позвонить MyFunc<21>(). А поскольку аргументы шаблона должны быть определяемыми во время компиляции значениями, сам пользователь не может предоставить значение, которое не определено во время компиляции.

Поскольку параметры шаблона всегда определены во время компиляции, вы можете вкладывать шаблоны:

template<int ArrayLen> void OtherFunc {
  MyFunc<ArrayLen + 3>();
}

Эта новая функция шаблона вызывает старую функцию с массивом на 3 больше, чем было задано.

1 голос
/ 26 августа 2011

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

#include <iostream>

template <int N>
struct Factorial
{
    enum { value = N * Factorial<N-1>::value };
};

template <>
struct Factorial<1>
{
    enum { value = 1 };
};

// example use
int main()
{
    std::cout << Factorial<5>::value << endl;
    return 0;
}
0 голосов
/ 26 августа 2011

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

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

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

Шаблон - это функция, которую можно параметризировать.В вашем примере Dozen - это шаблон:

template <typename T> 
class Dozen
{
...
};

, где T - это параметр Dozen шаблона.Вкратце, T - это параметр шаблона .

Возможно, поможет простая аналогия.Думайте о шаблоне (здесь это Дюжина ) как слепок скульптуры, который может быть заполнен жидким материалом, который установит внутри броска принятие его формы и в конечном счете произведет скульптуру.Теперь параметр T подобен жидкому материалу (резина, металл, стекло и т. Д.), Который придает скульптуре определенные символы.Таким образом, вы можете использовать одно и то же приведение для создания серии похожих скульптур.

Таким образом, полая полость в представлении представляет собой T параметр шаблона, заполнитель, в который вы будете помещать аргументы шаблона, где вызаполните шаблон.

Итак, это примерно идея параметризации в метапрограммировании.

Переход к аргументу шаблона и пример с комментариями:

  // T states parameter of Dozen template
template <typename T>
class Dozen
{
    // the T is argument used to instantiate concrete type from another template
    ArrayInClass<T,12> contents;
};

Здесь выможет подумать о функции, вызывающей другую функцию и передающей параметр:

void foo(int a)
{
   bar(a);
}

foo не использует саму себя, но передает ее в качестве аргумента bar.Аналогичным образом, Dozen пересылает свой собственный параметр шаблона T в качестве аргумента для шаблона ArrayInClass, чтобы создать конкретный тип этого ArrayInClass.

Наконец, T является компилируемымвыражение времениЭто означает, что это дает значение во время компиляции.(Выражения - это функции языка программирования, которые дают значение).Значением выражения является тип ( T ) или числовая константа ( 12 ).

ArrayInClass<T,12> также является выражением времени компиляции, которое приводит к созданию экземпляраArrayInClass шаблон для производства конкретного типа.Вкратце, выражение типа компиляции может быть использовано для создания другого выражения времени компиляции - для получения другого (сложного) значения.

В метапрограммировании хорошая идея не думать о value сномер или строка.Тип также является значением здесь.

0 голосов
/ 26 августа 2011

ОК, исходя из вашего комментария, вот несколько рассуждений:

Предположим, у вас есть шаблон:

template <typename T> struct Moo { };

Теперь мы знаем, что, чтобы сказать Moo<Bar>, Bar должен быть (тип, который) известен во время компиляции. Пока проблем нет.

Теперь предположим, что мы хотим построить некоторый класс:

class MyClass
{
  int         a;
  double      b;
  Moo<double> m;
};

Это тоже хорошо, потому что Moo<double> является допустимым типом. Опять без проблем. Но теперь давайте обобщим MyClass и сделаем его шаблоном:

template <typename U>
class MyClass
{
  int    a;
  U      b;
  Moo<U> m;
};

Теперь еще раз, чтобы сказать MyClass<Zoo>, Zoo должен быть известен во время компиляции. Из-за этого зависимый тип Moo<U> заменяется на Moo<Zoo> в MyClass<Zoo>, и поскольку Zoo известен, этот тип члена теперь также известен.

Выделенный жирным шрифтом текст, который вы цитируете, просто говорит, что это рассуждение работает, и вы получаете что-то верное.

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

0 голосов
/ 26 августа 2011

Разделы, выделенные жирным шрифтом, говорят, что когда вы определяете класс Foo<T>, этот T должен быть известен компилятору во время компиляции;то есть это не может быть неполным типом.

Для каждого отдельного типа T, с которым вы создаете экземпляр Foo, компилятор создает совершенно новый тип (Foo<int>, Foo<MyClass> и т. Д.), Который не связан ни с одним из других типов, которые вы можетесоздали экземпляр Foo, хотя все они могут иметь такое же поведение, как определено реализацией Foo.

В примере, который вы разместили, я предполагаю, что ArrayInClass<T,N> создает массив типа T с N элементами.

Например,

template< typename T, size_t N >
class ArrayInClass
{
  T myArr_[N];

public:
  // public interface
};

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

0 голосов
/ 26 августа 2011

Это говорит о том, что в вашем шаблоне класса, Dozen<T>, параметр шаблона T также используется в качестве параметризации переменной-члена ArrayInClass<T,N>.

Так что, если вы создадите экземпляр Dozen<T> как, например ::

Dozen<float> my_dozen;

тогда компилятор сгенерирует код следующим образом:

class Dozen<float> { 
  public: 
    ArrayInClass<float,12> contents; 
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...