Методы обобщенного и метапрограммирования в программной реализации декодера алгебро-геометрических кодов

А. М. Пеленицын, аспирант Южного федерального университета, Ростов-на-Дону

Методы обобщенного и метапрограммирования в программной реализации декодера алгебро-геометрических кодов

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

Введение

Конструкции помехоустойчивых кодов на основе алгебраических кривых (АГ-кодов) и методы их декодирования [1] используют ряд абстрактных математических объектов, программная реализация которых представляет собой более сложную задачу, чем в случае классических кодов. Учитывая тот факт, что «открытых» реализаций кодеков для АГ-кодов почти нет или мало [2, 3], опыт их реализации представляет интерес. Увеличение числа программных пакетов такого типа может также служить накоплению экспериментальных данных в области помехоустойчивого кодирования.

Помимо интереса с теоретико-кодовой точки зрения компьютерное решение ряда алгебраических задач, в том числе из теории кодирования, ставит специфические задачи в программной инженерии. Одним из важных вопросов в таких задачах является выразительность систем типов используемых языков программирования. Было замечено (например, в [4, 3.1]), что система типов языка С++, а именно, методы, основанные на механизме шаблонов, в том числе метапрограммирование, позволяют увеличить выразительность создаваемого программного кода по сравнению с общепринятыми методами объектно-ориентированного

и, тем более, процедурного программирования.

В алгебраических задачах, как правило, приходится иметь дело с рядом абстрактных структур, каждая из которых может иметь различные «носители». Например, реализация арифметики в простых конечных полях отличается от реализации в случае конечных полей, являющихся расширениями простых. Однако с точки зрения алгоритмов над конечными полями это различие не играет никакой роли и по возможности должно быть скрыто за (обобщенными) интерфейсами, что позволит избежать дублирования кода. При этом необходимо сохранять максимальную эффективность. Именно такая задача является почти дословной формулировкой принципа обобщенного программирования Мёссера-Степанова [5] (реализация алгоритмов наиболее независимым от представления входных данных образом). В ряде работ было замечено, что для ее решения, а значит, и для воплощения принципа обобщенного программирования при реализации линейно-алгебраических и графовых алгоритмов, удобно использовать уже упомянутый механизм шаблонов C++ [6, 7, 8]. Данную статью можно считать продолжением указанных исследований применительно к аппарату вычислительной алгебраической геометрии [9, chap. 10].

Статья состоит из введения, двух разделов и заключения. В первом разделе прово-

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

1. Обзор реализации

Создана библиотека, реализующая работу с полиномами многих переменных в объеме, необходимом для выполнения BMS-ал-горитма, который является основным конструктивным звеном в декодирующем алгоритме, а также сам BMS-алгоритм и алгоритм декодирования на его основе, описанный в [9, chap. 10]. В реализацию не включена процедура голосования большинством Фенга и Рао, что несколько снижает декодирующую силу алгоритма, но не снижает значимости полученных в статье результатов. Сообщение о реализации BMS-алгорит-ма сделано в [10], в настоящей статье более подробно рассматриваются, в том числе, приемы, использованные в этой реализации, но не описанные в [10].

При реализации библиотеки использовался язык программирования C++, включая инструменты, появившиеся в его новом стандарте [11] (чтобы особо отметить использование этих инструментов, ниже будет употребляться обозначение «C++11»). В связи с распространением компиляторов, в основном реализующих новый стандарт (несколько последних версий компилятора из набора GCC и версия компилятора Visual

C++ 2010 г.), это не должно накладывать су- Ц щественных ограничений на использование | данной программной реализации.

Библиотека в терминологии C++ является «headers-only», т.е. вся реализация ^ содержится в заголовочных файлах C++, предназначенных для включения в виде текста в использующие их проекты, и, таким образом, существует, прежде всего, в виде открытых исходных кодов. Наиболее важны четыре шаблона классов:

— Point<N, OrderPolicy>,

— Polynomial<T>,

— BMSAlgorithm< SeqT, PolynomialT, OrderPolicy >,

— BMSDecoding<N, ECCodeParams>

