Может ли кто-нибудь помочь мне создать контейнер переменных с помощью Boost::MPL?

Я создал физическую систему, которая обрабатывает любой объект столкновения с любым объектом столкновения следующим образом:

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 и несколько раз читал книгу Андрея. Но я, кажется, увяз в том, как это работает, и на самом деле не перевожу это в то, как я его использую. Я бы хотел, чтобы в книге MPL был еще один раздел с примерами из реального мира.

Мне удалось заставить все части игрового движка взаимодействовать с рендерингом, физикой, коллизиями (я отделяю обнаружение от реакции), вводом, сетью, звуком и т. д. Все в общих чертах. Теперь мне просто нужно держать все вещи в общем виде. После всей этой универсальной работы было бы глупо требовать наследования только для того, чтобы я мог хранить что-то в контейнере, и я не хочу передавать код каждой возможности сбора, поскольку это одно из больших преимуществ универсального программирования.

Я видел, что Джалф указал, что он/она использовал MPL, чтобы сделать что-то подобное, но не вдавался в подробности, чтобы я мог в этом разобраться. Если кто-нибудь знает пример практического использования или где я могу получить больше информации об использовании MPL, я был бы признателен.

Еще раз спасибо!

Обновить

boost MPL и boost Fusion, кажется, делают то, что я хочу, но, похоже, очень мало хороших примеров из реальной жизни для обеих библиотек. Документация для MPL немного больше, чем этот шаблон, и удачи в понимании последствий этого. Fusion немного лучше с "Вот пример, но это только верхушка айсберга!"

Типичный пример Boost MPL — has_xxx. Они используют XXX и xxx в примере, из-за чего трудно увидеть разницу, где XXX (необходимый текст) и Test или CheckType или любой другой различимый тип пользователя можно использовать вместо xxx. Кроме того, нет упоминания о том, что ничего из этого не находится в пространстве имен. Теперь я понимаю, почему Скотт Мейерс сравнил это со сценой в душе в «Психо».

На самом деле это позор, потому что то немногое, что мне удалось скомпилировать и понять, делает действительно полезные вещи, но это так сложно понять, что я бы никогда не потратил столько усилий, если бы занимался поставкой продукта.

Если кто-нибудь знает примеры из реального мира или лучшие ссылки, объяснения или учебник, я был бы признателен.

Обновить

Вот еще код:

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

Тогда все, что мне нужно, это вызвать столкновение со всеми моими активными сталкивающимися объектами со всеми моими активными и пассивными объектами.

Я не использую std::function, потому что добавление имен функций делает код более понятным. Но, возможно, это просто унаследованное мышление.


person Tavison    schedule 26.04.2011    source источник
comment
Контейнеры mpl не имеют данных времени выполнения, вместо этого используйте контейнеры fusion   -  person Anycorn    schedule 26.04.2011
comment
Спасибо, я изучаю их.   -  person Tavison    schedule 26.04.2011
comment
Кроме того, я думаю, что шаблон, который вам нужен, это multiple dispatch. Так что вы можете перечитать связанная глава из книги Андрея... Удачи   -  person sehe    schedule 26.04.2011
comment
@sehe Я снова посмотрю эту главу. Я не читал его в какое-то время. Я сделал двойную отправку. Чего у меня нет, так это автоматического контейнера мира столкновений, который знает, какие последовательности находятся внутри, какие из них являются динамическими, а какие статичными.   -  person Tavison    schedule 26.04.2011
comment
@Tavison: возможно, я просто не совсем понимаю :) Я поставил +1 к вопросу, несмотря ни на что   -  person sehe    schedule 26.04.2011
comment
@sehe Возможно, я смотрю на что-то вроде диспетчера грубой силы. Я еще раз перечитаю главу. Я бы хотел, чтобы список типов генерировался автоматически, и, возможно, добавление дополнительного объекта сделает это.   -  person Tavison    schedule 26.04.2011
comment
@sehe О, и у меня все, от столкновения до падения, работает нормально. Мне просто приходится вручную писать код для каждой комбинации контейнеров типов, чего я и пытаюсь избежать.   -  person Tavison    schedule 26.04.2011
comment
@Tavsion: IIRC Я почти уверен, что у Александреску было решение этой проблемы, но у меня нет под рукой книжной полки.   -  person sehe    schedule 26.04.2011


