如何使用和访问参数包的模板参数包
How to use and access a template parameter pack of parameter packs
简短介绍
我正在尝试创建一个 AddComponents()
方法来一次创建多个组件并将其添加到一个实体。我已经写了一个一次添加一个组件的工作方法。
它具有以下签名:
template <class TComponent, typename... TArguments>
TComponent & AddComponent(TArguments&&... arguments);
并按以下方式使用
entity.AddComponent<SomeComponent>(data1, data2, data3);
我现在想创建一个将多个组件添加到一个函数中的函数,即采用 TComponents
的参数包。当然,数据也必须传递,这就是事情变得糟糕的地方;我基本上需要一个参数包的参数包。然后该函数应遍历 TComponents
(例如使用 int i < sizeof...(Types)
)并为每个 TComponent
调用 AddComponent<T>(data...)
。我已经使用了很多模板,但我很难理解这个问题。
我想要的
理想情况下,我希望它像这样使用:
entity.AddComponents<PositionComponent, SpriteComponent, TComponent>(
{ position },
{ texture, colour },
{ data1, data2 });
在内部我需要一种方法来做类似
的事情
for each TComponent : TComponents
{
this->AddComponent<TComponent>(forward...(data))
}
理论上,如果没有这个我也许可以逃脱,但这似乎是一个有趣的问题。
添加组件代码
为了防止人们质疑函数的作用,这里是代码。
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;
}
我会使用类似于 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)
};
这里的第一个参数告诉你想要分段构造,所以每个参数都将作为一个 "pack" 扩展到对的成员中。在这种情况下,该对的元素构造如下:
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
,您在包 Components
和 Tuples
中都有对应的 Tuple
。然后,每个元组被发送到一个函数,该函数使用元组 Tuple
.
构造类型为 Component
的单个组件
此示例使用 C++17 功能,例如 折叠表达式 和 std::apply
,但它可以在 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"}
);
当然,这需要将组件从参数移动到实体中,这可能不是完全免费的,具体取决于具体情况。
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)>
{
使用头=T0;
使用尾巴=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... );
未能传递参数导致未添加组件。
我能想象的最好的是递归 addComponents()
,"consume" 第一个未推导的模板类型与第一个推导的参数元组列表,并用剩余的参数调用自身。
在 C++17 中你也可以使用 std::apply()
.
首先,你需要一个addComponents()
的ground case版本(中断递归)
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 风格:使用模板折叠并避免递归。很不错。
简短介绍
我正在尝试创建一个 AddComponents()
方法来一次创建多个组件并将其添加到一个实体。我已经写了一个一次添加一个组件的工作方法。
它具有以下签名:
template <class TComponent, typename... TArguments>
TComponent & AddComponent(TArguments&&... arguments);
并按以下方式使用
entity.AddComponent<SomeComponent>(data1, data2, data3);
我现在想创建一个将多个组件添加到一个函数中的函数,即采用 TComponents
的参数包。当然,数据也必须传递,这就是事情变得糟糕的地方;我基本上需要一个参数包的参数包。然后该函数应遍历 TComponents
(例如使用 int i < sizeof...(Types)
)并为每个 TComponent
调用 AddComponent<T>(data...)
。我已经使用了很多模板,但我很难理解这个问题。
我想要的
理想情况下,我希望它像这样使用:
entity.AddComponents<PositionComponent, SpriteComponent, TComponent>(
{ position },
{ texture, colour },
{ data1, data2 });
在内部我需要一种方法来做类似
的事情for each TComponent : TComponents
{
this->AddComponent<TComponent>(forward...(data))
}
理论上,如果没有这个我也许可以逃脱,但这似乎是一个有趣的问题。
添加组件代码
为了防止人们质疑函数的作用,这里是代码。
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;
}
我会使用类似于 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)
};
这里的第一个参数告诉你想要分段构造,所以每个参数都将作为一个 "pack" 扩展到对的成员中。在这种情况下,该对的元素构造如下:
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
,您在包 Components
和 Tuples
中都有对应的 Tuple
。然后,每个元组被发送到一个函数,该函数使用元组 Tuple
.
Component
的单个组件
此示例使用 C++17 功能,例如 折叠表达式 和 std::apply
,但它可以在 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"}
);
当然,这需要将组件从参数移动到实体中,这可能不是完全免费的,具体取决于具体情况。
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)>
{ 使用头=T0; 使用尾巴=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... );
未能传递参数导致未添加组件。
我能想象的最好的是递归 addComponents()
,"consume" 第一个未推导的模板类型与第一个推导的参数元组列表,并用剩余的参数调用自身。
在 C++17 中你也可以使用 std::apply()
.
首先,你需要一个addComponents()
的ground case版本(中断递归)
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 风格:使用模板折叠并避免递归。很不错。