Может ли кто-нибудь помочь мне создать контейнер переменных, используя 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();
...
...
}
Я бы хотел выводить контейнеры с помощью функций добавления, поэтому использование системы автоматически обновляет списки типов. Я думаю, что понимаю, как создать список типов с помощью функции шаблона, но не то, как получить его там, где он мне нужен, или на каком этапе компиляции он завершен.
Если нет, то какая-то система использует два списка типов, а затем внутренне записывает функцию обновления для перебора всех списков, объединяя их друг с другом.
Я прочитал несколько книг по ускоренному MPL и несколько раз читал книгу Андрея. Но я, кажется, увлекся тем, как все это работает, и не перевожу это на вопрос, как мне это использовать. Я бы хотел, чтобы у них был еще один раздел о реальных примерах в книге MPL.
Мне удалось заставить все части игрового движка взаимодействовать с рендерингом, физикой, столкновениями (я отделяю обнаружение от реакции), вводом, сетью, звуком и т. Д. Все в общих чертах. Теперь мне просто нужно обобщить все вещи. После всей этой общей работы было бы глупо требовать наследования, чтобы я мог что-то хранить в контейнере, и я не хочу передавать код каждой возможности сбора, поскольку это является одним из больших преимуществ универсального программирования.
Я видел, что Джальф указал, что он / она использовал MPL для того, чтобы сделать нечто подобное, но не вдавался в подробности, чтобы я мог это понять. Если кто-нибудь знает пример практического использования или где я могу получить больше информации об использовании MPL, я был бы благодарен.
Еще раз спасибо!
Обновить
Boost MPL и Boost Fusion, кажется, делают то, что я хочу, но, похоже, очень мало хороших примеров из реальной жизни обеих библиотек. Документация по MPL немного больше, чем этот шаблон, и удачи в понимании последствий этого. Fusion немного лучше с "Вот пример, но это только верхушка айсберга!"
Типичным примером повышения MPL является has_xxx. В этом примере они используют XXX и xxx, что затрудняет понимание разницы, где вместо xxx можно использовать XXX(требуемый текст) и Test или CheckType или любой более различимый тип пользователя. Плюс нет упоминания о том, что ничего из этого нет в пространстве имен. Теперь я знаю, почему Скотт Мейерс сравнил это с душевой в Психо.
Это действительно позор, потому что то, что я получил, чтобы скомпилировать и понять, действительно полезно, но так сложно понять, что я никогда не потратил бы столько усилий, если бы работал над доставкой продукта.
Если кто-то знает примеры из реального мира или лучшие ссылки, объяснения или учебник, я был бы благодарен.
Обновить
Вот еще код:
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, потому что добавление имен функций делает код более понятным для меня. Но, возможно, это просто устаревшее мышление.
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 того, какие типы должны храниться.
Я думаю, что это хорошая отправная точка для расширения, но охватывает большую часть частей метапрограммирования.
Я надеюсь, что это помогает другим.
Если я правильно понимаю, ваша проблема:
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
). Существует альтернатива, которая, на мой взгляд, является более подходящей, гораздо менее многословной и более подходящей: стирание типов. (Название техники может быть немного неудачным и может вводить в заблуждение в первый раз.)
Хорошим примером стирания типа является 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, игнорируйте узел.