Ответы (2)


Если я правильно понимаю, ваша проблема:

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<typename Iter> void insert(Iter first, Iter last) только для того, чтобы вы могли копировать содержимое контейнера в менеджер?

Позвольте мне предположить, что ваши требования таковы, что на самом деле manager должен использоваться гораздо более оперативно, как в моей первоначальной формулировке вашего вопроса. (Я не думаю, что это сильное воображение для PhysicsWorld). Есть альтернатива, которую я считаю более подходящей, гораздо менее многословной и более удобной в сопровождении: type-erasure. (Название метода может быть немного неудачным и может ввести в заблуждение в первый раз.)

Хорошим примером стирания типов является 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. Контракт здесь — это аспект стирания типов во время компиляции.

Я не собираюсь демонстрировать, как реализовать стирание типов, потому что есть отличная презентация Boostcon 2010 по теме. (Вы можете посмотреть презентацию и/или получить слайды по ссылке). Или я (или кто-то другой) могу сделать это в комментариях.

И последнее замечание: реализация стирания типов (обычно) использует динамический полиморфизм. Я упомянул об этом, потому что заметил, что вы рассматривали использование списков типов как объекта времени выполнения, хранящегося как член manager. Это пахнет отражением бедняка, и действительно, динамическим полиморфизмом бедняка. Так что не делай этого, пожалуйста. Если вы имели в виду списки типов как результат вычисления MPL, то игнорируйте узел.

person Luc Danton    schedule 26.04.2011
comment
Сначала последнее замечание: да, я ищу вычисление времени компиляции списка типов. И я широко использую функцию, но даже если вы можете объединить несвязанные типы вместе, вы не можете хранить их вместе. Если мы представим полностью общий мир, в котором вместо ООП возобладало универсальное программирование, то идея контейнера, содержащего объекты, которые согласны с контрактом, но не связаны между собой, не покажется такой уж странной. Проблема локализации — это последняя проблема, и я думаю, что совет перечитать главу 11 современного C++ — это то, что я упустил. - person Tavison; 27.04.2011
comment
И спасибо за ссылку. Любые темы на эту тему очень полезны. Я могу показаться сумасшедшим, но заранее приняв решение, что это будет чисто универсальная игра, я узнал много интересных вещей, но что более важно, я почти закончил полный дизайн игрового движка с удивительными и неожиданными преимуществами. Его очень легко использовать правильно и трудно использовать неправильно, всегда приятно, но его на удивление легко расширить. Гораздо больше, чем я ожидал. - person Tavison; 27.04.2011
comment
@Tavison и std::vector‹std::function‹void()›› действительно могут хранить вместе разные, несвязанные типы. - person Luc Danton; 27.04.2011
comment
Да, но сигнатура должна совпадать, поэтому, если get collidable() одного объекта возвращает сферу, она не может быть сохранена с тем, который хранит коробку или сетку. В какой-то момент объекты должны быть снова известны для окончательного теста на столкновение. Или я что-то упускаю. Я знаю, что перевернув вещи с ног на голову, можно все исправить, но я не вижу, как таким образом получить окончательное конкретное, конкретное испытание. И я не знаю, как мне снова получить свой тип без чего-то вроде bool doYouHaveABox(). Я добавлю больше моего текущего кода, так что, возможно, вы сможете показать мне, что я пропустил. - person Tavison; 27.04.2011
comment
Смотрю видео, но оно вылетело, поэтому я его скачаю. Я начинаю понимать идею. От уровня объекта вверх у него нет типа, но тип не стирается, если я правильно понимаю. Так что я все еще могу удвоить отправку на основе типов, я просто делаю это ниже, чем объект. Я думаю, что это было бы прекрасно, если бы это было то, что я собираю до сих пор. - person Tavison; 27.04.2011

Это не полное И я не получил всего, что хотел, но пока этого достаточно. Я ввожу все решение на случай, если оно поможет другим.

#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, какие типы нужно хранить.

Я думаю, что это обеспечивает хорошую отправную точку для расширения, но охватывает большую часть частей метапрограммирования.

Надеюсь, это поможет другим.

person Tavison    schedule 02.05.2011