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'

Вопрос: можно ли отключить эту статическую проверку? Либо через макрос, либо трюк с шаблоном?

Что касается возможных обходных путей:

  1. Приведенный выше код функционально эквивалентен этому рукописному коду. Для этого кода шаблон CRTP работает. Тем не менее, это требует большого количества стандартного кода, который так удобно автоматизирует библиотека Boost.Parameter.
  2. Я мог бы потребовать, чтобы параметр 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<> шаблон, для наглядности.

Когда вы используете политику, вы можете проверить, обернута ли она, и развернуть ее.

Другие вопросы по тегам