(часть параметров шаблонов здесь и далее для краткости опускается). Кроме того, имеется набор вспомогательных шаблонов классов и функций, занимающих в общей сложности около 4 тыс. строк кода вместе с включенной в исходный код документацией. Наличие свободных функций является отступлением от чистого объектно-ориентированного дизайна и следует рекомендациям [12, п. 44], касающимся использования языка программирования C++ как мультипарадигменного. Также, в соответствии с последним, в данной реализации особое внимание уделено применению обобщенного программирования на основе шаблонов C++. Объектно-ориентированный подход используется лишь в малой степени как дополнительное средство поддержки модульности; наследование применяется лишь в нескольких местах для удобства реализации идиом обобщенного программирования; не используется (динамический, основанный на виртуальных функциях) полиморфизм.

1.1. Шаблон класса Point<N>

Шаблон класса Point<N> моделирует точку в N-мерной целочисленной решетке. С точки зрения реализации он представляет собой обертку над шаблоном std: :array<N, int> из нового стандарта библиотеки C++, который, в свою очередь, является стати-

Ч..... 61

ческим массивом с STL-совместимым интерфейсом. К отдельным координатам точки можно обращаться с помощью операции взятия индекса (operator []). Важной частью интерфейса Point<N> являются функции, реализующие различные упорядочения на множестве точек решетки, такие как

byCoordinateLess и totalLess или его синоним operator<. Первая (свободная функция) обеспечивает покоординатное сравнение точек (частичное упорядочение), вторая (функция-член) задает специальное моно-миальное упорядочение [9, с. 7], которое, в частности, является полным порядком,

* что позволяет хранить объекты Point<N> | в упорядоченных ассоциативных контейне-| рах стандартной библиотеки C++ (напри-¡2 мер, в качестве ключей шаблона класса || std::map). Кроме того, имеется набор сво-¡5 бодных функций, реализующих поиск в на-^ боре точек минимумов и максимумов отно-| сительно разных порядков, поиск в наборе §_ точек точки меньше заданной, и т. п. Все это Ц необходимо выполнять на разных стадиях <| BMS-алгоритма.

g Реализация totalLess в форме функции-Ц члена (в отличие от других функций, связан! ных с упорядочением) связана с желанием <1 параметризовать тип точки мономиальным § упорядочением. Такая параметризация дос-I тупна с помощью использования второго па-& раметра шаблона Point<N>, который имеет & значение по умолчанию, соответствующее

* градуированному антилексикографическо-Л му упорядочению. Этот прием будет подроб-

1 но описан в следующем разделе. ||

| 1.2. Шаблон класса Polynomial<T>

&

^ Шаблон класса Poiynomiai<T> представ-

SS ляет тип полинома, параметр типа т обозна-

| чает тип коэффициентов соответствующего

g множества полиномов. Для получения поли-

| номов многих переменных используется свой-

Ц ство, позволяющее представлять полиномы полиномами меньшей степени (см., к при-

2 меру, [13, гл. IV, § 1, п. 5]). Например, по-¡1 лином от двух переменных с коэффициента тами типа т неотличим по алгебраическим

свойствам от полинома от одной переменной с коэффициентами, представляющими собой полиномы от одной переменной с коэффициентами типа т. Таким объектам соответствует тип Polynomial<Polynomial<T>>.

Для удобства использования полиномов многих переменных введен отдельный шаблон класса MVPolyType<n, T>, где первый параметр указывает на количество переменных в результирующем типе. Например, следующие две строки кода определяют переменные одинакового типа, представляющего полином от трех переменных с целочисленными коэффициентами.

Polynomial< Polynomial

< Polynomial<int> > > p;

MVPolyType<3, int>::ResultT q;

Полиномы можно задавать в специальном строковом представлении, перечисляя коэффициенты полинома в квадратных скобках через пробел в порядке возрастания степеней. Например, "[2 о 1]" соответствует полиному х2 + 2. Так как полином от нескольких переменных есть обычный полином с коэффициентами — полиномами от переменных, число которых меньше на единицу числа переменных данного полинома, то строковое представление таких полиномов будет содержать вложенные скобки. Например, "[ [2 о 2] [1] [о 3]]" соответствует полиному Зху + 2У2 + х + 2 (с точностью до возможного переименования переменных).

