Search code examples
c++boostmetaprogrammingmixinsboost-mpl

MPL factory method to create mixin classes


I have a Visual Studio 2008 C++03 project where a factory method is used to create mixin classes based on a set of bit flags using a large switch/case statement.

For example:

inline boost::shared_ptr< MyInterface > Create( DWORD flags )
{
    int a, b, c;
    /* ... */

    /*
        0x000000 - MixinBase
        0x000001 - AddOnA
        0x001000 - AddOnB
        0x002000 - AddOnC
        0x400000 - AddOnD
        ... several more
    */

    switch( flags )
    {
    case 0x000001:
        return boost::make_shared< AddOnA< MixinBase > >( a, b, c );
    case 0x001001:
        return boost::make_shared< AddOnB< AddOnA< MixinBase > > >( a, b, c );
    case 0x003001:
        return boost::make_shared< AddOnC< AddOnB< MixinBase > > >( a, b, c );    
    case 0x003001:
        return boost::make_shared< AddOnC< AddOnB< AddOnA< MixinBase > > > >( a, b, c );    
    case 0x402001:
        return boost::make_shared< AddOnD< AddOnC< AddOnA< MixinBase > > > >( a, b, c );    
    default:
        return boost::make_shared< MixinBase >( a, b, c );
    }
}

Unfortunately, this switch/case statement quickly grows enormous with just a few flags. Is there a better way to do this? Possibly using template meta-programming?

Thanks


Solution

  • This is definitely possible, even though not straightforward: since flags is only know at runtime, you'll need to intertwine compile-time and runtime computation.

    Here is a generic solution that could be used with any base interface, base class and mixins:

    // recursive case
    template < typename Interface, typename BaseMixin, 
               typename It, typename End, typename WrappersToApply >
    struct MixinCreatorIteration
    {
      static boost::shared_ptr< Interface > apply( int flags )
      {
        typedef typename mpl::deref< It >::type               flag_to_wrapper;
        typedef typename mpl::first< flag_to_wrapper >::type  flag;
        typedef typename mpl::second< flag_to_wrapper >::type wrapper;
    
        if ( flags & flag::value )  // add current wrapper
        {
          return MixinCreatorIteration<
            Interface, 
            BaseMixin, typename
            mpl::next< It >::type,
            End, typename
            mpl::push_back<
              WrappersToApply,
              wrapper
            >::type
          >::apply( flags );
        }
        else                        // don't add current wrapper
        {
          return MixinCreatorIteration<
            Interface,
            BaseMixin, typename
            mpl::next< It >::type,
            End,
            WrappersToApply
          >::apply( flags );
        }
      }
    };
    
    
    //base case through partial template specialization
    template < typename Interface, typename BaseMixin, 
               typename End, typename WrappersToApply >
    struct MixinCreatorIteration< Interface, BaseMixin, 
                                  End, End, WrappersToApply >
    {
      static boost::shared_ptr< Interface > apply( int flags )
      {
        using mpl::placeholders::_1;
        using mpl::placeholders::_2;
    
        typedef typename
          mpl::fold<
            WrappersToApply,
            BaseMixin,
            mpl::apply1< _2, _1 >
          >::type mixin;
    
        return boost::make_shared< mixin >();
      }
    };
    
    
    template < typename Interface, typename BaseMixin, typename WrapperMap >
    struct MixinCreator
    {
      static boost::shared_ptr< Interface > apply( int flags )
      {
        return MixinCreatorIteration<
          Interface, 
          BaseMixin, typename
          mpl::begin< WrapperMap >::type, typename
          mpl::end< WrapperMap >::type,
          mpl::vector< >
        >::apply( flags );        
      }
    };
    

    And here is a sample use, akin to your example:

    boost::shared_ptr< MyInterface > create( int flags )
    {
      using namespace mpl::placeholders;
    
      typedef mpl::map<
        mpl::pair< mpl::int_< 0x01 >, AddOnA<_> >,
        mpl::pair< mpl::int_< 0x02 >, AddOnB<_> >,
        mpl::pair< mpl::int_< 0x04 >, AddOnC<_> >
      > flag_to_wrapper;
    
      return MixinCreator< MyInterface, MixinBase, flag_to_wrapper >::apply( flags );
    }
    
    int main()
    {
        create( 0x01 ); // creates AddOnA< MixinBase >
        create( 0x02 ); // creates AddOnB< MixinBase >
        create( 0x07 ); // creates AddOnC< AddOnB< AddOnA< MixinBase > > >
        create( 0x08 ); // creates MixinBase
    }
    

    Basically, the idea is to store the relation between flags and wrappers into a compile-time data structure (here, an mpl::map) and to iterate over this structure, keeping the wrappers to apply along the way. At the end of the iteration, all the wrappers are applied and the instance is created.

    In your example, the construction needs parameters: if you can use C++11, you can easily adapt my solution to use variadic parameters and perfect forwarding; otherwise, you can use the preprocessor to generate various versions of the apply functions (see Boost.Preprocessor for how to do that).