Pull to refresh

Произвольный порядок списка инициализации шаблона

Reading time4 min
Views5.9K
Думаю многие кто работает с шаблонами, знакомы со следующей ситуацией. У нас есть некий шаблонный класс с кучей шаблонных параметров

struct deferred;
struct deadline;
struct disable;

template<class T, class Deferred = disable, class Deadline = disable>
struct some_container

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

typedef some_container<int, disable, deadline> deadline_container;
<source>
А хотелось бы 

<source lang=cpp>
typedef some_container<int, deadline> deadline_container;

А ещё лучше, что бы даже порядок задания не имел значение и следующие два, были бы эквивалентны

typedef some_container<int, deferred, deadline> full_container1;
typedef some_container<int, deadline, deferred> full_container1;

Но мы прекрасно понимаем, что как только мы поменяли два параметр у нас получится совершенно не то чего мы ожидали (это вам не tuple где порядок указание не имеет значении)
Думаю многие уже подумали о том, что всего этого можно добиться добавив прослойку между нашим типом и пользователем, для которой написать все возможные специализации. Если у вас только 2 шаблонных параметр то да, если 3 то уже сложно, а если добавится 4, 5 то пиши пропало. Да и как правило добавление нового параметра приводит к переделыванию всех предыдущих специализаций (так как в специализации мы не можем увеличивать число шаблонных параметров, а можем их только уменьшать).
Если вас заинтересовало, прошу под кат, я покажу вам как добиться этого

Но для начала немного дёгтя. Шаблонные типы хороши тем, что пользователь может параметризовать их разными типами, в том числе и своими. Способ который я хочу показать не позволяет специализировать шаблон произвольным типом. Т.е. например вы определи свой тип deadline_super который сопоставим с типом deadline, тогда вы можете подставлять его в специализацию шаблона

typedef some_container<int, disable, deadline_super>

Но вы не сможете использовать этот тип в том механизме который позволят специализировать шаблон в произвольном виде. Пожалуй это единственное серьёзное ограничение. Учитывая этот аспект становится понятно, что этот механизм удобно использовать при написании модульных компонент, а не расширяемых или полиморфных.
Вся реализация основывается на такой компоненте boost как mpl::set. Я не буду много рассказывать о том, что такое boost::mpl, скажу лишь что mpl::set позволяет создавать аналогичный std::set контейнер но на этапе компиляции и состоящий из типов.
Первое что нам потребуется это способ проверки списка типов на наличие нужного нам типа, и выдачу некоего дефолтного значения (struct disable) в противном случае

template <class Plugin,  class IsSet>
struct get_plugin_impl;

template <class Plugin>
struct get_plugin_impl<Plugin, boost::mpl::false_>
{
    typedef disable_plugin type;
};

template <class Plugin>
struct get_plugin_impl<Plugin, boost::mpl::true_>
{
    typedef Plugin type;
};

template <class List, class Plugin>
struct get_plugin
{
    typedef typename get_plugin_impl<Plugin,
        typename boost::mpl::has_key<List, Plugin>::type>::type type;
};

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

template <class List>
struct get_plugins
{
    typedef typename get_plugin<List, deferred_plugin>::type deferred;
    typedef typename get_plugin<List, deadline_plugin>::type deadline;
};

И это то место, которое будет меняться при добавлении новых шаблонных параметров. Но это происходит предельно легко, и никаких 2^n комбинаций перечислять не надо.
Дальше мы вводим прослойку между конечным шаблонным типом и пользователем

template<class T, 
         class P1 = disable_plugin,
         class P2 = disable_plugin>
struct container
{
    typedef boost::mpl::set<P1, P2> plugin_list;
    typedef get_plugins<plugin_list> plugs;
    
    typedef typename plugs::deferred deferred;
    typedef typename plugs::deadline deadline;
    
    typedef some_container<T, deferred, deadline> type; 
};

Именно она позволяет нам абстрагироваться от числа и порядка указания шаблонных параметров. По сути она объединяет в себе все те (2^n + K) специализаций который нам пришлось бы писать для различного числа заданных шаблонных параметров и их порядка.

Возвращаясь нашему шаблонному типу, я покажу вам как это работает

#define static_type(name) \
    static const std::string& type() \
    { \
        static std::string type_(name); \
        return type_; \
    } \

struct deferred_plugin
{ static_type("deferred"); };

struct deadline_plugin
{ static_type("deadline"); };

struct disable_plugin
{ static_type("disable"); };

template<class T, class Deferred, class Deadline>
struct some_container
{
    static const std::string& type()
    {
        static std::string type_("some_container<" + 
                                    Deferred::type() + ", " +
                                    Deadline::type() + ">");
        return type_;
    }
};

Использование

   cout << container<int>::type::type() << std::endl;
   cout << container<int, deadline_plugin>::type::type() << std::endl;
   cout << container<int, deferred_plugin>::type::type() << std::endl;
   cout << container<int, deferred_plugin, deadline_plugin>::type::type() << std::endl;
   cout << container<int, deadline_plugin, deferred_plugin>::type::type() << std::endl;

Выхлоп

some_container<disable, disable>
some_container<disable, deadline>
some_container<deferred, disable>
some_container<deferred, deadline>
some_container<deferred, deadline>

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

Вот и все, надеюсь кому то это поможет сделать код более красивым и аккуратным.
Исходникик: git
Tags:
Hubs:
+20
Comments11

Articles

Change theme settings