Для полиномиальных типов перегружены арифметические операции, которые необходимы для реализации BMS-алгоритма и декодера. Это сложение полиномов, умножение полинома на скаляр, умножение полинома на моном (operator«) и вычисление полинома в точке. Таким образом, из основных операций с полиномами не реализовано лишь умножение. Продемонстрируем использование операции умножения на моном. Считается, что моном задается своей степенью, которая, в свою очередь, представлена типом Point<N>. Пример умножения полинома от двух переменных на моном х3у2:

Point<2> mon;

mon [0] = 3;

mon [1] = 2;

p <<= mon;

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

Примером того, как элементы C++11 упрощают решение ряда типичных задач, является агрегатная инициализация объектов Point<N> — код с ее использованием, эквивалентный приведенному выше, выглядит так:

Point<2> mon {3, 2};

p <<= mon;

Чтобы такой синтаксис создания объекта класса стал доступен, необходимо было определить конструктор с параметром типа std::initializer_list<T>, где t указывает ожидаемый тип элементов в фигурных скобках.

Для полиномов многих переменных нельзя однозначно («наилучшим образом») определить степень. Для выполнения BMS-ал-горитма требуется задавать некоторое мо-номиальное упорядочение на множестве мономов, и определить степень возможно. Однако работа со степенями полиномов, получаемых на каждой итерации алгоритма, ведется в значительной мере независимо от самих полиномов: на каждой итерации сначала определяется геометрический образ очередного приближения к решению, составленный из степеней будущих полиномов в N-мерной решетке, а затем для каждой степени строится полином. Указанный подход обусловил следующее решение: в данной реализации тип полинома не хранит никакой информации о своей степени (что бы ни подразумевалось под степенью полинома от нескольких полиномов), если такая информация нужна клиенту типа, он должен хранить и обновлять ее самостоятельно.

1.3. Шаблон класса BMSAlgorithm Ц

Шаблон класса BMSAlgorithm<Polyno- | mialT> параметризован типом полиномов (в частности, алгоритм может работать с полиномами различного числа перемен- ^ ных) и имеет довольно простой интерфейс, отражающий назначение и использование BMS-алгоритма. Напомним, что BMS-алго-ритм по заданной финитной n-мерной последовательности над конечным полем Nn ^ Fq строит конечное множество полиномов от n переменных, называемое минимальным множеством этой последовательности. Интерфейс, который должна поддерживать последовательность, подаваемая на вход алгоритму, предельно сжат. Большая часть операций, проводимых с последовательностью в ходе алгоритма, аналогична операциям, определенным для типа многочленов. Конструктор класса принимает последовательность и точку, обозначающую конец последовательности, алгоритм обрабатывает все элементы в диапазоне от элемента с мультииндексом (0, ..., 0) до этого маркера конца последовательности в порядке, заданном используемым мономиальным упорядочением (напомним, что конкретное упорядочение — часть описания типа точки). Метод данного шаблона класса computeMinimalSet возвращает коллекцию с STL-совместимым интерфейсом, содержащую выход алгоритма. Тип этой коллекции является частью определения шаблона класса, к нему можно обратиться следующим образом:

BMSAlgorithm<PolynomialT>::Polynomial Collection

1.4. Шаблон класса BMSDecoding

Интерфейс типа BMSDecoding достаточно лаконичен: в конструкторе задается параметр l кода Cl [9, chap. 10], единственная открытая функция-член decode принимает вектор, пришедший по каналу, представленный типом BMSDecoding::FieldElemsCollec tion с STL-совместимым интерфейсом (в текущей реализации это std::vector).

2. Некоторые приемы обобщенного и метапрограммирования в реализации декодера одного класса алгебро-геометрических кодов

2.1. Рекурсивное инстанцирование шаблонов

Важным проектным решением, опирающимся на особенности шаблонов C++, является рекурсивное определение полиномов многих переменных: Polynomial^.. Polynomial<T>...>, где глубина вложенности равна n, для моделирования полинома от n в переменных. Идея типа, параметризованного самим собой, так называемое «рекурсив-

