【问题标题】:Constructing a compile-time list of templated types?构建模板类型的编译时列表?
【发布时间】:2016-01-18 22:03:40
【问题描述】:

很抱歉这个问题的措辞含糊。我遇到了一个似乎应该可以解决的问题,但我的调查有点死胡同。简而言之,我想要一个包含从类 A 继承的所有类型的编译时列表。话虽如此,我将直接进入我面临的问题。

假设我有两种类型:实体和组件。

class Entity {
public:
    entity_id id;
    std::vector<component_id> components;
};

template<typename UnderlyingComponentType>
class Component {
public:
     static std::vector<UnderlyingComponentType> components;
     component_id id;
     entity_id containingEntity;
}

简而言之,每个实体都包含多个不同类型的组件(例如 TextComponent、SoundComponent、WeightComponent)。然而,不是直接将这些组件作为字段包含,而是仅将它们的 ID 存储在实体中。这允许对相同类型的组件进行快速迭代,因为它们都在一个连续的向量中(例如,可以一次性遍历所有 DescriptionComponents)。同样,可以为任何给定实体检索给定类型的任何组件:

DescriptionComponent& decription = myEntity.getComponent<DescriptionComponent>()
// Can go to the DescriptionComponent's static vector, and look up the
// appropriate component given our entity's ID (matching it with the component's
// containingEntity ID.)

可以为删除或添加单个组件提供类似的功能。但是,当我想删除给定实体的 所有 组件时,我遇到了问题。因为组件存储在实体之外,所以没有一种简单的方法可以说“循环遍历您包含的组件并将它们全部删除”。相反,我们必须为我们包含的每种组件类型转到底层静态向量,并从那里删除。这是我撞到砖墙的地方。这里似乎有两种潜在的解决方案:

1.) 在每个实体中存储一个类型集合,这些类型映射到我们包含的所有组件类型的组件向量(例如entity.getComponentTypes == X&lt;DescriptionComponent, SoundComponent&gt;; 这里的缺点是每个实体的组件可以不断变化(我可以添加一个WeightComponent 在上述示例中的运行时)。因此,我不相信有办法根据运行时类型信息(如type_indexes 等)链接到编译时类型

2.) 生成实现组件的每种类型的编译时列表,然后在实体要求删除其所有组件时循环遍历它们all。例如,假设我们有一个实体e,它的ID 为123,并包含一个DescriptionComponent 和一个SoundComponent。我们仍然会有一个包含[DescriptionComponent, SoundComponent, WeightComponent] 的类型的编译时列表,并且我们会告诉每个组件的静态向量删除123 引用的任何组件。由于只有DescriptionComponentSoundComponent向量有这些引用,所以只有这两个会删除组件,e的所有组件都会被删除。

上面的选项 2 似乎是更可行的选项:创建一个编译时类型列表。手动编写代码很容易:std::tuple&lt;DescriptionComponent, SoundComponent, WeightComponent&gt; 可以轻松存储并允许我根据需要遍历这些类型。但是,我最理想的是以编程方式生成这个列表的方法,这样如果我以后再添加一个NameComponent,我就不必更新任何其他代码:它当我从我的模板化 Component 类继承时应该自动管理。例如,

class NameComponent : public Component<NameComponent> {
    //implementation details
}

//Now, my all-types list should contain [DescriptionComponent, SoundComponent,
// WeightComponent, NameComponent], and I didn't have to update any config.

我完全不确定上述是否可以在本地实现,但任何帮助或资源建议都是可行的。我在this StackOverflow question 看到了一个类似的问题,但它依赖于宏(据我了解,不要以相同的方式与模板交互)。同样,如果这看起来像是一个 A/B 问题,而像 type_index 地图这样的简单解决方案实际上起作用,那么这种类型的批评也会受到欢迎!我确信我忽略了一些细节或措辞不佳,所以如果/何时需要更多信息,请随时告诉我。

