【问题标题】:Could someone help me create a variable container using Boost::MPL?有人可以帮我使用 Boost::MPL 创建一个变量容器吗?
【发布时间】:2011-04-26 07:16:25
【问题描述】:

我创建了一个物理系统,可以处理任何碰撞对象到任何碰撞对象,如下所示:

namespace Collision
{
    template <typename T, typename U>
    inline void Check(T& t, U& u)
    {
        if(u.CheckCollision(t.GetCollider()))
        {
            u.HitBy(t);
            t.Hit(u);
        }
    }
}

还有其他几个帮助对象使其易于使用,但要点是需要针对静态对象和其他动态对象测试动态对象,但不需要检查静态对象。

我想要的是这样的:

void func()
{
    PhysicsWorld world;
    shared_ptr<CSphere> ballPhysics(new CSphere(0,0,ballSprite->Width()));
    BallCommand ballBehavior;
    CBounds bounds(0, 0, 640, 480);
    CBox obstacle(200, 150, 10, 10);

    Collision::Collidable<CBounds> boundC(bounds);
    Collision::Collidable<std::shared_ptr<CSphere>, BallCommand&> ballC(ballPhysics, ballBehavior);
    Collision::Collidable<CBox> obstC(obstacle);

    world.addStatic(boundC);
    world.addDynamic(ballC);
    world.addStatic(obstC);
    ...
    ...
    world.Update();
    ...
    ...
}

我很想通过添加函数来推断容器,因此使用系统会自动更新类型列表。我想我知道如何使用模板函数生成类型列表,但不知道如何在我需要的地方获得它,或者在编译的什么时候完成。

如果不是这样,那么某些系统使用两个类型列表,然后在内部编写更新函数以遍历所有列表,将它们相互配对。

我已经阅读了一些关于 boost MPL 的书,并多次阅读了 Andrei 的书。但是,我似乎陷入了它的工作原理,并没有真正将其转化为我如何使用它。我希望他们在 MPL 书中有更多关于现实世界示例的部分。

我已经能够让游戏引擎的所有部分与渲染、物理、碰撞(我将检测与反应分开)、输入、网络、声音等以通用方式进行交互。现在我只需要以一种通用的方式来保存所有的东西。在完成了所有通用工作之后,要求继承只是为了让我可以在容器中保存一些东西是很愚蠢的,而且我不想手动编写每个集合的可能性,因为这是通用编程的一大好处。

我看到 Jalf 曾表示他/她使用 MPL 做了类似的事情,但没有详细说明,我无法弄清楚。如果有人知道实际使用示例或者我可以在哪里获得有关使用 MPL 的更多信息,我将不胜感激。

再次感谢!

更新

boost MPL 和 boost Fusion 似乎都可以满足我的要求,但是这两个库在现实生活中的好例子似乎都很少。 MPL 的文档只不过是这个模板所做的,祝你好运理解它的含义。 “这是一个例子,但这只是冰山一角!”

一个典型的 boost MPL 示例是 has_xxx。他们在示例中使用了 XXX 和 xxx,因此很难看出可以使用 XXX(所需文本)和 Test 或 CheckType 或任何更可区分的用户类型来代替 xxx 的区别。另外,没有提到这些都不在命名空间中。现在我知道为什么 Scott meyers 将其与 Psycho 中的淋浴场景进行比较了。

真的很遗憾,因为我编译和理解的很少内容确实有用,但很难弄清楚如果我是在出货产品上,我永远不会花这么多精力。

如果有人知道真实世界的示例或更好的参考资料、解释或教程,我将不胜感激。

更新

这里有更多代码:

template <typename T, typename V = VictimEffect, typename M = MenaceEffect>
class Collidable
{
    T m_Collider;
    V m_HitBy;
    M m_Hit;

public:
    Collidable(T collide, V victim, M menace) : m_Collider(collide), m_HitBy(victim),         m_Hit(menace) {;}
    Collidable(T collide) : m_Collider(collide) {;}
    Collidable(T collide, V victim) : m_Collider(collide), m_HitBy(victim) {;}

    T& GetCollider()
    {
        return m_Collider;
    }

    template <typename V>
    void HitBy(V& menace)
    {
        m_HitBy.HitBy(menace.GetCollider());
    }

