読者です 読者をやめる 読者になる 読者になる

Boost.TypeErasureが更新された

C++

RFC: type erasure


VaultにBoost.TypeErasureというライブラリが置いてあるのですが、「設計が悪いから書きなおした」とのこと。
ダウンロードして見てみると、すごく使いやすくなっていました。
ユーザー定義の要件も簡単に作れます。


ゲームのシーン管理はこのように書くことができます:

#include <iostream>
#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/primitive_concept.hpp>
#include <boost/type_erasure/concept_interface.hpp>
#include <boost/type_erasure/rebind_any.hpp>
#include <boost/mpl/vector.hpp>

using namespace boost::type_erasure;

template <class T>
struct update : primitive_concept<update<T>, void(T&)> {
    static void apply(T& x) { x.update(); }
};

template <class T>
struct draw : primitive_concept<draw<T>, void(const T&)> {
    static void apply(const T& x) { x.draw(); }
};

namespace boost { namespace type_erasure {

template <class T, class Base>
struct concept_interface<update<T>, Base, T> : Base {
    void update()
        { ::update<T>()(*this); }
};

template <class T, class Base>
struct concept_interface<draw<T>, Base, T> : Base {
    void draw() const
        { ::draw<T>()(*this); }
};

}} // namespace boost::type_erasure


// 継承関係はない
struct Title {
    void update()     { std::cout << "Title::update" << std::endl; }
    void draw() const { std::cout << "Title::draw" << std::endl; }
};

struct Game {
    void update()     { std::cout << "Game::update" << std::endl; }
    void draw() const { std::cout << "Game::draw" << std::endl; }
};

namespace mpl = boost::mpl;

int main()
{
    typedef any<mpl::vector<
        update<_self>,
        draw<_self>,
        destructible<>,
        assignable<>
    >> scene_type;

    Title title;
    scene_type scene(title);
    scene.update();
    scene.draw();

    Game game;
    scene = scene_type(game);
    scene.update();
    scene.draw();
}
Title::update
Title::draw
Title::update
Title::draw

staticなapply関数だけを持つ小さな要件クラスをまず作り、その後それをanyのインタフェースとして使えるようにするために継承可能なインタフェースで包みます。これだけでユーザー定義の要件を作ることができます。
実際に使用する際には、boost::type_erasure::anyクラスに要件のリストをmpl::vectorで指定します。
_selfは、その名のとおり、自分自身を指す型のプレースホルダーです。他に、第1引数を指す_aプレースホルダー、第2引数を指す_bプレースホルダーなどもあります。


とても簡単で使いやすく、わかりやすいと思います。


内部で保持する値を切り替えたい場合には、一旦anyに変換してから代入します。
これは、代入の動作をユーザー定義で切り替えたい場合のための設計判断でしょう。


destructibleやassignableを毎回指定するのがめんどうであれば、これらのよく使う要件を合成して名前を付けておくこともできます。

typedef mpl::vector<destructible<>, assignable<>> common; // よく使う要件
typedef any<mpl::vector<update<_self>, draw<_self>, common>> scene_type;

この設計はなかなか天才的ですばらしいと感じます。
BoostCon 2011で発表のあったBoost.Genericと連携していく予定のようなので、今後に期待ですね。



2011/05/23 11:42 追記
実行結果がどっちもTitleクラスのを呼び出しててダメですね・・・。
こちら修正版です。assignableはやっぱり指定してはいけないです。

#include <iostream>
#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/primitive_concept.hpp>
#include <boost/type_erasure/concept_interface.hpp>
#include <boost/type_erasure/rebind_any.hpp>
#include <boost/mpl/vector.hpp>

using namespace boost::type_erasure;

template <class T>
struct update : primitive_concept<update<T>, void(T&)> {
    static void apply(T& x) { x.update(); }
};

template <class T>
struct draw : primitive_concept<draw<T>, void(const T&)> {
    static void apply(const T& x) { x.draw(); }
};

namespace boost { namespace type_erasure {

template <class T, class Base>
struct concept_interface<update<T>, Base, T> : Base {
    void update()
        { ::update<T>()(*this); }
};

template <class T, class Base>
struct concept_interface<draw<T>, Base, T> : Base {
    void draw() const
        { ::draw<T>()(*this); }
};

}} // namespace boost::type_erasure


struct Title {
    void update()     { std::cout << "Title::update" << std::endl; }
    void draw() const { std::cout << "Title::draw" << std::endl; }
};

struct Game {
    void update()     { std::cout << "Game::update" << std::endl; }
    void draw() const { std::cout << "Game::draw" << std::endl; }
};

namespace mpl = boost::mpl;

int main()
{
    typedef any<mpl::vector<
        update<_self>,
        draw<_self>,
        copy_constructible<>,
        relaxed_match
    >> scene_type;

    Title title;
    scene_type scene(title);
    scene.update();
    scene.draw();

    Game game;
    scene = scene_type(game);
    scene.update();
    scene.draw();
}
Title::update
Title::draw
Game::update
Game::draw