Boost.Parameter: именованный аргумент шаблона в сочетании с CRTP
Предупреждение: для объяснения проблемы необходимо длинное введение. Идиома Именованный шаблонный аргумент, впервые описанная в разделе 16.1 Vandevoorde и Josuttis, может быть удобно написана с помощью библиотеки Boost.Parameter.
#include <iostream>
#include <typeinfo>
#include <boost/parameter.hpp>
#include <boost/static_assert.hpp>
struct DefaultPolicy1 {};
struct DefaultPolicy2 {};
typedef boost::parameter::void_ DefaultSetter;
BOOST_PARAMETER_TEMPLATE_KEYWORD(Policy1_is)
BOOST_PARAMETER_TEMPLATE_KEYWORD(Policy2_is)
typedef boost::parameter::parameters<
boost::parameter::optional<tag::Policy1_is>,
boost::parameter::optional<tag::Policy2_is>
> PolicySelector;
template
<
class PolicySetter1 = DefaultSetter,
class PolicySetter2 = DefaultSetter
>
class BreadSlicer
{
typedef typename PolicySelector::bind<
PolicySetter1,
PolicySetter2
>::type Policies;
public:
// extract policies:
typedef typename boost::parameter::value_type<
Policies, tag::Policy1_is, DefaultPolicy1
>::type P1;
typedef typename boost::parameter::value_type<
Policies, tag::Policy2_is, DefaultPolicy2
>::type P2;
};
Приведенный выше код позволяет переопределить необязательные параметры шаблона BreadSlicer
в произвольном порядке, называя их Policy1_is
а также Policy2_is
, Это делает его очень удобным для разработки на основе политик со многими параметрами по умолчанию.
int main()
{
typedef BreadSlicer<> B1;
// can override any default policy
typedef BreadSlicer< Policy1_is<int> > B2;
typedef BreadSlicer< Policy2_is<char> > B3;
// order of policy-setting is irrelevant
typedef BreadSlicer< Policy1_is<int>, Policy2_is<char> > B4;
typedef BreadSlicer< Policy2_is<char>, Policy1_is<int> > B5;
// similar static asserts work for B1 ... B4
BOOST_STATIC_ASSERT((std::is_same<B5::P1, int >::value));
BOOST_STATIC_ASSERT((std::is_same<B5::P2, char>::value));
return 0;
}
Чтобы избежать очень тонких нарушений ODR с дизайном на основе политик (объяснение см. В этом старом посте Александреску), я хотел бы иметь возможность применить шаблон CRTP к именованным аргументам шаблона:
int main()
{
// ERROR: this code does NOT compile!
struct CuriousBreadSlicer
:
BreadSlicer< Policy1_is<CuriousBreadSlicer> >
{};
typedef CuriousBreadSlicer B6;
BOOST_STATIC_ASSERT((std::is_same<B6::P1, CuriousBreadSlicer>::value));
BOOST_STATIC_ASSERT((std::is_same<B6::P2, DefaultPolicy2 >::value));
return 0;
}
Однако вышеприведенная реализация Boost.Parameter не может скомпилироваться, поскольку происходит сбой некоторого внутреннего static_assert с сообщением типа (VC10 SP1)
'main:: CuriousBreadSlicer': неопределенный класс не допускается в качестве аргумента для свойства встроенного типа компилятора '__is_base_of'
Вопрос: можно ли отключить эту статическую проверку? Либо через макрос, либо трюк с шаблоном?
Что касается возможных обходных путей:
- Приведенный выше код функционально эквивалентен этому рукописному коду. Для этого кода шаблон CRTP работает. Тем не менее, это требует большого количества стандартного кода, который так удобно автоматизирует библиотека Boost.Parameter.
- Я мог бы потребовать, чтобы параметр CRTP всегда был первым в списке аргументов шаблона, а не заключать его в
Policy1_is
учебный класс. Это решает ошибки времени компиляции, но теряет порядок переопределения.
Так что, похоже, я - то, что игроки в гольф называют "между клубами". Какое решение будет лучшим?
1 ответ
Минимальный пример без CRTP:
#include <boost/parameter.hpp>
#include <boost/static_assert.hpp>
BOOST_PARAMETER_TEMPLATE_KEYWORD(Policy1_is)
BOOST_PARAMETER_TEMPLATE_KEYWORD(Policy2_is)
typedef boost::parameter::parameters<
boost::parameter::optional<tag::Policy1_is>,
boost::parameter::optional<tag::Policy2_is>
> PolicySelector;
struct foo {};
struct bar {};
struct baz;
typedef typename PolicySelector::bind<foo, baz>::type Policies;
boost::parameter::value_type<Policies, tag::Policy1_is, bar>::type x; // <- !!!
Так boost::parameter::value_type
требует, чтобы селектор политики основывался на полных типах, что не относится к вашим рукописным классам.
Я не совсем уверен, зачем классу нужна собственная политика. Если вам это нужно, возможно, вы могли бы обернуть неполный тип во что-то законченное:
struct CuriousBreadSlicer : BreadSlicer <
Policy1_is<CuriousBreadSlicer *> > // <- compiles
Или вы можете использовать свой собственный wrap_incomplete_type<>
шаблон, для наглядности.
Когда вы используете политику, вы можете проверить, обернута ли она, и развернуть ее.