Я пытаюсь работать с C ++ Mixins, однако, мои создаются фабриками, которые следуют той же иерархии, что и создаваемые элементы.
И у меня проблема с расширением как функции создания фабрики, так и конструкторов в правильном порядке.
Например:
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;
}
Это работает для одного миксина, но, к сожалению, не масштабируется.
Например, если у нас есть завод
typedef FactoryMixInB<FactoryMixInA<BaseFactory> > FinalFactory
typedef MixInB<MixInA<Base> > FinalItem
затем FinalFactory::GetNewItem(Args&&... args)
расширяется до GetNewItem(m_a, m_b, ...)
но конструктор расширяется до FinalItem::FinalItem(m_b, m_a, ...)
Проблема в том, что конструктор построен Base
-> MixInA
-> MixInB
И завод идет FactoryMixInB
-> FactoryMixInA
-> BaseFactory
Каково решение этой проблемы?
Я думал об изменении направления на заводе, написав
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;
}
Но это не работает, так как требует возврата пакета параметров, что я не считаю возможным.
Есть другой ответ? Можно ли обернуть упаковку, например, в кортеж? Есть ли шаблон дизайна для этого?
Чтобы вернуть / сохранить содержимое пакета variadic, самый простой способ — использовать кортежи:
template<typename... ARGS>
std::tuple<ARGS...> GetAdditionalConstructorArgs() { return std::mke_tuple( pack... ); }
...
auto new_args = GetAdditionalConstructorArgs();
Обратите внимание, что я удалил rvalue-ссылку из возврата. НИКОГДА не возвращать как rvalue ссылки, просто вернуть по значению.
Позже, если вам нужно снова передать этот пакет, хранящийся в виде кортежа, как пакет с переменным числом, вы должны рекурсивно извлечь содержимое этого кортежа. Существует идиома, которая называется Трюк с индексами который извлекает элементы кортежа, рекурсивно расширяя их как пакет с переменными параметрами:
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)>{} );
}
Наконец, generate_indices
тип является метафункцией, которая генерирует indices
экземпляр шаблона с номерами из 0
в N-1
, где N
это размер (длина) кортежа.
Вот пример использования tuple_call
:
tuple_call( [&]( int a , bool b , char c ) { std::cout << a; } ,
std::make_tuple( 1 , true , 'a' )
);
Для получения дополнительной информации об уловке индексов, я рекомендую это: http://loungecpp.wikidot.com/tips-and-tricks%3aindices