Cookie
Cookie

Reputation: 12692

Adding a factory hierarchy to C++ Mixins

I am trying to work with C++ Mixins, however, mine get created by factories which follow the same hierarchy as to the created items.

And I have a problem expanding both the factory creating function and the constructors in the correct order.

For example:

template<class BaseT>
class MixInA : public BaseT
{
public:
  template <class... Args>
  void MixInA(double a, Args&&... args) { m_a(a), Base(std::forward<Args>(args)...); }
  double m_a;
}

template<class FactoryBaseT>
class FactoryMixInA : public FactoryBaseT
{
public:
  template <class... Args>
  void GetNewItem(Args&&... args) { FactoryBaseT::GetNewItem(m_a, std::forward<Args>(args)...); }
  double m_a;
}

template<class BaseT>
class MixInB : public BaseT
{
public:
  template <class... Args>
  void MixInB(double a, Args&&... args) { m_b(b), Base(std::forward<Args>(args)...); }
  double m_b;
}

template<class FactoryBaseT>
class FactoryMixInB : public FactoryBaseT
{
public:
  template <class... Args>
  void GetNewItem(Args&&... args) { FactoryBaseT::GetNewItem(m_b, std::forward<Args>(args)...); }
  double m_b;
}

This works for a single mixin, but unfortunately doesn't scale.

For example, if we have a factory

typedef FactoryMixInB<FactoryMixInA<BaseFactory> > FinalFactory
typedef MixInB<MixInA<Base> > FinalItem

then FinalFactory::GetNewItem(Args&&... args) expands to GetNewItem(m_a, m_b, ...) but the constructor expands to FinalItem::FinalItem(m_b, m_a, ...)

The problem is that the constructor gets built Base -> MixInA -> MixInB

And the factory goes FactoryMixInB -> FactoryMixInA -> BaseFactory

What is the solution to this problem?

I was thinking of changing the direction in the factory by writing

template<class FactoryBaseT>
class FactoryMixInB : public FactoryBaseT
{
public:
  template <class... Args,class... Args2>
  void GetNewItem(Args&&... args)
  {
    Args2&&... base_args = FactoryBaseT::GetAdditionalConstructorArgs();
    Args&&... new_args = GetAdditionalConstructorArgs();
    GetNewItem(std::forward<Args>(new_args)..., std::forward<Args2>(base_args)...);
  }
  template <class... Args>
  Args&&... GetAdditionalConstructorArgs() { return m_b; }
  double m_b;
}

But this doesn't work, as it requires returning a parameter pack, which I don't believe possible.

Is there another answer? Can the pack be wrapped into e.g. a tuple? Is there a design pattern for this?

Upvotes: 1

Views: 176

Answers (1)

Manu343726
Manu343726

Reputation: 14184

To return/store the contents of a variadic-pack, the most easy way is to use tuples:

template<typename... ARGS>
std::tuple<ARGS...> GetAdditionalConstructorArgs() { return std::mke_tuple( pack... ); }

...

auto new_args = GetAdditionalConstructorArgs();

Note that I have removed the rvalue-reference from the return. NEVER return as rvalue references, just return by value.

Later if you need to pass that pack stored as a tuple again as a variadic pack, you need to exptract the contents of the tuple recursively. There is a idiom called The indices trick which extracts tuple elements recursively expanding them as a variadic pack:

template<std::size_t... INDICES>
struct indices{};

template<typename F , typename... ARGS , std::size_t... INDICES>
void tuple_call( F function , std::tuple<ARGS...>&& tuple , indices<INDICES...> )
{
    function( std::forward<typename std::tuple_element<INDICES, std::tuple<ARGS...>>::type>( std::get<INDICES>( tuple ) )... );
}

template<typename F , typename... ARGS>
void tuple_call( F function , std::tuple<ARGS...>&& tuple )
{
   tuple_call( function , std::forward<std::tuple<ARGS...>>( tuple ) , generate_indices<sizeof...(ARGS)>{} );
}

Finally, the generate_indices type is a metafunction which generates a indices template instance with numbers from 0 to N-1, where N is the size (length) of the tuple.

Here is an example of the usage of tuple_call:

tuple_call( [&]( int a , bool b , char c ) { std::cout << a; } , 
            std::make_tuple( 1 , true , 'a' ) 
          );

For more information about the indices trick, I recommend this: http://loungecpp.wikidot.com/tips-and-tricks%3aindices

Upvotes: 2

Related Questions