* ное инстанцирование» шаблонов классов, | является известным шаблоном проектиро-| вания [14, гл. 21]. Однако приемы обработ-¡2 ки такого рода структур все еще недоста-I точно развиты.

¡5 При появлении рекурсивной структуры ^ возникает вопрос о способе ее обхода. В раз-| ных ситуациях это может потребовать более S^ или менее сложных шаблонных техник. При-Ц ведем некоторые из них. Заметим, что часть <| примеров касается перегруженных опера-g ций для полиномов, и поскольку в реализации Ц использована «каноническая форма» [12, п. ! 27] арифметических операций, вся реали-S. зация помещена в присваивающие версии § операций, которые реализованы как функ-

1 ции-члены шаблона класса Polynomial<T>. & При создании шаблонов классов с ис-& пользованием рекурсивного инстанциро- вания обычно предпринимаются действия

* для облегчения их использования. В дан-

|| ном случае для этой цели создан шаблон

Л MVPolyType<n, т>. Его определение дает

S^ представление о типичном подходе к работе

^ с рекурсивно инстанцируемыми типами. с

SS template<int VarCnt, typename Coef>

§ struct MVPolyType {

ss

¡2 typedef Polynomial<

о

jg // "recursive call" to MVPolyType: <u

Э typename MVPolyType<VarCnt - 1,

Ms

Coef>::ResultT >

2 ResultT; ¡1

if };

§ template<typename Coef>

struct MVPolyType<1, Coef> {

typedef Polynomial<Coef> ResultT;

};

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

Аналогичным образом внутри типа полинома Polynomial<T> задается константа var_ cnt, определяющая количество переменных полинома в зависимости от типа T, и синоним типа coefT для обозначения фактического типа коэффициентов полинома в случае нескольких переменных. Например,

Polynomial<Polynomial<int>>::VAR_ CNT

равно 2 и Polynomial<Polynomial<int> >::CoefT

обозначает int.

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

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

Polynomial operator *=

{

for (typename StorageT::iterator it = data.begin();

it != data.end(); ++it) {

(*it) *= c; }

return *this;

}

Тип полинома является, по сути, контейнером особого рода и реализован на базе одного из стандартных контейнеров, который скрыт за синонимом типа StorageT. Соответствующее поле шаблона класса называется data.

Под умножением на скаляр понимается вызов для полинома операции '*' с объектом, тип которого совпадает с coefT. Непосредственно умножение производится вызовом той же операции для каждого элемента контейнера data. Если мы имеем дело с полиномом одной переменной, то data должен содержать элементы типа coefT, для которого, таким образом, должна быть определена операция '*' (тип коэффициентов полинома должен допускать умножение). В ином случае (data содержит элементы другой инстанции Polynomial) снова будет вызвана приведенная выше функция, однако изменится тип Polynomial — теперь это будет тип полинома от переменных, число которых меньше на единицу. Еще раз подчеркнем, что описанная логика полностью реализована в приведенном выше коде, поэтому не требуется, к примеру, использовать специализацию шаблонов, чтобы явно останавливать рекурсию.

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

ном и обращения к коэффициенту полинома Ц от многих переменных по его мультииндек- | су (иначе говоря, по мультистепени монома, при котором стоит запрашиваемый коэф- ^ фициент). В обоих случаях требуется осу- ^ ществлять одновременный обход объекта типа Polynomial<...Polynomial<T>...> и объекта типа Point<N>, где число n совпадает с глубиной вложенности типа полинома (а значит, в соответствии с принципами библиотеки, с числом переменных полинома).

Рассмотрим реализацию операции обращения к коэффициенту полинома по его (мульти) индексу. В частном случае, при работе с полиномом от одной переменной обращаться к коэффициенту хотелось бы по обычному целочисленному индексу, по причине чего реализована версия operator [] (int). Для обращения к коэффициенту полинома от многих переменных предоставляется перегруженная версия

operator [] (Point<VAR_CNT>) (размерность точки должна совпадать с количеством переменных полинома). Обсудим вопрос о том, как должна продвигаться рекурсия.

