Думаю многие кто работает с шаблонами, знакомы со следующей ситуацией. У нас есть некий шаблонный класс с кучей шаблонных параметров
Пускай это будет некий интерфейс отложенного выполнения задач (активная очередь). И мы хотим расширить её функционал добавив отложенное выполнение и дедлайны. Но не хотим что бы все это было сразу включено, а хотим что бы можно было собирать нужную конфигурацию под свои нужды. Проблема таких шаблонов (у которых много параметров и почти все уже имею значение по умолчанию) в том, что для того что бы переопределить, скажем, последний параметр нам нужно указать все перед ним стоящие.
А ещё лучше, что бы даже порядок задания не имел значение и следующие два, были бы эквивалентны
Но мы прекрасно понимаем, что как только мы поменяли два параметр у нас получится совершенно не то чего мы ожидали (это вам не tuple где порядок указание не имеет значении)
Думаю многие уже подумали о том, что всего этого можно добиться добавив прослойку между нашим типом и пользователем, для которой написать все возможные специализации. Если у вас только 2 шаблонных параметр то да, если 3 то уже сложно, а если добавится 4, 5 то пиши пропало. Да и как правило добавление нового параметра приводит к переделыванию всех предыдущих специализаций (так как в специализации мы не можем увеличивать число шаблонных параметров, а можем их только уменьшать).
Если вас заинтересовало, прошу под кат, я покажу вам как добиться этого
Но для начала немного дёгтя. Шаблонные типы хороши тем, что пользователь может параметризовать их разными типами, в том числе и своими. Способ который я хочу показать не позволяет специализировать шаблон произвольным типом. Т.е. например вы определи свой тип deadline_super который сопоставим с типом deadline, тогда вы можете подставлять его в специализацию шаблона
Но вы не сможете использовать этот тип в том механизме который позволят специализировать шаблон в произвольном виде. Пожалуй это единственное серьёзное ограничение. Учитывая этот аспект становится понятно, что этот механизм удобно использовать при написании модульных компонент, а не расширяемых или полиморфных.
Вся реализация основывается на такой компоненте boost как mpl::set. Я не буду много рассказывать о том, что такое boost::mpl, скажу лишь что mpl::set позволяет создавать аналогичный std::set контейнер но на этапе компиляции и состоящий из типов.
Первое что нам потребуется это способ проверки списка типов на наличие нужного нам типа, и выдачу некоего дефолтного значения (struct disable) в противном случае
Это позволит нам из списка типов узнать был ли задан нужный нам тип и если нет, то использовать дефолтное значение. Как видите данный код зависит только от одного типа который участвует в параметризации конечной структуры (и сообщае�� что мы не используем это конкретный тип), поэтому при добавлении новых шаблонных типов, этот код не будет меняться.
На следующем шаге, мы определяем все типы которые используются в конечном шаблоне
И это то место, которое будет меняться при добавлении новых шаблонных параметров. Но это происходит предельно легко, и никаких 2^n комбинаций перечислять не надо.
Дальше мы вводим прослойку между конечным шаблонным типом и пользователем
Именно она позволяет нам абстрагироваться от числа и порядка указания шаблонных параметров. По сути она объединяет в себе все те (2^n + K) специализаций который нам пришлось бы писать для различного числа заданных шаблонных параметров и их порядка.
Возвращаясь нашему шаблонному типу, я покажу вам как это работает
Использование
Выхлоп
Обратите внимание, что порядок специализации сохранен в нужном виде, не зависимо от того в каком порядке были указаны типы при инстанцировании шаблона.
Вот и все, надеюсь кому то это поможет сделать код более красивым и аккуратным.
Исходникик: git
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