    template <typename V>
    void Hit(V& victim)
    {
        m_Hit.Hit(victim.GetCollider());
    }

    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

然后使用它我这样做

    Collidable<Boundary, BallCommand> boundC(boundary, ballBehavior);
    Collidable<CollisionBox> ballC(circle);

那么我所需要的就是调用 collide with all my active collidable objects 来对抗我所有的主动和被动对象。

我没有使用 std::function ,因为添加了函数名使代码对我来说更清晰。但也许这只是传统思维。

【问题讨论】:

  • mpl 容器没有运行时数据,请改用融合容器
  • 谢谢,我正在调查他们。
  • 另外,我认为您所追求的模式是multiple dispatch。所以你可以重新阅读安德烈书中的related chapter ...干杯
  • @sehe 我会再看一遍那一章。我有一段时间没读过了。我已经完成了双重调度。我没有的是一个自动的碰撞世界容器,它知道里面有哪些序列,哪些是动态的,哪些是静态的。
  • @Tavison:也许我只是不完全理解 :) 我已经 +1 这个问题了

标签: c++ templates metaprogramming boost-mpl


【解决方案1】:

如果我理解正确,您的问题是:

class manager {
public:
    template<typename T>
    void add(T t);

private:
    /* ??? */ data;
    /* other members? */
};

manager m;
some_type1 s1;
some_type2 s2;
m.add(s1);
m.add(s2);
/* m should hold its copies of s1 and s2 */

其中 some_type1 和 some_type2 不相关,您不愿意重新设计它们以使用动态多态性。

我认为 MPL 或 Fusion 都不会用这种形式做你想做的事。如果您的问题是使用哪个容器作为PhysicsWorld 的成员,那么编译时计算的数量将无济于事:成员类型是在实例化时确定的,即manager m; 行。

可以以某种元编程方式重写管理器,以便以这种方式使用它:

typedef manager<> m0_type;
typedef typename result_of::add<m0_type, some_type1>::type m1_type;
typedef typename result_of::add<m1_type, some_type2>::type final_type;
/* compile-time computations are over: time to instantiate */
final_type m;
/* final_type::data could be a tuple<some_type1, some_type2> for instance */
m.add(s1); m.add(s2);

这确实是 MPL+Fusion 可以帮助的事情。然而,这仍然在编译时世界中非常固定:你能想象编写一个template&lt;typename Iter&gt; void insert(Iter first, Iter last) 只是为了将容器的内容复制到管理器中吗?

请允许我假设您的要求是这样的,实际上manager 必须以更实时的方式使用,就像我最初对您问题的表述一样。 (我认为PhysicsWorld 的想象力并不丰富)。有一个替代方案,我认为它更合适,更简洁且更易于维护:类型擦除。 (该技术的名称可能有点不幸,第一次可能会产生误导。)

类型擦除的一个很好的例子是 std::function:

std::function<void()> func;
func = &some_func; /* this just looks like that internally std::function stores a void(*)() */
func = some_type(); /* but here we're storing a some_type! */

类型擦除是一种将编译时与运行时联系起来的技术:在上面的两个赋值中,参数都是不相关的类型(其中一个是非类的,因此甚至不是远程运行时多态的),但 std::function 处理两者,如果他们履行合同,他们可以用作f()(其中 f 是相应类型的实例)并且表达式具有类型(可转换为)void。这里的约定是类型擦除的编译时方面。

我不打算演示如何实现类型擦除,因为主题上有a great Boostcon 2010 presentation。 (您可以通过链接观看演示和/或获取幻灯片)。或者我(或其他人)可以在 cmets 中做到这一点。

最后一点,类型擦除的实现(通常)使用动态多态性。我提到这一点是因为我注意到您考虑将类型列表用作存储为manager 成员的运行时对象。这闻起来像是穷人的反射,实际上,是穷人的动态多态性。所以请不要那样做。如果您的意思是 MPL 计算结果中的类型列表,则忽略该节点。