Пусть задана точка р типа Point<vAR_ CNT>. Для полинома от многих переменных нужно обратиться к p [0]-му элементу в контейнере data (это полином от переменных, количество которых меньше на одну) и снова вызвать для него operator [] (Point<vAR_cNT>) с аргументом, представляющим как бы срез точки p: точку со всеми элементами p, кроме p [0]-го. Для этой цели был создан шаблон класса siice<Dim, Offset>, который хранит ссылку на исходную точку размерности Dim и поддерживает операцию взятия индекса, причем при обращении к i-му элементу по индексу возвращается Offset + i-й элемент исходной точки. Используя этот шаблон в реализации operator [] (Point<vAR_cNT>) хотелось бы написать что-то наподобие (data [p [0]]) [make_ slice (p)], где функция make_slice создает срез, исключающий первый элемент точки. При этом в интерфейс полинома нужно до-бавитьoperator [] (Slice<Dim, Offset>) , в котором будет написано примерно то же,

v 65

«

0

1 I

s

I ^

о Ï

0 £

s

1

I н

t s

1 &

>!S

I

I

00

1

s 00

0

1

5

0

6

Е

1

s

S

0

1 л

£ о

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

Для остановки рекурсии в шаблонных типах обычно используется специализация шаблона (как в примере с MVPolyType). В данном случае нужно было бы создать специализацию operator [] (Slice<Dim, Dim-1>) . Однако в языке C++ [14, п. 12.3.3] запрещается специализировать шаблонные члены шаблонов класса (именно таким является operator [] (siice<Dim, offset>)). Так появляется еще один уровень косвенности, вместо (data [p [0]]) [make_slice (p)] используется вызов отдельной свободной функции apply_

subscript (data [p [0]], make_slice (p)) ,

которая либо продвигает рекурсию, вызывая

operator [] (Slice<Dim, Offset>) с переданным ей срезом, либо (в своей специализации) останавливает рекурсию, вызывая operator [] (int) с нулевым элементом полученного среза (этот элемент стоит в последней координате исходной точки).

Удобно считать, что при обращении по индексу, выходящему за пределы текущего контейнера, нужно вернуть «нулевое значение». Такое значение получается с помощью шаблона CoefficientTraits.Итого-вый код, реализующий операцию обращения по индексу, выглядит так:

template<typename T, typename S, typename Pt>

