Compile-time template library
От: Юрий Жмеренецкий ICQ 380412032
Дата: 22.10.05 01:48
Оценка: 75 (8)
В связи с отсутствием времени, интернета и компьютера в ближайшем будущем
решил показать вам свои поделки.

Compile-time template library (сttl) — это небольшая экспериментальная
библиотечка шаблонов, не зависящая от каких-либо сторонних библиотек
(эпизодически boost::preprocessor, можно безболезненно открутить). Но
библиотекой это назвать довольно трудно, поскольку в основном это идеи,
возможно еще сырые, но заслуживающие внимания и критики общественности.
А может быть и нет, посмотрим. Причины, которые толкают меня на показ
объективно сырого кода будут объяснены в дальнейшем. А пока...

В составе присутсвуют:

cttl::lambda - реализация лямбда-нотации для шаблонных функций (да,
существует boost::mpl, погодите ругаться). Используется примерно так:

typedef plus<
    mul<num<2>, mul<_1, _1> > ,
    minus<mul<num<3>, _2>, num<4> > 
> expr;

cout << lambda<expr, num<1>, num<2> >::result::value << endl; // 4


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

cttl::bind — работает почти как лямбда, но не производит вычисления
выражений,- просто заменяет параметры в дереве:

typedef bind<plus<_1, num<2> >, num<4> >::result r;
//r == plus<num<4>, num<2> >


cttl::typelist — список типов. Отличительная особенность-компактность
реализации: preprocessed файл на 64 типа (именно столько шаблонных
параметров поддерживает vc7.1) занимет около 4 кб.

Но все это рабочие инструменты, необходимые для весьма интересной вещи —
обобщенной рекурсии. Для ясности пару примеров:

'Классический' факториал:
template<int n>
struct Factorial {
    enum { value = n * Factorial<n-1>::value };
};

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


Простой пример. Вызов функцией самой себя и специализация для завершения.
Но большинство рекурсивных функций на шаблонах не страдают простотой и
изяществом (хотя это субъективно).

cttl::recursion не поддерживает явное задание результатов отдельных итераций,
а реализует следующий принцип — указывается первый тип, функция
перехода между типами, критерий остановки и тело рекурсии. Значение рекурсии автоматически вычис
сяется, используя исходные данные. Т.е. используя cttl::recursion
невозможно задать отдельное значение (0=>1 для факториала). Этот класс исполь
зуется только для обобщение самого понятия рекурсии, а критерий остановки
задается явно, с использованием предиката для применения к каждому типу(значению).

Вариант факториала с использованием cttl::recursion:

typedef recursion_starter<
    rec::params<num<5> >        // начальное значение
    ,equal<rec::item, num<0> >    // условие завершения
    ,minus<rec::item,  num<1> >     // переход к следующему элементу
    ,mul<rec::item, rec::current>    // f(n)=n*f(n-1)
>::result r;

cout << r::current::value << endl; // 120


сущность rec::item представляет собой текущий итерируемый элемент (по очереди 4,3,2,1)
rec::current — результат рекурсии на каждом шаге ( 5, 20, 60, 120, 120)
еще есть rec::index представляющий из себя номер итерации
все эти 'заменители' можно использовать внутри любой из трех функций.

Это был пример 'чистой' рекурсии. Что значит 'чистая' ? В рассмативаемом контексте
это значит что для _пяти_ значений 5,4,3,2,1 тело функции было вызвано 4 раза.
Так же существует итерационный режим использования, при котором тело функции
задается посредством второго параметра(так как функция перехода в данном примере
вычислялась 5 раз), а функция перехода — четвертым. Такой режим используется для
работы со списками типов, при необходимости обработки каждого элемента в списке

//Теперь вычислим длину списка:

typedef typelist<int, char, float> my_list;
    
typedef recursion_starter<
    rec::params<my_list> 
    ,equal<rec::current, null_list>
    ,rec::index            // возвращаем номер итерации
    ,next<rec::current>    // итерируемся здесь
>::result r3;

cout << r3::index::value << endl;    //3

// вычисление длины списка другим способом
typedef recursion_starter<
    rec::params<my_list, num<0> > 
    ,equal<rec::current, null_list >
    ,plus<rec::item, num<1> >
    ,next<rec::current>    
>::result r4;

cout << r4::item::value << endl; //3

//еще несколько функций:

// получение элемента списка по индексу (нумерация с 1)
// аналог TypeAt Александреску
                    
typedef typelist<int, char, float, double, long> my_list1;
typedef recursion_starter<
    rec::params<my_list1> 
    ,equal<rec::index, num<1+3> > // нужен третий - четвертый не обрабатывается
    ,rec::current
    ,next<rec::current>
>::result r5;

// list<float, list<double, list<ling, null_list> > >
cout << typeid(r5::item).name() << endl << endl;

// получение суммарного рамера типов из списка
typedef recursion_starter<
    rec::params<typelist<int, char, float>, num<0> > // num<0> используется в качестве аргумента 
                                                        // plus для последней итерации 
    ,equal<rec::current, null_list >
    ,plus<rec::item, sizeof_<get_type<rec::current> > >
    ,next<rec::current>
>::result r6;

cout << r6::item::value << endl;    //9
cout << sizeof(int)+sizeof(char)+sizeof(float) << endl;    //9

// начинается самое интересное

// Обращение(реверс) списка
typedef recursion_starter<
    rec::params<typelist<int, char, float>, null_list > 
    ,equal<get_type<rec::current>, null_list>
    ,list<get_type<rec::current>, rec::item> // list на самом деле является метафункцией :)
    ,next<rec::current>
>::result r7;

// list<float, list<char, list<int, null_list > > >
std::cout << typeid(r7::item).name() << std::endl;

// К каждому элементу списка можно применить какую-нибудь метафункцию(любую)...
// например, сделать все типы указателями...

typedef recursion_starter<
    rec::params<r7::item, null_list > 
    ,equal<get_type<rec::current>, null_list>
    ,list<ref<get_type<rec::current> >, rec::item> 
    ,next<rec::current>
>::result r8;

// list<int*, list<char*, list<float*, null_list > > >
std::cout << typeid(r8::item).name() << std::endl;


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

Последние примеры демонстрируют всю(?) силу применяемого подхода и показывают
как можно логично и главное достаточно просто и прозрачно реализовать некоторые
алгоритмы для работы с шаблонами в compile-time.

К сожалению мне физически не хватило времени(буквально на днях вступаю в ряды РА)
для полировки этих шаблонов, поэтому весьма вероятно наличие ошибок и недоработок
(в частности в итерационном режиме некорректно обрабатывается rec::index в случае наличия менее
двух элементов). Из недоработок — recursion_starter не пригоден для использования
внутри lambd'ы, хотя это можно сделать без особых усилий, используя bind, который первоначально для этого и создавался.
Исходники прилагаются.

PS. Это не велосипеды, это 4fun.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.