如何根据标记的参数包调用一组可变参数基类构造函数?

Cas*_*mor 5 c++ templates variadic-templates c++11

我希望能够做到这一点:

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*)
Run Code Online (Sandbox Code Playgroud)

我如何在这里填写 /* 神秘代码 */ 来做我想做的事,提供一个很好的接口来调用一些 mixin 的构造函数?我有一个解决方案,它通过使所有非空构造实际上采用一个 args 元组,然后重载计算出要调用哪个,但我想通过让他们编写构造函数 A(tuple) 来避免限制 mixin 作者,而不仅仅是 A(int, int)。

谢谢!

Luc*_*ton 4

我想我明白你想要什么。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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这有很多好处:TU构造各发生一次,两者都不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 {}; }
Run Code Online (Sandbox Code Playgroud)

所以现在如果我们有using tuple_type = std::tuple<int, long, double, double>;thenmake_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))...)
    {}
};
Run Code Online (Sandbox Code Playgroud)

让我们尝试将其插入您的 mixin 中:

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))...);
    }
};
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我已经将这些元组的元组和索引的元组提升了一级。原因是我无法表达和匹配类型,例如std::tuple<indices<Indices...>...>(声明的相关包是什么??int...... Indices),即使我这样做了,包扩展也不是为处理过多的多级包扩展而设计的。您现在可能已经猜到了,但是将其全部打包在与其索引捆绑在一起的元组中是我在解决此类问题时的做法...这确实缺点,但是构造不再到位,Mixins<...>而且现在需要可移动构造。

我建议也添加一个默认构造函数(即Mix() = default;),因为 usingMix<A, B> m(std::piecewise_construct, std::forward_as_tuple(), std::forward_as_tuple());看起来很愚蠢。请注意,如果任何一个Mixin<...>不是默认构造函数,则这样的默认声明将不会产生默认构造函数。

该代码已经使用 GCC 4.7 的快照进行了测试,除了那个sizeof...(Mixins)意外之外,它可以逐字工作。