【问题讨论】:

  • 我相信boost::hana 可能有你需要的东西。虽然在 msvc 下不起作用,而且它是一个额外的依赖项 - 但您要解决的任务并不简单(直到将来我们有编译时反射)
  • 感谢资源,我去看看,我听说过 boost::hana 的好消息。很高兴知道问题本身有一些复杂性:编译时反射将是天赐之物!

标签: c++ templates c++11 metaprogramming c++14


【解决方案1】:

您可以使用类型擦除并存储知道要处理哪种组件类型的元素,而不是将component_id 存储在实体中的向量中。

#include <memory>

struct component_id{};

struct component_type_ref
{
  template <typename Comp>
  component_type_ref()
      : _id(Comp::id), _impl(std::make_shared<_impl_t<Comp>>())
  {
  }

  component_id id() const
  {
    return _id;
  }

  void clear()
  {
    _impl->clear();
  }

private:
  struct _impl_base
  {
    virtual void clear();
  };

  template <typename Comp>
  struct _impl_t : public _impl_base
  {
    void clear()
    {
      Comp::components.clear();
    }
  };

  component_id _id;
  std::shared_ptr<_impl_base> _impl;
};

另见https://channel9.msdn.com/Events/GoingNative/2013/Inheritance-Is-The-Base-Class-of-Evil

例如,通过这种方式,您可以在实体的向量中查找组件 id,然后对其调用 clear。如果有一个已知的要在组件类型上运行的操作列表(并且这些操作不需要是模板),那么这可能是一个解决方案

如果不是这样,那么恐怕您将需要使用类型向量。这里不用tuple,简单的

template<typename... T> struct my_type_vector{};

可能就足够了。但正如你自己写的,这在运行时不太可行。

【讨论】:

  • 这太好了,谢谢!利用虚拟调度的想法是我什至没有考虑过的,但似乎非常合适。似乎甚至可以通过将 std:: 函数对象传递给基类构造函数来完全优化虚拟调用。非常感谢您的反馈,非常感谢!
  • 很高兴你喜欢它。不过,我不确定我是否理解您对 std::function 的评论。你只需要 clear() 吗?或者你会存储半打 std::function 对象吗?只是好奇...
  • 是的,这就是我的建议:只要只需要一个函数,还不如直接传入而不是依赖虚拟调度。如果需要不止一个,那么虚拟功能的开销几乎肯定会更少。话虽如此,我根本没有描述过这个概念:即使在简单的情况下,虚拟函数也可能更便宜!不过,无论如何,您的解决方案干净优雅,再次感谢。
【解决方案2】:

您可以使用以下代码制作类似于 std::type_index 的内容:

template<typename T>
void type_id() {}

using type_id_t = void(*)();

现在在你的地图中,使用这个:

std::map<type_id_t, /* whatever */> myMap;

然后像这样检索:

auto myElement = myMap[type_id</* somthing else */>];

这将在没有 RTTI 的情况下完成这项工作

【讨论】:

  • 感谢您的建议!但是,在这个模型中,我看不出如何从 type_id 推断出适当的类型。例如,如果我映射type_id&lt;A&gt; =&gt; 123type_id&lt;B&gt; =&gt; 456,我仍然不知道我怎么知道调用A::components.remove(123)(因为我在执行转换时去掉了模板类型信息)。我希望是错的,因为这会很棒,但我不知道它现在如何工作。
  • 一旦类型信息被条带化(通过将类型转换为值),就无法取回该类型。但是,使用std::map::find,您可以删除123,因为您可以按值类型进行搜索。
  • Hey Guillame:这是一种将类型映射到值的非常有用的方法,但我不确定它是否适用于这个特定问题。我面临的主要困境是如何将这些remove 调用定向到适当的拥有向量。因此,通过type_idcomponent_id 的映射,我仍然需要一种方法来告诉程序应该从哪里删除带有给定component_ids 的组件(应该来自A::components?还是来自@ 987654335@? map 方法在这里似乎没有提供解决方案,因为这些分量向量甚至不共享一个公共类型)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-31
  • 1970-01-01
  • 2014-12-09
  • 1970-01-01
相关资源
最近更新 更多