如何使用和访问参数包的模板参数包

时间:2019-04-02 20:22:42

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

简短介绍

我正在尝试创建一个AddComponents()方法,该方法可以一次创建多个组件并将其添加到实体。我已经写了一种可以一次添加一个组件的工作方法。

它具有以下签名:

template <class TComponent, typename... TArguments>
TComponent & AddComponent(TArguments&&... arguments);

,并按以下方式使用

entity.AddComponent<SomeComponent>(data1, data2, data3);

我现在想创建一个函数,一次添加多个组件,即,使用参数包TComponents。当然,数据也必须传递,这就是丑陋的地方。我基本上需要一个参数包的参数包。然后,该函数应在TComponents上进行迭代(例如,使用int i < sizeof...(Types)),并为每个AddComponent<T>(data...)调用TComponent。我已经使用了很多模板,但是我一直努力解决这个问题。

我想要的

理想情况下,我希望将其用于如下用途:

entity.AddComponents<PositionComponent, SpriteComponent, TComponent>(
{ position },
{ texture, colour },
{ data1, data2 });

内部需要一种类似的方法

for each TComponent : TComponents
{
    this->AddComponent<TComponent>(forward...(data))
}

从理论上讲,没有这个我也许可以逃脱,但是这似乎还是一个有趣的问题。

AddComponent代码

以防人们可能质疑函数的功能,这就是代码。

template <class TComponent, typename... TArguments>
inline TComponent & Entity::AddComponent(TArguments&&... arguments)
{
    auto typeId = detail::GetComponentTypeID<TComponent>();

    auto component = std::make_shared<TComponent>(eventManager, *this, std::forward<TArguments>(arguments)...);

    componentList.push_back(component);
    componentDictionary.insert(std::make_pair(typeId, component));

    entityManager.AddEntityToGroup(*this, typeId);

    this->AddPolymorphism(typeId, component);
    eventManager.RaiseEvent(mbe::event::ComponentsChangedEvent(*this));

    return *component;
}

3 个答案:

答案 0 :(得分:3)

我将使用类似于STL最终所做的事情:将成组的包作为元组传递。

例如一对emplace_back:

stuct Widget {
    int a;
    std::string name = {};
};

std::pair<Widget, Widget>{
    std::piecewise_construct,
    std::forward_as_tuple(12, "values"),
    std::forward_as_tuple(31)
};

在这里,第一个参数告诉您需要分段构造,因此每个参数将充当一个“包装”,将被扩展为该对的成员。在这种情况下,该对中的元素被构造为:

Widget first{12, "values"};
Widget second{31};

对于您来说,似乎总是想构造N组件。因此解决方案是:

template<typename Component, typename Tuple>
void addComponentUnpack(Tuple&& tuple) {
    std::apply([](auto&&... args) {
        addComponent<Component>(std::forward<decltype(args)>(args)...);
    }, std::forward<Tuple>(tuple));
}

template<typename... Components, typename... Tuples>
void addComponents(Tuples&&... tuples) {
    (addComponentUnpack<Components, Tuples>(std::forward<Tuples>(tuples)), ...);
}

因此,对于每个Component,在TupleComponents包中都有一个对应的Tuples。然后,将每个元组发送到使用元组Component构建一个类型为Tuple的单个组件的函数。

此示例使用了诸如 fold表达式std::apply之类的C ++ 17功能,但是可以在C ++ 14中使用更多代码来实现。


但是,如果您不希望使用元组作为参数,则可以始终直接接收类型的实例:

template<typename... Components>
void addComponents(Components&&... components) {
    // ...
}

用法如下:

addComponents(
    PositionComponent{12, 43},
    CollisionComponent{},
    SpriteComponent{"texture name"}
);

// or

addComponents<PositionComponent, CollisionComponent, SpriteComponent>(
    {12, 42},
    {},
    {"texture name"}
);

当然,这需要将组件从参数移至实体,根据情况的不同,实体可能并非完全免费。

答案 1 :(得分:2)

template<class...Ts>
struct types_t:
  std::integral_constant<std::size_t, sizeof...(Ts)>
{};
template<class T0, class...Ts>
struct types_t<T0, Ts...>:
  std::integral_constant<std::size_t, 1+sizeof...(Ts)>

{       使用head = T0;       使用tail = types_t;    };

template<class Componets>
auto ComponentWorker( Components ) {
  return [this](auto components, auto&&...args) {
    using TComp0 = typename decltype(components)::head;
    // add TComp0 using all of args...
    using TCompTail = typename decltype(components)::tail;
    if constexpr( TCompTail::value != 0 )
      return ComponentWorker( TCompTail{} );
    else
      return; // void
  }
}
template <class TC0, class... TCs, class... TArguments>
auto AddComponent(TArguments&&... arguments) {
  using components = types_t<TC0, TCs...>;
  return ComponentWorker( components )( std::forward<TArguments>(arguments)... );
}

这里的语法是:

AddComponent<A,B,C>( A_args... )( B_args... )( C_args... );

不传递参数导致未添加组件。

答案 2 :(得分:2)

我能想象的最好的是递归addComponents(),它使用第一个推论的元组列表“消费”第一个未推导的模板类型,并用其余的调用自身。

在C ++ 17中,您也可以使用std::apply()

首先,您需要addComponents()的基本版本(以中断递归)

   template <int = 0>
   void addComponents ()
    { };

下一个递归版本(编辑:从Guillaume Racicot指出的错误中纠正;谢谢!)

   template <typename T0, typename ... Ts, typename A0, typename ... As>
   void addComponents (A0 && tpl, As && ... as)
    {
      std::apply([](auto && ... args)
         { return addComponent<T0>(std::forward<decltype(args)>(args)...); }, 
         std::forward<A0>(tpl));

      addComponents<Ts...>(std::forward<As>(as)...);
    }

不确定元组和std::apply()完美转发的正确性(抱歉)。

显然,这要求将单个组件的参数包装在元组中。

所以您的通话成为

entity.AddComponents<PositionComponent, SpriteComponent, TComponent>(
   std::make_tuple(position),
   std::make_tuple(texture, colour),
   std::make_tuple(data1, data2));

-编辑--我现在看到Guillaume Racicot基本上编写了相同的解决方案,但使用的是更好的C ++ 17样式:使用模板折叠并避免递归。很好。