【讨论】:

  • 最后一点,是的,我正在寻找类型列表的编译时计算。而且我正在广泛使用函数,但是即使您可以通过这种方式将不相关的类型结合在一起,您也无法将它们存储在一起。如果我们想象一个完全通用的世界,其中通用编程而不是 OOP,那么容器的想法会包含同意合同但不相关的对象,这似乎并不奇怪。包含问题是最后一个问题,我认为重新阅读现代 C++ 第 11 章的技巧是我错过的。
  • 感谢您的链接。这方面的任何主题都非常有帮助。我可能看起来像个疯子,但提前决定这将是纯粹的通用帮助我学到了很多很酷的东西,但更重要的是我几乎完成了一个完整的游戏引擎设计,并带来了令人惊讶和意想不到的好处。它很容易正确使用,也很难错误使用,总是很好,但扩展起来却出奇的容易。远远超出我的预期。
  • @Tavison an std::vector<:function>> 确实可以将不同的、不相关的类型存储在一起。
  • 是的,但是签名必须匹配,所以如果一个对象的 get collidable() 返回一个球体,它就不能与一个存储盒子或网格的对象一起存储。在某些时候,需要再次知道对象以进行最终碰撞测试。或者有什么我想念的。我知道颠倒过来可以解决问题,但我不知道如何以这种方式获得最终的具体、具体的测试。而且我不知道如何在没有 bool doYouHaveABox() 之类的情况下再次恢复我的类型。我会添加更多我当前的代码,所以也许你可以告诉我我错过了什么。
  • 正在观看视频,但它崩溃了,所以我会下载它。我开始明白了。从对象的级别开始,它没有类型,但如果我理解正确,类型不会被删除。所以我仍然可以根据我只是比对象低的类型进行双重调度。如果这是我目前收集到的,我认为那将是完美的。
【解决方案2】:

这不完整而且我没有得到我想要的一切,但现在已经足够了。我正在输入整个解决方案,以防它对其他人有所帮助。

#include <boost\mpl\vector.hpp>
#include <boost\mpl\fold.hpp>
#include <boost\mpl\for_each.hpp>
#include <boost\mpl\inherit.hpp>
#include <boost\mpl\inherit_linearly.hpp>
#include <iostream>
#include <vector>

using namespace boost::mpl::placeholders;

typedef boost::mpl::vector<short, long, char, int> member_types;

template <typename T>
struct wrap
{
    std::vector<T> value;
};

typedef boost::mpl::inherit_linearly<member_types, boost::mpl::inherit<wrap<_2>, _1> >::type Generate;

class print
{
    Generate generated;

public:
    template <typename T>
    void operator()(T)
    {
        std::cout << *static_cast<wrap<T>&>(generated).value.begin() << std::endl;
    }

    template <typename T>
    void Add(T const& t)
    {
        static_cast<wrap<T>&>(generated).value.push_back(t);
    }
};

void main()
{
    print p;

    short s = 5;
    p.Add(s);
    long l = 555;
    p.Add(l);
    char c = 'c';
    p.Add(c);
    int i = 55;
    p.Add(i);

    boost::mpl::for_each<member_types>(p);
}

这不是我需要的最终对象,但现在我拥有了所有部件来制作我想要的东西。

更新

最后我明白了。

template <typename TL>
class print
{
    template <typename T>
    struct wrap
    {
        std::vector<T> value;
    };

    typedef typename boost::mpl::inherit_linearly<TL, boost::mpl::inherit<wrap<_2>, _1> >::type Generate;
    Generate generated;

public:
    void Print()
    {
        boost::mpl::for_each<TL>(*this);
    }

    template <typename T>
    void operator()(T)
    {
        std::cout << *static_cast<wrap<T>&>(generated).value.begin() << std::endl;
    }

    template <typename T>
    void Add(T const& t)
    {
        static_cast<wrap<T>&>(generated).value.push_back(t);
    }
};

这里的 TL 是一个 boost::mpl 容器,用于保存哪些类型。

我认为这为扩展提供了一个很好的起点,但涵盖了大部分元编程部分。

我希望这对其他人有所帮助。

【讨论】:

    猜你喜欢
    • 2021-05-09
    • 2016-07-21
    • 2021-04-25
    • 1970-01-01
    • 1970-01-01
    • 2022-10-12
    • 1970-01-01
    • 2017-01-07
    • 1970-01-01
    相关资源
    最近更新 更多