【问题标题】:How can I call a set of variadic base class constructors based on tagged argument packs?如何基于标记的参数包调用一组可变参数基类构造函数?
【发布时间】:2012-11-05 08:02:10
【问题描述】:

我希望能够做到这一点:

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*)

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

谢谢!

【问题讨论】:

  • 你的例子的问题是你一直在使用单元素元组,所以很难知道你到底在追求什么行为。如果您的目标是将元组扩展为 Base 的单独构造函数参数,我认为这是不可能的。为什么不采用可变参数包而不是元组作为构造函数参数?
  • 呃,是的,你当然是对的。我会考虑一下,并用一个反映我实际意图的例子来修改这个问题。
  • 代码现在更恶心 100% :)
  • struct Mix : Mixins&lt;Mix&lt;Mixins...&gt;&gt;... 这一行似乎暗示着无限的递归。有关使用构造函数委托的高度复杂的 mixin 的示例,请查看我的 C++11 预处理器项目 code.google.com/p/c-plus/source/browse/src — 从 framework.h 开始。如果您可以消除对递归的渴望,转而使用更简单的 CRTP,事情会容易得多。
  • 其实,它工作得很好。我在一个大型项目中使用这种风格的 mixin 已经有一段时间了,它可以编写非常优雅的代码。这是类固醇上的 CRTP :) 唯一的问题是我没有调用 mixin 构造函数的好方法:(

标签: c++ templates c++11 variadic-templates


【解决方案1】:

我想我明白你想要什么。 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

正如您所看到的,这有很多好处:每个 TU 都会发生一个构造,TU 都不需要是例如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&lt;int, long, double, double&gt;;,那么make_indices&lt;tuple_type&gt;() 会产生一个indices&lt;0, 1, 2, 3&gt; 类型的值。

首先,分段构造的非可变情况:

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))...)
    {}
};

让我们尝试用你的 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))...);
    }
};

如您所见,我使用元组元组和索引元组提高了一级。原因是我无法表达和匹配诸如std::tuple&lt;indices&lt;Indices...&gt;...&gt; 之类的类型(相关包声明为什么?int...... Indices?),即使我做了包扩展也不是为处理多级而设计的包扩展太多。您现在可能已经猜到了,但是在解决此类问题时,将它们全部打包在一个与其索引捆绑在一起的元组中是我的工作方式......这确实有缺点,但是构造是不再存在,Mixins&lt;...&gt; 现在需要为 MoveConstructible。

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

代码已经用 GCC 4.7 的快照进行了测试,除了sizeof...(Mixins) 事故之外,它可以逐字运行。

【讨论】:

  • 哇,感谢您提供如此全面的答案!我还在纠结这个。我在工作,所以我真的不能玩它,但有几个问题:像这样构建副本的开销是多少?我的观察是否正确,假设您有 Mix() = default,如果您要指定 any 参数包,则需要指定所有参数包? (所以你不能有一些默认可构造的mixin,而另一些则不能,并且只为不可构造的那些传入包。)
  • 另外,当我有机会时,我会在问题中发布我的工作半解决方案,它有不同的权衡,所以你可以看看和比较。
  • @rodarmor 正确的是,分段构造函数需要传递所有包。 然而这完全独立于默认构造函数(同样,Mixin = default(); 将是正确的,即使某些 mixin 不可默认构造)并且空包会导致默认构造。
  • 我认为这可以通过使用例如struct defaulted_type {} constexpr defaulted; 并确保 make_indices&lt;defaulted_type&gt;() 返回 indices&lt;&gt;。然后通过添加construct 的重载以使用defaulted_type,您可以简化诸如Mix&lt;A, B, C&gt; m(std::piecewise_construct_t, std::forward_as_tuple(foo, bar), std::tuple&lt;&gt; {}, std::forward_as_tuple(qux)); 的构造以使用defaulted 而不是std::tuple&lt;&gt; {]
  • 与 SFINAE 类似,当包数与混入数不匹配时,您可以使用技巧添加多个 defaulted。总而言之,我的回答是为了说明分段构造,而不是对您的所有需求做出明确的决定;)
【解决方案2】:

在基于策略的设计环境中,我遇到了一个非常相似的问题。基本上我的类从一组策略继承行为,其中一些是有状态的,需要构造函数初始化。

我找到的解决方案是将策略类组织在一个较深的层次结构中,而不是一个较宽的层次结构中。这允许编写构造函数,它们只从参数包中获取他们需要的元素并传递剩余的包以初始化层次结构的“顶部”部分。

这是我的代码:

/////////////////////////////////////////////
//               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');
}

我在this page 对这种方法进行了更广泛的分析。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-08
    • 2021-10-11
    • 1970-01-01
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 2019-08-25
    • 1970-01-01
    相关资源
    最近更新 更多