Search code examples
c++templatescrtpnamed-parametersboost-parameter

Boost.Parameter: named template argument in combination with CRTP


Warning: longish introduction ahead needed to explain the problem. The Named Template Argument idiom first described in ch 16.1 of Vandevoorde and Josuttis can be conveniently written with the Boost.Parameter library

    #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;
    };

The above code allows to override the optional template parameters of BreadSlicer in arbritrary order by naming them Policy1_is and Policy2_is. This makes it very convenient to to policy-based design with many defaulted parameters.

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;
}

In order to avoid very subtle ODR violations with policy-based design (for an explanation see this old post by Alexandrescu), I would like to be able to apply a CRTP pattern on the named template arguments:

    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;
    }

However, the Boost.Parameter implementation above fails to compile because some internal static_assert fails with a message like (VC10 SP1)

'main::CuriousBreadSlicer' : an undefined class is not allowed as an argument to compiler intrinsic type trait '__is_base_of'

Question: can this static checking be turned off? Either through a macro or a template trick?

As to possible work-arounds:

  1. The above code is functionally equivalent to this handwritten code. For that code, the CRTP pattern does work. However, it requires a lot of boiler-plate code that Boost.Parameter library so conveniently automates.
  2. I could require the CRTP parameter to always be first in the list of template arguments and not wrap it in a Policy1_is class. This solves the compile time errors, but it loses the order independence of overriding.

So it seems that I am what golf players call "in between clubs". Which solution would be best?


Solution

  • A minimal example with no 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; // <- !!!
    

    So boost::parameter::value_type requires the policy selector to be based on complete types, which is not the case for your handwritten classes.

    I'm not completely sure why a class would need itself as its own policy. If you need that, perhaps you could wrap an incomplete type in something complete:

    struct CuriousBreadSlicer : BreadSlicer <
              Policy1_is<CuriousBreadSlicer *> > // <- compiles
    

    Or you could use your own wrap_incomplete_type<> template, for clarity.

    When you use the policy, you can check if it's wrapped, and unwrap it.