суббота, 2 мая 2009 г.

Boost.Preprocessor

Препроцессор - штука достаточно неодназначная, что-бы это понять, достаточно поднять дискуссию на эту тему на каком-нибудь формуе, и опасная, так-как он вообще не в курсе синтаксиса языка программирования С++, ничего не знает о пространствах имен, шаблонах и тд. Лучше всего ограничить его применение условной компиляцией а так-же не забывать использовать #undef. Но, если не следовать этой рекомендации, то с его помощью можно делать очень интересные вещи :) Это можно продемонстрировать на примере списков типов, которые описаны в книге Александресску - "Современное проектирование на С++". Вот реализация такого списка
struct empty_t {};

template<class Head, class Tail>
struct cons
{
    typedef Head head;
    typedef Tail tail;
};
а использовать его можно так:
typedef cons< int, cons<long, cons< double, empty_t > > > tl_first;
Но это совсем не удобно, что-бы это исправить, нужен более простой способ объявления списков типов. Самый простой способ это сделать - макросы a-la Александресску:
#define TYPE_LIST_1(t) cons< t, empty_t >
#define TYPE_LIST_2(t1, t2) cons< t1, TYPE_LIST_1(t2) >
#define TYPE_LIST_3(t1, t2, t3) cons< t1, TYPE_LIST_2(t2, t3) >
typedef TYPE_LIST_3(int, long, double) tl_second;
Это просто и понятно, но не очень удобно, хотелось-бы избавиться от размера списка в имени макроса. То-же самое можно реализовать и без макросов, используя шаблоны:
template<class T1, class T2 = empty_t, class T3 = empty_t, class T4 = empty_t>
struct make_typelist;


template<class T1>
struct make_typelist<T1, empty_t, empty_t, empty_t>
{
    typedef cons<T1, empty_t> type;
};

template<class T1, class T2>
struct make_typelist<T1, T2, empty_t, empty_t>
{
    typedef cons<T1, cons<T2, empty_t> > type;
};

template<class T1, class T2, class T3>
struct make_typelist<T1, T2, T3, empty_t>
{
    typedef cons<T1, cons<T2, cons<T3, empty_t> > > type;
};
теперь достаточно написать:
typedef make_typelist<int, long, double>::type tl_third;
и у нас есть список типов. В этом коде мы сталкиваемся с одним из недостатков языка С++ - отсутствием шаблонов с переменным числом параметров, поэтому приходится описывать все необходимые специализации. Страшно представить, что будет, если нам потребуется создать список хотя-бы из десяти элементов. И вот здесь нам может помочь библиотека boost.preprocessor. С ее помощью можно написать вот такой нечитаемый код:
#include <boost/preprocessor/repetition.hpp>
//boost preprocessor
#define MAX_TYPELIST_SIZE 4

template< BOOST_PP_ENUM_PARAMS_WITH_A_DEFAULT(MAX_TYPELIST_SIZE, class T, empty_t)>
struct make_typelist;

#define TUPLE_PRINT(n, i, data) data
#define GEN_MAKETYPELIST(n, i, unused)                                  \
template< BOOST_PP_ENUM_PARAMS(i, class T) >                            \
struct make_typelist<                                                   \
      BOOST_PP_ENUM_PARAMS(i,T)                                         \
      BOOST_PP_COMMA_IF(i)                                              \
      BOOST_PP_ENUM(                                                    \
          BOOST_PP_SUB(MAX_TYPELIST_SIZE,i), TUPLE_PRINT, empty_t) >    \
{                                                                       \
    typedef BOOST_PP_ENUM_PARAMS(i, cons<T), empty_t                    \
            BOOST_PP_REPEAT(i, TUPLE_PRINT, > ) type;                   \
};

BOOST_PP_REPEAT_FROM_TO(1, MAX_TYPELIST_SIZE, GEN_MAKETYPELIST, ~)

typedef make_typelist<int, long, double>::type tl_third;

#undef TUPLE_PRINT
#undef GEN_MAKETYPELIST
В этом примере, препроцессор создаст точно такой-же код как и в предидущем примере. Но теперь, в случае если нам потребуется увеличить максимальный размер списка типов для make_typelist, достаточно изменить MAX_TYPELIST_SIZE. Многие библиотеки boost используют подобную технику для эмуляции шаблонов с переменным количеством параметров, например boost.tuple. В данном конкретном случае, макрос GEN_MAKETYPELIST создает одну из специализаций шаблонного класса make_typelist, в качестве параметров он получает максимальный размер списка типов(n), и количество параметров шаблона(i). Этот макрос используется в BOOST_PP_REPEAT_FROM_TO, для генерации всех необходимых специализаций шаблона. Сама библиотека boost.preprocessor - достаточно простая. Начать ее использовать очень легко, главное знать, для чего она может быть полезна.