Как я могу вызвать набор конструкторов базового класса с переменным числом аргументов, основанных на пакетах с тегами? - PullRequest
4 голосов
/ 16 марта 2012

Я хотел бы иметь возможность сделать это:

template<typename Mix>
struct A {
  A(int i) { }
};

template<typename Mix>
struct B {
  B() { }
  B(const char*) { }
};

template<template<typename> class... Mixins>
struct Mix : Mixins<Mix<Mixins...>>... {
   // This works, but forces constructors to take tuples
   template<typename... Packs>
   Mix(Packs... packs) : Packs::Type(packs.constructorArgs)... { }
};

template<template<typename> class MixinType, typename... Args>
struct ArgPack {
  typedef MixinType Type; // pretend this is actually a template alias
  tuple<Args...> constructorArgs;
  ArgPack(Args... args) : constructorArgs(args...) { }
}

template<typename... Args>
ArgPack<A, Args...> A_(Args... args) {
  return ArgPack<A, Args...>(args...);
}

template<typename... Args>
ArgPack<B, Args...> B_(Args... args) {
  return ArgPack<B, Args...>(args...);
}

Mix<A, B> m(); // error, A has no default constructor

Mix<A, B> n(A_(1)); // A(int), B()
Mix<A, B> n(A_(1), B_("hello"); // A(int), B(const char*)

Как мне заполнить / * загадочный код здесь * /, чтобы сделать то, что я хочу, чтобы обеспечить хороший интерфейс для вызова некоторого набораконструкторов миксинов?У меня есть решение, которое работает, заставляя все ненулевые конструкции фактически принимать кортеж с аргументами, а затем перегружать данные, какие из них вызывать, но я бы хотел избежать ограничения авторов миксинов, заставляя их писать конструктор A (кортеж),вместо просто A (int, int).

Спасибо!

Ответы [ 2 ]

3 голосов
/ 16 марта 2012

Я думаю, я понимаю, что вы хотите. std::pair имеет похожую функцию:

std::pair<T, U> p(std::piecewise_construct
                      , std::forward_as_tuple(foo, bar)
                      , std::forward_as_tuple(qux) );
// p.first constructed in-place as if first(foo, bar) were used
// p.second constructed in place as if second(qux) were used

Как вы можете видеть, у этого есть много преимуществ: имеет место ровно одна конструкция T и U, и не требуется, например, T и U. MoveConstructible, и это стоит только конструкции из двух мелких кортежей. Это также делает идеальную пересылку. В качестве предупреждения, однако, это значительно сложнее реализовать без наследования конструкторов, и я буду использовать эту функцию, чтобы продемонстрировать возможную реализацию кусочно-конструктора, а затем попытаюсь сделать его вариационную версию.

Но, во-первых, полезная утилита, которая всегда пригодится, когда задействованы различные пакеты и кортежи:

template<int... Indices>
struct indices {
    using next = indices<Indices..., sizeof...(Indices)>;
};

template<int Size>
struct build_indices {
    using type = typename build_indices<Size - 1>::type::next;
};
template<>
struct build_indices<0> {
    using type = indices<>;
}

template<typename Tuple>
constexpr
typename build_indices<
    // Normally I'd use RemoveReference+RemoveCv, not Decay
    std::tuple_size<typename std::decay<Tuple>::type>::value
>::type
make_indices()
{ return {}; }

Так что теперь, если у нас есть using tuple_type = std::tuple<int, long, double, double>;, тогда make_indices<tuple_type>() возвращает значение типа indices<0, 1, 2, 3>.

Во-первых, не вариадный случай кусочной конструкции:

template<typename T, typename U>
class pair {
public:
    // Front-end
    template<typename Ttuple, typename Utuple>
    pair(std::piecewise_construct_t, Ttuple&& ttuple, Utuple&& utuple)
        // Doesn't do any real work, but prepares the necessary information
        : pair(std::piecewise_construct
                   , std::forward<Ttuple>(ttuple), std::forward<Utuple>(utuple)
                   , make_indices<Ttuple>(), make_indices<Utuple>() )
     {}

private:
    T first;
    U second;

    // Back-end
    template<typename Ttuple, typename Utuple, int... Tindices, int... Uindices>
    pair(std::piecewise_construct_t
             , Ttuple&& ttuple, Utuple&& utuple
             , indices<Tindices...>, indices<Uindices...>)
        : first(std::get<Tindices>(std::forward<Ttuple>(ttuple))...)
        , second(std::get<Uindices>(std::forward<Utuple>(utuple))...)
    {}
};

Давайте попробуем добавить это в свой миксин:

template<template<typename> class... Mixins>
struct Mix: Mixins<Mix<Mixins...>>... {
public:
    // Front-end
    template<typename... Tuples>
    Mix(std::piecewise_construct_t, Tuples&&... tuples)
        : Mix(typename build_indices<sizeof...(Tuples)>::type {}
                  , std::piecewise_construct
                  , std::forward_as_tuple(std::forward<Tuples>(tuples)...)
                  , std::make_tuple(make_indices<Tuples>()...) )
    {
        // Note: GCC rejects sizeof...(Mixins) but that can be 'fixed'
        // into e.g. sizeof...(Mixins<int>) even though I have a feeling
        // GCC is wrong here
        static_assert( sizeof...(Tuples) == sizeof...(Mixins)
                       , "Put helpful diagnostic here" );
    }

private:
    // Back-end
    template<
        typename TupleOfTuples
        , typename TupleOfIndices
        // Indices for the tuples and their respective indices
        , int... Indices
    >
    Mix(indices<Indices...>, std::piecewise_construct_t
            , TupleOfTuples&& tuple, TupleOfIndices const& indices)
        : Mixins<Mix<Mixins...>>(construct<Mixins<Mix<Mixins...>>>(
            std::get<Indices>(std::forward<TupleOfTuples>(tuple))
            , std::get<Indices>(indices) ))...
    {}

    template<typename T, typename Tuple, int... Indices>
    static
    T
    construct(Tuple&& tuple, indices<Indices...>)
    {
        using std::get;
        return T(get<Indices>(std::forward<Tuple>(tuple))...);
    }
};

Как вы можете видеть, я поднялся на один уровень выше с этими кортежами кортежей и кортежей индексов. Причина в том, что я не могу выразить и сопоставить тип, такой как std::tuple<indices<Indices...>...> (что означает соответствующий пакет, объявленный как? int...... Indices?), И даже если я сделал расширение пакета, не предназначено для работы с многоуровневым слишком много расширения пакета Возможно, вы уже догадались, но упаковка всего этого в кортеж, связанный с его индексами, - это мой образ действий, когда дело доходит до решения такого рода задач ... У этого есть недостаток, однако это не конструкция на месте, и теперь Mixins<...> должны быть MoveConstructible.

Я бы тоже рекомендовал добавить конструктор по умолчанию (т.е. Mix() = default;), потому что использование Mix<A, B> m(std::piecewise_construct, std::forward_as_tuple(), std::forward_as_tuple()); выглядит глупо. Обратите внимание, что такое объявление по умолчанию не даст конструктора по умолчанию, если любой из Mixin<...> не является DefaultConstructible.

Код был протестирован со снимком GCC 4.7 и работает дословно, за исключением этого sizeof...(Mixins) неудача.

0 голосов
/ 05 ноября 2012

Я столкнулся с очень похожей проблемой в контексте разработки на основе политик. По сути, мой класс наследует поведение от набора политик, и некоторые из них с состоянием и требуют инициализации конструктора.

Решение, которое я нашел, состоит в том, чтобы классы политики были организованы в глубокой иерархии, а не в широкой. Это позволяет писать конструкторы, которые берут только нужные им элементы из пакета аргументов и передают оставшийся пакет для инициализации «верхней» части иерархии.

Вот мой код:

/////////////////////////////////////////////
//               Generic part              //
/////////////////////////////////////////////
template<class Concrete,class Mother,class Policy>
struct PolicyHolder{};

struct DeepHierarchyFinal{};

template<class Concrete,class P,typename... Args>
struct DeepHierarchy:   public PolicyHolder<Concrete,DeepHierarchy<Concrete,Args...>,P>,
                        public P
{
    template<typename... ConstructorArgs>
    DeepHierarchy(ConstructorArgs... cargs):
    PolicyHolder<Concrete,DeepHierarchy<Concrete,Args...>,P>(cargs...){};
};

template<class Concrete,class P>
struct DeepHierarchy<Concrete,P>:   public PolicyHolder<Concrete,DeepHierarchyFinal,P>,
                                    public P
{
    template<typename... ConstructorArgs>
    DeepHierarchy(ConstructorArgs... cargs):
    PolicyHolder<Concrete,DeepHierarchyFinal,P>(cargs...){};
};
///////////////////////////////////////////
//                Test case              //
///////////////////////////////////////////

///////////////////////////////////////////
//                Policies               //
///////////////////////////////////////////
struct Policy1{};
struct Policy2{};
struct Policy3{};
struct Policy4{};

template<class Concrete,class Mother>
struct PolicyHolder<Concrete,Mother,Policy1> : public Mother
{
    int x;
    template<typename... Args>
    PolicyHolder(int _x,Args... args):Mother(args...),x(_x) {};
};

template<class Concrete,class Mother>
struct PolicyHolder<Concrete,Mother,Policy2> : public Mother
{
    template<typename... Args>
    PolicyHolder(Args... args):Mother(args...){
        cout<<"Policy2 initialized";
        // Here is a way to know (at runtime) if a particular
        // policy has been selected in the concrete class
        if (boost::is_convertible<Concrete,Policy3>::value)
            cout<<" together with Policy3\n";
        else
            cout<<" without Policy3\n";
    };
};

template<class Concrete,class Mother>
struct PolicyHolder<Concrete,Mother,Policy3> : public Mother
{
    string s;
    char c;
    template<typename... Args>
    PolicyHolder(string _s,char _c,Args... args):Mother(args...), s(_s),c(_c) {};
};

template<class Concrete,class Mother>
struct PolicyHolder<Concrete,Mother,Policy4> : public Mother
{
    template<typename... Args>
    PolicyHolder(Args... args):Mother(args...) {
        // Here is a way to check (at compile time) that 2 incompatible policies
        // does not coexist
        BOOST_STATIC_ASSERT(( ! boost::is_convertible<Concrete,Policy1>::value));
    };
};
//////////////////////////////////////////////
//              Concrete class              //
//////////////////////////////////////////////
template<class... PoliciesPack>
struct C: public DeepHierarchy<C<PoliciesPack...>,PoliciesPack...>
{
    using Policies=DeepHierarchy<C<PoliciesPack...>,PoliciesPack...>;
    string s;
    template<typename... Args>
    C(string _s,Args... args):Policies(args...),s(_s){};
};

BOOST_AUTO_TEST_CASE( testDeepHierarchyConstruction )
{
    C<Policy1,Policy2> c0("foo",4);
    BOOST_CHECK_EQUAL(c0.x,4);

    C<Policy1,Policy2,Policy3> c1("bar",3,"foo",'f');
    BOOST_CHECK_EQUAL(c1.c,'f');

    C<Policy3,Policy4> c2("rab","oof",'d');
    BOOST_CHECK_EQUAL(c2.c,'d');
}

Я сделал более подробный анализ этого подхода на этой странице .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...