Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [ 20 ] 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

#define typelist 1Ct1) Typelist<Tl, NullType> #define typelist 2Ct1, t2) Typelist<Tl, typelist 1Ct2) > #define typelist 3Ct1, t2, t3) Typelist<Tl, typelist 2Ct2, t3) > #define typelist 4Ct1, t2, t3, t4) \ Typelist<Tl, Typelist 3CT2, тЗ, Т4)

#define TYPELIST 50C...) ...

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

Теперь можно сформулировать более удобное определение списка целочисленных типов Signedlntegrals.

typedef TYPELIST 4(signed char, short int, long int) Signedlntegrals;

Линеаризация создания списков типов - всего лишь начало. Манипуляции со списками типов все еше неудобньг. Например, доступ к последнему элементу списка Signedln-tegral вынуждает использовать конструкцию Signedlntegrals: :Tail: :Tail::Head. He вполне понятно, как мы сможем манипулировать списками типов в обобшенном виде. Итак, пришло время определить основные операции над списками типов в терминах элементарных операций над списками значений.

3.4. Вычисление длины списка

Рассмотрим простую операцию. Задан список типов TList. На этапе компиляции нужно получить константу, равную его длине. Эта константа должна бьпъ статической, поскольку список типов является статической конструкцией, и естественно ожидать, что все вычисления, относящиеся к нему, выполняются именно на этапе компиляции.

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

Код, вычисляющий длину списка типов, довольно лаконичен.

template <class TList> struct Length; template <> struct Length<NullType> {

enum { value = 0 };

template <class T, class u> struct Length< Typelist<T, u> > {

enum { value = 1 +Length<u>::value };

В переводе на человеческий язык, это означает: "Длина нулевого списка типов равна 0. Длина любого другого списка типов равна 1 плюс длина его оставшейся части".

Реализация структуры Length использует частичную шаблонную специализацию (глава 2), позволяющую различать нулевой тип и список типов. Первая специализация структуры Length является полной и соответствует только типу NullType. Вторая, частичная, специализация соответствует любому классу Typelist<T, и>, включая составные списки, в которых класс и, в свою очередь, является классом Typel i st<v, w>.



Во второй специализации вычисления проводятся рекурсивно. Величина value в ней определяется как 1 (с учетом головы списка т) плюс длина хвоста списка. Когда в хвосте списка остается единственный класс NullType, обнаруживается совпадение с первым определением, и рекурсия останавливается. В результате вычисляется величина, равная длине списка. Допустим, например, что нам нужно определить массив в стиле языка С, в котором содержатся указатели на объекты класса std: :type info для всех целочисленных типов со знаком. Используя структуру Length, можно написать следующий код.

std::type info* intsRtti[Length<signedintegrals>::value];

Bo время вычислений на этапе компиляции в памяти будут размещены четыре элемента массива i ntsRtti

3.5. Интермеццо

Впервые проблема шаблонных метапрограмм обсуждалась в книге Veldhuizen (1995). Затем эта тема глубоко изучалась в работе Czarnecki and Eisenecker (2000), которая содержала полную коллекцию имитаций выполнения операторов языка С++ на этапе компиляции программы.

Идея и реализация структуры Length напоминают классический пример рекурсии: алгоритм, вычисляющий длину односвязного списка структур. (Однако есть два существенных отличия: алгоритм для структуры Length выполняется на этапе компиляции и применяется к типам, а не к значениям.)

Возникает вопрос: можно ли разработать итеративный, а не рекурсивный вариант структуры Length? Помимо всего прочего, итерация более естественна для языка С++, чем рекурсия. Ответ на этот вопрос приведет нас к реализации других функциональных возможностей класса Typelist.

Ответ оказался отрицательным по весьма интересной причине.

Средства языка С++, предназначенные для программирования на этапе компиляции, состоят из шаблонов, целочисленных вычислений и определений типов (операторы typedef). Посмотрим, как работает каждый из этих инструментов.

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

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

Определения типов (операторы typedef) могут рассматриваться как введение констант, задающих имя типа. И вновь после определения все эти имена оказываются зафиксированными - в дальнейшем переопределить символ, введенный оператором typedef, невозможно.

Эти две особенности вычислений на этапе компиляции делают их принципиально несовместимыми с итерациями. Во время итераций вычисляется значение итератора.

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



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

3.6. Индексированный доступ

к элементам списка типов желательно иметь индексированный доступ. Это позволит организовать линейный доступ к элементам, упрощая манипуляции со списками. Разумеется, как и все остальные сущности, с которыми мы работаем, индекс должен быть статической величиной.

Объявление щаблона для индексированной операции может выглядеть следующим образом.

template <class TList, unsigned int index> struct TypeAt;

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

Входные данные: список типов TList, индекс i Результат: внутренний тип Result

Если список TList не пуст и индекс i равен нулю,

то класс Result - это голова списка TList. Иначе,

если список TList не пуст и индекс i не равен нулю,

то класс Result получается путем применения алгоритма TypeAt к хвосту списка TLi st и индексу i -1. Иначе происходит выход за пределы допустимого диапазона изменения индекса, который порождает сообщение об ощибке на этапе компиляции.

Ниже приведено воплощение алгоритма TypeAt на языке С++.

template <class Head, class Tail> struct TypeAt<Typelist<Head, Tail>, 0> {

typedef Head Result;

template <class Head, class Tail, unsigned int i>

struct TypeAt<Typelist<Head, Tai1>, i>

typedef typename TypeAt<Tail, i-l>::Result Result;

Если вы попробуете выйти за пределы допустимого диапазона изменения индекса, компилятор сообщит, что специализации TypeAt<Nul 1туре, х> не существует. Здесь символ x означает величину, на которую вы превысили размер списка. Это сообщение могло бы быть более информативным, но сойдет и так.

В библиотеке Loki (файл Typelist.h) определен вариант структуры TypeAt под названием TypeAtNonStrict. Эта структура реализует некоторые функциональные возможности структуры TypeAt с той лищь разницей, что выход за пределы допусти-



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [ 20 ] 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105