T applySubscript(S const & el, Pt const & pt) {

return el[pt]; // call for operator[]

template<typename T, typename S, int Dim>

// recursion stop T applySubscript(S const & el, Slice<Dim, Dim-1> const & pt {

return el[pt[0]]; // call for operator[]

template<typename T>

typename Polynomial<T>::CoefT Polynomial<T>::operator[](Point<VAR_ CNT> const & pt) const { if (pt[0] < 0 || data.size() <= pt[0]) return CoefficientTraits<CoefT>::addI d(); else return

applySubscript<Polynomial<T>::CoefT>(d ata[pt[0]],

make_slice(pt)); }

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

Код operator [] (Slice<...>) не приводится, так как он полностью повторяет

operator [] (Point<VAR_CNT>) , а operator

[] (int) отличается только тем, что вместо вызова apply_subscript стоит data [pt] (pt — полученное целое число).

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

2.2. Характеристики типов и проблема дублирования в них кода

Упомянутый выше шаблон Coefficient Traits представляет собой пример шаблона класса характеристик, или, в другом переводе, свойств типов (type traits) [14, гл. 15],

[15, п. 2.10]. В данном случае этот класс предоставляет необходимую полиному информацию о типе коэффициентов, а именно, значение этого типа, играющее роль аддитивно нейтрального элемента (см. пример выше), мультипликативно нейтрального:

CoefficientTraits<T>::multId

функцию, возвращающую аддитивно обратный элемент по заданному:

CoefficientTraits<T>::addInverse

и мультипликативно нейтральный:

CoefficientTraits<T>::multInverse

Применение характеристик основано на использовании специализаций шаблонов. К примеру, если клиент шаблона класса полинома инстанцирует его с типом Bar для коэффициентов, нулевое значение которого хранится в статической константе Bar::ZERo, то клиенту нужно предоставить специализацию CoefficientTraits, функция addid которого возвращает Bar::ZERo. Имеется общее определение coefficientTraits<T>, которое будет использовано, если клиент не сделает последнего, оно реализует наиболее принятое поведение (например, нулевое значение создается с помощью вызова конструктора без параметров типа т).

При описании характеристик типов обычно упускают вопрос: как избежать дублирования в разных инстанциях шаблона характеристик. Он представляется достаточно общим, в данном случае он возникает из-за того, что библиотека NTL [16], которая предоставляет реализацию арифметики в конечных полях, имеет четыре класса для полей разного типа: простых полей характеристики, отличной от двух, расширенных полей характеристики, отличной от двух, F2 и его расширений. При этом все четыре класса имеют почти идентичный интерфейс, поэтому при создании специализаций

CoefficientTraits для них пришлось бы

четыре раза повторять один и тот же код. Решение проблемы предложено в данной библиотеке и основано на таких библиотеках, как enable_if и MPL (Metaprogramming Library) коллекции библиотек Boost [17].

Семейство шаблонов enable_if позво- Ц ляет включать или исключать отдельные | специализации шаблонов классов (а также шаблонов функций) на основе свойств ^ аргументов шаблонов. Свойство шаблон- ^ ного аргумента, которое необходимо проверять, — это принадлежность данного типа к множеству типов NTL, моделирующих конечные поля. Работа с наборами типов является одним из ключевых моментов метапрограммирования на шаблонах C++ и реализована в более старой библиотеке Loki (в виде «списков типов» — type lists из [15, гл. 3] — основополагающем труде по обсуждаемым в данной статье вопросам проектирования на C++) и в более современной — Boost. MPL (см. также Sequence Classes [4, 5.8]).

Вначале необходимо объявить коллекцию типов, принадлежность к которой будет проверяться далее:

typedef boost::mpl::vector<

NTL::GF2E, NTL::ZZ_ pE, NTL::GF2, NTL::ZZ_p> NtlFieldTypes;

Для использования enable_if в шаблон класса CoefficientTraits необходимо добавить один параметр, с помощью которого enabie_if будет управлять специализациями CoefficientTraits:

template<typename CoefT, typename

Enable = void>

struct CoefficientTraits {

/* general implementation */

};

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

template<typename T> struct CoefficientTraits< CoefT,

typename boost::enable_if<

boost::mpl::contains<NtlFi eldTypes, CoefT>

>::type > { // specific implementation for NTL-

types ... };

4.....67

со

0

1

а s

I Si

о

"т

0

£

S

1 I

и

S

1 t 'S

I

I I

со

Ii

5 00

0

1

6

о &

is Si

=s

0

1 л

£ о

2.3. Классы стратегий

Еще один прием, классы стратегий, обеспечивает параметризацию типа точки используемым в алгоритме мономиальным упорядочением, она основана на классах стратегий (policy classes) [15, гл. 1]. Стратегия — связанный набор действий, относящихся к разрабатываемому типу, ортогональный к остальной функциональности этого типа и нуждающийся в параметризации. Способ упорядочения точек N-мерной целочисленной решетки — хороший претендент на вынесение его кода в стратегию. Общая идея реализации состоит в том, чтобы добавить в разрабатываемый шаблонный тип еще один шаблонный параметр шаблона и наследоваться от него.

typename T, template <typename>

class Policy = DefaultPolicy> class

MyClass: public Policy<T> { /*...

*/};

В классической работе [15] предлагается публичное наследование, при котором составляющие базового класса попадают в интерфейс производного. В нашей реализации клиенту не предоставляется непосредственный доступ к функциям базового класса, вместо этого их вызов помещен в интерфейсные функции производного класса (в данном случае, типа точки). Например, преобразование данной точки в следующую за ней относительно мономи-ального упорядочения:

inline

Point<Dim, OrderPolicy>&

Point<Dim,

OrderPolicy>::operator++(){ inc(data); return *this;

}

Функция inc () определена в шаблоне

OrderPolicy, от которого наследуется Point

аналогично:

return totalLess (data, other, data);

Функция totalLess () также определена в шаблоне OrderPolicy. Таким образом, шаблону OrderPolicy следует знать о типе поля data точки, который исполь-

зуется в качестве параметра шаблона

OrderPolicy. template<

int Dim, // point dimension template <typename PointImpl> class OrderPolicy =

GradedAntilexMonomialOrder> class Point : OrderPolicy< std::array<int, Dim> > { /* ... */ };

2.4. С++11 и «функциональный» стиль STL

Наиболее интересные задачи, связанные с проектированием, возникают при реализации относительно низкоуровневых сущностей, таких как точки целочисленной решетки и полиномы. Реализация более высокоуровневых частей, связанных непосредственно с помехоустойчивым кодированием, более очевидна и выполняется в обычном стиле STL, который является одним из главных воплощений идей обобщенного программирования по Степанову. Стоит заметить, что с появлением в C++11 лямбда-функций программирование в стиле STL стало намного более удобным и прозрачным. Как указывает автор C++ Бьерн Страуструп в своем докладе: «STL и в целом обобщенное программирование многим обязаны функциональному программированию... (однако — авт.) функциональные объекты, (в дополнение к которым и пришли лямбда-функции C++11 — авт.), являются не самым элегантным способом выражения идеи функций высших порядков» [18, 4.1.4]. Приведем пример, демонстрирующий стиль STL и использование лямбда-функций в рассматриваемой библиотеке.

В ситуации декодирования одним из основных входных параметров является полученное по каналу «слово» r — n-мерный вектор над некоторым конечным полем. Синдром — это отображение, которое моному m ставит в соответствие скалярное произведение r и вектора, полученного вычислением монома m в n точках P: некото-

рой алгебраической кривой. С помощью вычисления синдрома в наборе мономов m, которые образуют базис в некотором специальном линейном пространстве, получается «синдромная последовательность»1.

using namespace std::placeholders; auto syndromComponentAtBasisElem =

// declare lambda-function: [this,&received](BasisElem const & be) ->

typename SyndromeType::value_type { auto tit =

boost::make_ transform_iterator( this->curvePoints.begin(), std::bind( computeMonomAtPoint<Field, BasisElem, CurvePoint>, be, _1)); return typename SyndromeType::value_type( be,

std::inner_ product(received.begin(), received. end(),

tit, FieldElemTraits<Field>::addId ())); };

std::transform(basis.begin(), basis.end(),

std::inserter(syn, syn.begin()), syndromComponentAtBasisElem);

В первой строке импортируются имена из стандартного (начиная с C++11) пространства имен std::placeholders, которые позволяют использовать выражения вида _1 при связывании (std::bind) отдельных аргументов функции (это еще одна черта функционального программирования в C++ — частичное применение функций). Далее начинается объявление лямбда-функции, ссылка на которую помещается в переменную syndromComponentAtBasisElem, автоматически определяемого компилятором

1 Разрывы в операторных строках, не обусловленные форматом колонки, введены автором. — Прим. ред.

типа (auto). Лямбда-функция принимает Ц на вход один аргумент типа BasisElement, | который используется для обозначения мономов, и возвращает пару, состоящую ^ из своего аргумента be и значения синдро- ^ ма в этом мономе. Функция boost: :make_ transform_iterator создает специальный адаптер с интерфейсом итератора, который получается проходом переданного ей итератора по другой коллекции после применения ее переданной функции. В данном случае адаптируется коллекция точек кривой путем вычисления каждой точки в данном мономе be и в результате получается итератор по тому вектору, который будет участвовать в скалярном произведении, упомянутом в определении синдрома. Само скалярное произведение считается с помощью стандартного алгоритма std::inner_ product. Синдромная последовательность вычисляется с помощью стандартного алгоритма std::transform.

В самом начале объявления лямбда-функции можно видеть перечисление параметров из контекста текущей функции, точнее, функции-члена класса BMSDecoding, которые она (лямбда-функции) захватывает: [this, &received]. Процесс такого захвата объектов объемлющего контекста носит в функциональном программировании название замыкания (closure), однако во многих функциональных языках перечисления захватываемых параметров не требуется. В языке C++ это является частью решения так называемой upwards funarg problem [19], связанной с тем, что захватывающая функция может иметь большую длительность жизни по сравнению с функцией, в которой были объявлены захватываемые параметры. Особо остро такая проблема стоит в языках, не обладающих средствами сборки мусора [20] (к каковым относится и C++), и не очень элегантное решение, связанное с явным перечислением аргументов, является, по всей видимости, неизбежной платой за использование механизма замыканий в C++.

со

0

if

1 S I Si

о

"т

0

£

S

1 if

и

S

1 Л

I л

со

5

СО

0

1

6

О &

Е

Si

=s

0

1 л

£ о

Заключение

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

Список литературы

1. H0holdt T., van Lint J. H, Pellikaan R. Algebraic geometry codes // Handbook of Coding Theory, V. S. Pless, W. C. Huffman, and R. A. Brualdi, Eds. Amsterdam, The Netherlands: Elsevier. 1998. Р. 871- 961.

2. Madelung Y. Implementation of a decoding algorithm for AG-codes from the Hermitian curve, Rept. IT 93- 137, Inst. Circuit Theory Telecom., Tech. Univ. of Denmark, Lyngby, Denmark, Apr. 1993.

3. Маевский А. Э, Пеленицын А. М. О программной реализации алгебро-геометрического кодека с применением алгоритма Сакаты. В сб. «Материалы X Международной научно-практической конференции "Информационная безопасность"», ч. 2. Таганрог: ЮФУ. 2008. С. 55 - 57.

4. Abrahams D., GurtovoyA. C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond. Addison-Wesley, 2004.

5. Musser D. A., Stepanov A. A. Generic Programming // Proceeding of International Symposium on Symbolic and Algebraic Computation. Vol. 358 of Lecture Notes in Computer Science. Rome. Italy. 1988. Р. 13 - 25.

6. Siek J. G., Lumsdaine A. The Matrix Template Library: A generic programming approach to high performance numerical linear algebra // In International Symposium on Computing in Object-Oriented Parallel Environments, 1998.

7. Siek J. G, Lee L.-Q, and Lumsdaine A. The generic graph component library. In Proceedings of the 1999 ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications. ACM Press, 1999. Р. 399 - 414.

8. Veldhuizen T. L. Arrays in Blitz++. // Proceedings of the 2nd International Scientific Computing in Object-Oriented Parallel Environments (ISCOPE'98), vol. 1505 of Lecture Notes in Computer Science. Springer-Verlag, 1998.

9. Cox D. A, Little J. B, O'Shea D. B. Using Algebraic Geometry, Second Edition. Springer, 2005. — 496 p.

10. Пеленицын А. М. О реализации n-мерного BMS-алгоритма средствами обобщенного программирования // Труды научной школы И. Б. Симонен-ко. Ростов н/Д: изд-во ЮФУ, 2010. С. 197 - 203.

11. International Standards Organization: Programming Languages — C++. International Standard ISO/IEC 14882:2011.

12. Саттер Г., Александреску А. Стандарты программирования на С++. М.: Вильямс, 2005. — 224 с.

13. Бурбаки Н. Алгебра (Многочлены и поля. Упорядоченные группы). М.: Наука, 1965. — 300 с.

14. Вандевурд Д., Джосаттис Н. Шаблоны С++: справочник разработчика. М.: Вильямс, 2003. — 544 с.

15. Александреску А. Современное проектирование на C++. М.: Вильямс, 2002. — 336 с.

16. Shoup V. NTL: A Library for doing Number Theory // URL: http://shoup.net/ntl/.

17. Boost C++ Libraries. URL: http://www.boost.org/.

18. Stroustrup B. Evolving a language in and for the real world: C++ 1991 - 2006 // Procs. of the ACM HOPL - III. 2007. P. 4 - 1- 4 - 59.

19. Moses J. The Function of FUNCTION in LISP, or Why the FUNARG Problem Should be Called the Environment Problem // MIT AI Memo 199, 1970.

20. Appel A. W, Shao Z. An Empirical and Analytic Study of Stack vs. Heap Cost for Languages with Closures // Princeton CS Tech Report TR-450-94, 1994.