Boost.TypeErasureのドキュメント翻訳

訳してみました。型や関数ごとの説明以外は訳してあります。
本家

Boost.TypeErasure

Steven Watanabe

Copyright © 2011-2013 Steven Watanabe

Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

紹介
Boost.TypeErasureライブラリはC++においてコア言語が提供するものよりもより柔軟な実行時多態性を提供する。
C++は仮想関数とテンプレートという二つの異なる種類の多態性を持ち、ぞれぞれ利点と不利な点を持つ。
・仮想関数は実行時まで解決されないが、テンプレートは常にコンパイル時に解決される。もしあなたの型が実行時に変化しうるなら(例えばユーザーの入力にその型が依存するならば)テンプレートによる静的多態性はあまり役立たない。
・仮想関数は分割コンパイルに用いることができる。テンプレートの本体はそれが使用される全ての翻訳単位ごとに利用できる必要があるため、コンパイルを遅くしリビルド時間を増加させる。
・仮想関数は自動的に明示的な引数の要件を作成する。テンプレートはそれらがインスタンス化される際にチェックされるのみであり、テストやアサートやドキュメントにおいて余分な労力を要求する。
コンパイラは関数テンプレートがインスタンス化される度に新しくそのコピーを作成する。これはコンパイラが静的に何もかも知っているため良い最適化を可能にする一方でバイナリサイズの肥大化も引き起こす。
・テンプレートは値セマンティクスをサポートする。"intのように振舞い"そして共有されないオブジェクトは理解が容易である。その一方で仮想関数を使用するには(スマート)ポインタや参照を使用する必要がある。
・テンプレートライブラリはサードパーティの型を非侵入的に適用でき、シームレスな相互運用性を確保できる。仮想関数の場合基底クラスを継承したラッパーを作る必要がある。
・テンプレートは複数の型に関係する制約を扱うことができる。例えばstd::for_eachはイテレータレンジとそのレンジの要素ごとに呼ばれる関数を取る。仮想関数はそのような制約を真に表現できない。
Boost.TypeErasureライブラリはテンプレートの優れた抽象化能力と仮想関数の実行時の柔軟性を組み合わせる。
Boostはこの種の多態性の幾つかの特殊解を持っている。
・boost::any は CopyConstructible な型用である。
・boost::function は関数のように呼び出すことができるオブジェクト用である。
・Boost.Range は any_iterator を提供する。
Boost.TypeErasure は任意の要求をサポートするようにこれらを一般化し、事前定義された一般的なコンセプト群を提供する。

このドキュメントの読み方
過度な冗長性を避けるため全てのコード例は幾つかのusing directiveが定義されていると仮定する。

namespace mpl = boost::mpl;
using namespace boost::type_erasure;

基本的な使用法

(この節のコード例のソースはbasic.cppを参照のこと)
このライブラリの中心を成すクラスは any である。anyは指定した要件に適合するオブジェクトを格納できる。これらの要件はanyにMPLシーケンスとして渡される。

MPLシーケンスは複数のコンセプトを組み合わせる。ひとつのコンセプトのみを望む稀なケースではそれをMPLシーケンスでラップする必要は無い。

any<mpl::vector<copy_constructible<>, typeid_<>, relaxed> > x(10);
int i = any_cast<int>(x); // i == 10

copy_constructibleはオブジェクトのコピーと破棄を許可する組み込みのコンセプトである。typeid_は実行時型情報を提供するのでany_castを使用することができる。relaxedは便利で多様な既定動作を有効にする。relaxedを指定しない場合、anyは指定されたコンセプトのみを正確にサポートし、それ以外はサポートしない。特にrelaxedはanyのデフォルトコンストラクトと代入を許可する。
さて、この例ではたいした事をしておらずxはほとんどboost::anyと同じである。これをoperator++やoperator<<のような幾つかの演算子を追加することによってより面白くできる。

any<
    mpl::vector<
        copy_constructible<>,
        typeid_<>,
        incrementable<>,
        ostreamable<>
    >
> x(10);
++x;
std::cout << x << std::endl; // prints 11

本ライブラリは大半のC++演算子のコンセプトを提供しているが、これは明らかに全ての利用例をカバーするものではない。しばしば自身の要件に応じて定義する必要がある。そこで幾つかのSTLコンテナに定義されているpush_backメンバを例に取ってみよう。

BOOST_TYPE_ERASURE_MEMBER((has_push_back), push_back, 1)

void append_many(any<has_push_back<void(int)>, _self&> container) {
    for(int i = 0; i < 10; ++i)
        container.push_back(i);
}

has_push_backという名前のコンセプトを定義するためにBOOST_TYPE_ERASURE_MEMBERマクロを使用している。2番目のパラメーターはメンバ関数の名前で最後のマクロパラメーターは引数の数を示していてpush_backが単項であるから1である。has_push_backを使用する時は関数のシグネチャvoid(int)を示す必要がある。これはanyに格納する型が以下のようなメンバを持つ必要性があることを意味する。

void push_back(int);

このように我々はappend_manyをstd::vector<int>,std::list<int>またはstd::vector<long>(なぜならintはlongに変換可能だから)で呼び出すことができる。しかしstd::list<std::string>またはstd::set<int>ではできない。

またappend_manyは自身の引数自体を演算するしかないことに注意せよ。コピーを作ることはできない。これを扱うにはanyの第2引数に_self&を使用する。_selfはplaceholderである。_self&を使用することによって自身のオブジェクトを割り当てる代わりに外部オブジェクトへの参照をanyに格納することを示す。

ここでは別のplaceholderも実際には存在している。has_push_backの第2パラメータは既定で_selfが指定されている。もしconstメンバ関数を定義したいならそれを以下に示すようにconst _selfに変更する必要がある。

BOOST_TYPE_ERASURE_MEMBER((has_empty), empty, 0)
bool is_empty(any<has_empty<bool(), const _self>, const _self&> x) {
    return x.empty();
}

フリー関数を指定するにはBOOST_TYPE_ERASURE_FREEマクロを使用できる。

BOOST_TYPE_ERASURE_FREE((has_getline), getline, 2)
std::vector<std::string> read_lines(any<has_getline<bool(_self&, std::string&)>, _self&> stream)
{
    std::vector<std::string> result;
    std::string tmp;
    while(getline(stream, tmp))
        result.push_back(tmp);
    return result;
}

has_getlineの使用方法は前述のhas_push_backに非常に近い。違いはplaceholderの_selfが関数シグネチャ内に個別の引数の代わりに渡されていることである。

placeholderは第1引数にある必要はない。第2引数にすることも容易である。

void read_line(any<has_getline<bool(std::istream&, _self&)>, _self&> str)
{
    getline(std::cin, str);
}

コンセプトの結合
(この節のコード例のソースはcompose.cppを参照のこと)

複数のコンセプトをMPLシーケンスを使用して結合できる。

template<class T = _self>
struct arithmetic :
    mpl::vector<
        copy_constructible<T>,
        addable<T>,
        subtractable<T>,
        multipliable<T>,
        dividable<T>,
        equality_comparable<T>,
        less_than_comparable<T>
    >
{};

これでarithmeticはこのベースコンセプトのいずれとしても使用できるコンセプトになる。

複数の引数を持つ関数
(この節のコード例のソースはmulti.cppを参照のこと)
演算はひとつ以上のany引数を持つことができる。例として二項の加算を使用してみよう。

typedef any<
    mpl::vector<
        copy_constructible<>,
        typeid_<>,
        addable<>,
        ostreamable<>
    >
> any_type;
any_type x(10);
any_type y(7);
any_type z(x + y);
std::cout << z << std::endl; // prints 17

これはマルチメソッドではない。この+の引数の基礎の型は同じでなければならない、さもなくば未定義動作になる。この例は正しい。なぜならば引数は両方ともintを保持しているからである。

relaxedを指定することにより引数の型が間違っていても未定義動作にならず例外を投げるようになる。

addable<>は引数の型が正確に同じであることを要求する。しかしこれは全ての加算の使用法をカバーしていない。例えば、ポインタ演算はポインタと整数を引数に取りポインタを返す。この種の幾つかの型の間の関係をplaceholderに関連付けた型ごとに識別することでキャプチャすることができる。ポインタをplaceholder _aで表現し、整数をplaceholder _bで表現することにしよう。

int array[5];

typedef mpl::vector<
    copy_constructible<_a>,
    copy_constructible<_b>,
    typeid_<_a>,
    addable<_a, _b, _a>
> requirements;

この新しいコンセプトではaddable<_a, _b, _a>は _a + _b -> _a というポインタの加算ルールを取り扱っている。
また、独立して変数をキャプチャすることはもうできない。

any<requirements, _a> ptr(&array[0]); // illegal

これは動かない。なぜならばライブラリはコンセプトバインディングをキャプチャするときに_bをどの型にバインドしたらいいか知る必要があるからだ。anyをコンストラクトするときに両方のplaceholderのバインディングを指定する必要がある。

typedef mpl::map<mpl::pair<_a, int*>, mpl::pair<_b, int> > types;
any<requirements, _a> ptr(&array[0], make_binding<types>());
any<requirements, _b> idx(2, make_binding<types>());
any<requirements, _a> x(ptr + idx);
// x now holds array + 2

ここで+の引数は同じ型ではなく、引数_aはint*にマップし引数_bはintにマップすることを要求している。
またtupleを使うことで明示的にマップを書くことを回避できる。tupleはその全ての引数からplaceholderのバインディングを組み合わせる便利なクラスである。

tuple<requirements, _a, _b> t(&array[0], 2);
any<requirements, _a> y(get<0>(t) + get<1>(t));

コンセプト詳細

カスタムコンセプトの定義
(この節のコード例のソースはcustum.cppを参照のこと)

先ほどBOOST_TYPE_ERASURE_MEMBERをpush_backをサポートするコンテナ用のコンセプトを定義するために使用した。しかしながら時々このインターフェイスは柔軟性が十分とは言えなくなる。本ライブラリは動作を完全に制御するための低レベルインターフェイスを提供する。ではhas_push_backを定義するために何が必要になるか見ていこう。最初にhas_push_backテンプレート自身を定義する必要がある。これには二つのテンプレートパラメータが必要になる。ひとつはコンテナ用でもうひとつは要素の型用である。このテンプレートは演算を実行するのに使用されるapplyという名前のstaticメンバ関数を持たなければならない。

template<class C, class T>
struct has_push_back
{
    static void apply(C& cont, const T& arg) { cont.push_back(arg); }
};

これで演算をディスパッチするためにcallを用いてanyで使用できる。

std::vector<int> vec;
any<has_push_back<_self, int>, _self&> c(vec);
int i = 10;
call(has_push_back<_self, int>(), c, i);
// vec is [10].

二番目にやることはc.push_back(10)と呼べるようにanyをカスタマイズすることだ。これはconcept_interfaceを特殊化することで行う。第1引数はhas_push_backでhas_push_backコンセプトを使用する全てのanyにメンバ関数を注入するためである。第2引数はBaseでライブラリによって複数のconcept_interfaceを一緒にチェインさせるのに使用される。我々はこれをpublic継承する必要がある。またBaseは完全なany型へのアクセスを手に入れるために使用される。第3引数はこのanyを表現するplaceholderである。もし誰かがpush_back<_c, _b>を使用したならば、我々はコンテナにpush_backメンバのみ挿入することを望む。値型ではなく。よって第3引数はコンテナのplaceholderである。

push_backを定義するとき引数の型はas_paramメタ関数を使用する。これはTがplaceholderの場合を扱っているだけである。もしTがplaceholderでないならばメタ関数はそれ自身の引数 const T&をそのまま返すだけある。

namespace boost {
namespace type_erasure {
template<class C, class T, class Base>
struct concept_interface<has_push_back<C, T>, Base, C> : Base
{
    void push_back(typename as_param<Base, const T&>::type arg)
    { call(has_push_back<C, T>(), *this, arg); }
};
}
}

そこで我々の例はこうなる。

std::vector<int> vec;
any<has_push_back<_self, int>, _self&> c(vec);
c.push_back(10);

これが我々が望んだものである。

オーバーロード
(この節のコード例のソースはoverload.cppを参照のこと)

concept_interfaceはanyに任意の宣言の注入を可能にする。これはとても柔軟性があるが幾つかの注意すべき落とし穴がある。時々同じコンセプトを違ったパラメータで何回か使用したいときがある。concept_interfaceを特殊化してオーバーロードを正しく扱うことは少々トリッキーだ。fooというコンセプトがあったとして以下のように動かしたい。

any<
    mpl::vector<
        foo<_self, int>,
        foo<_self, double>,
        copy_constructible<>
    >
> x = ...;
x.foo(1);   // calls foo(int)
x.foo(1.0); // calls foo(double)

concept_interfaceは直線的に継承チェーンを作りそれ以上のことをしない。そのためひとつのfooのオーバーロードが他のものを隠してしまう。
ここに私が見つけたきちんと動くテクニックがある。
メンバ関数用には二つの特殊化の使用を回避する方法が見つけられなかった。

template<class T, class U>
struct foo
{
    static void apply(T& t, const U& u) { t.foo(u); }
};

namespace boost {
namespace type_erasure {

template<class T, class U, class Base, class Enable>
struct concept_interface< ::foo<T, U>, Base, T, Enable> : Base
{
    typedef void _fun_defined;
    void foo(typename as_param<Base, const U&>::type arg)
    {
        call(::foo<T, U>(), *this, arg);
    }
};

template<class T, class U, class Base>
struct concept_interface< ::foo<T, U>, Base, T, typename Base::_fun_defined> : Base
{
    using Base::foo;
    void foo(typename as_param<Base, const U&>::type arg)
    {
        call(::foo<T, U>(), *this, arg);
    }
};

}
}

これはSFINAEをusing declarationが必要かどうか判別するために使用している。concept_interfaceの第4引数はSFINAEの使用に伴う常にvoidのダミーパラメータである。私が過去に試した問題のほかの解決策としてはfunのダミー宣言を注入し常にusing declaration内に入れることだ。これはいくつかの理由から劣っている解決策である。これはダミーオーバーロードを追加するための余分なインターフェイスを要求する。またこれはユーザーが一つだけのオーバーロードしか求めていない場合でもfunが常にオーバーロードされることを意味する。これはfunのアドレスを取ることを困難にする。

SFINAEの使用は幾つかのコードの重複を要求することに注意せよ。concept_interfaceの実装は普通1行で書けるので重複するコードの総量は相対的に小さい。少々煩わしいが、私はこれはベターな解決策のためには受け入れ可能なコストだと信じている。

フリー関数用にはinline friendが使用できる

template<class T, class U>
struct bar_concept
{
    static void apply(T& t, const U& u) { bar(t, u); }
};

namespace boost {
namespace type_erasure {

template<class T, class U, class Base>
struct concept_interface< ::bar_concept<T, U>, Base, T> : Base
{
    friend void bar(typename derived<Base>::type& t, typename as_param<Base, const U&>::type u)
    {
        call(::bar_concept<T, U>(), t, u);
    }
};

template<class T, class U, class Base>
struct concept_interface< ::bar_concept<T, U>, Base, U, typename boost::disable_if<is_placeholder<T> >::type> : Base
{
    using Base::bar;
    friend void bar(T& t, const typename derived<Base>::type& u)
    {
        call(::bar_concept<T, U>(), t, u);
    }
};

}
}

基本的にオーバーロードが確実にplaceholderである第1引数に注入されるように各引数ごとにconcept_interfaceを特殊化する必要がある。お気づきかもしれないが引数の型は少々トリッキーである。最初の特殊化では第1引数はas_paramの代わりにderivedを使用している。この理由はもしas_paramを使用した場合同じ関数を2回定義することにより最後にはODR違反になるからである。同様に二番目の特殊化にSFINAEを使用して引数が両方ともplaceholderの場合にbarが確実に一度だけ定義されるようにしている。メタプログラミングを少し用いればこの二つの特殊化された関数を統一することは可能だろうが、引数が大量でもない限りおそらくやるだけの価値はないだろう。

コンセプトマップ
(この節のコード例のソースはconcept_map.cppを参照のこと)

時々コンセプトモデルに型を非侵入的に適用できると便利なことがある。例えばstd::type_infoをless_than_comparableモデルにしたい状況を考えてみよう。これを行うには単にコンセプト定義を特殊化すればよい。

namespace boost {
namespace type_erasure {

template<>
struct less_than_comparable<std::type_info>
{
    static bool apply(const std::type_info& lhs, const std::type_info& rhs)
    { return lhs.before(rhs) != 0; }
};

}
}

大半は可能だが、しかし全ての組み込みのコンセプトが特殊化できるとは限らない。コンストラクタ、デストラクタ、RTTIはライブラリから特別な扱いを受ける必要があり特殊化できない。プリミティブなコンセプトのみが特殊化できる。そんなわけでiteratorコンセプトも不可能である。

関連型
(この節のコード例のソースはassociated.cppを参照のこと)

typename T::value_type や typename std::iterator_traits<T>::reference といった関連型はテンプレートプログラミングにおいて極めて一般的である。Boost.TypeErasureはdeducedテンプレートを使用してこれらを取り扱う。deducedはメタ関数によって呼ばれることによりバインドされる型が決まり明示的に指定する必要がないことを除いて普通のplaceholderと同じである。

たとえばイテレータ、生ポインタ、もしくはスマートポインタを保有可能なコンセプトを以下のように定義できる。まずその関連型を定義するpointeeという名前のメタ関数を定義する。

template<class T>
struct pointee
{
    typedef typename mpl::eval_if<is_placeholder<T>,
        mpl::identity<void>,
        boost::pointee<T>
    >::type type;
};

単純にboost::pointeeを使うことができないことに注意せよ。なぜならこのメタ関数はplaceholderでインスタンス化しても安全にする必要があるからだ。エラーを起こさない限りそれが何を返そうが構わない。(本ライブラリはplaceholderを用いてインスタンス化をすることは決してないが、ADLが偽のインスタンス化を起こす可能性がある。)

template<class T = _self>
struct pointer :
    mpl::vector<
        copy_constructible<T>,
        dereferenceable<deduced<pointee<T> >&, T>
    >
{
    // provide a typedef for convenience
    typedef deduced<pointee<T> > element_type;
};

ここでxのコンセプトは_selfとpointer<>::element_typeの二つのplaceholderを使用している。xをint*でコンストラクトするときpointer<>::element_typeはpointee<int*>::typeに推論され、それはintである。よってxをdereferencingするとintを保持するanyを返す。

int i = 10;
any<
    mpl::vector<
        pointer<>,
        typeid_<pointer<>::element_type>
    >
> x(&i);
int j = any_cast<int>(*x); // j == i

時々関連型が指定された型であることを要求したいことがある。これはsame_typeコンセプトを使用することによって解決できる。ここでintの要素型を指すどんなポインタをも保有することができるanyを作成する。

int i = 10;
any<
    mpl::vector<
        pointer<>,
        same_type<pointer<>::element_type, int>
    >
> x(&i);
std::cout << *x << std::endl; // prints 10

このようにsame_typeを使用することにより事実上ライブラリが全てのpointer<>::element_typeの使用をintで置き換えることになり、常にそれがintにバインドされているということを保障できる。よってxをdereferencingするとintが返る。
またsame_typeはplaceholderが二つでも使用できる。これは関連型を何度も書く代わりに簡単な名前の使用を可能にする。

int i = 10;
any<
    mpl::vector<
        pointer<>,
        same_type<pointer<>::element_type, _a>,
        typeid_<_a>,
        copy_constructible<_a>,
        addable<_a>,
        ostreamable<std::ostream, _a>
    >
> x(&i);
std::cout << (*x + *x) << std::endl; // prints 20

Anyの使用
コンストラク
(この節のコード例のソースはconstruction.cppを参照のこと)
本ライブラリはanyがコンストラクタをキャプチャ可能にするconstructibleコンセプトを提供する。単独のテンプレート引数は関数シグネチャ形式でなければならない。戻り値型は構築される型を指定するplaceholderである。引数型はコンストラクタの引数である。

typedef mpl::vector<
    copy_constructible<_a>,
    copy_constructible<_b>,
    copy_constructible<_c>,
    constructible<_a(const _b&, const _c&)>
> construct;

typedef mpl::map<
    mpl::pair<_a, std::vector<double> >,
    mpl::pair<_b, std::size_t>,
    mpl::pair<_c, double>
> types;

any<construct, _b> size(std::size_t(10), make_binding<types>());
any<construct, _c> val(2.5, make_binding<types>());
any<construct, _a> v(size, val);
// v holds std::vector<double>(10, 2.5);

ここでデフォルトコンストラクタを望む場合は?我々は保持される型のデフォルトコンストラクタを呼び出すanyのデフォルトコンストラクタを持つことができない。なぜなら保持される型がなんであるか知る方法がないからだ。つまりplaceholderのバインド情報を明示的に渡す必要性がある。

typedef mpl::vector<
    copy_constructible<>,
    constructible<_self()>
> construct;

any<construct> x(std::string("Test"));
any<construct> y(binding_of(x)); // y == ""

これはデフォルトコンストラクタに限定された方法ではない。もしコンストラクタが引数を取るのならばそれらはバインディングの後に渡すことができる。

typedef mpl::vector<
    copy_constructible<>,
    constructible<_self(std::size_t, char)>
> construct;

any<construct> x(std::string("Test"));
any<construct> y(binding_of(x), 5, 'A');

変換
(この節のコード例のソースはconvert.cppを参照のこと)
変換が"アップキャスト"である限りanyは他のanyへ変換することができる。

typedef any<
    mpl::vector<
        copy_constructible<>,
        typeid_<>,
        ostreamable<>
    >
> any_printable;
typedef any<
    mpl::vector<
        copy_constructible<>,
        typeid_<>
    >
> common_any;
any_printable x(10);
common_any y(x);

この変換は可能である。なぜならばcommon_anyの要件はany_printable要件のサブセットだからである。逆方向の変換は不可。

common_any x(10);
any_printable y(x); // error

参照
(この節のコード例のソースはreferences.cppを参照のこと)
参照でキャプチャするには単にplaceholderに参照を追加すればよい。

int i;
any<typeid_<>, _self&> x(i);
any_cast<int&>(x) = 5; // now i is 5

_selfは既定のplaceholderである。なので_self&を使うことは簡単である。また代わりに別のplaceholderを使用しても良い。any<typeid_<_a>, _a&>も全く同じ挙動をする。

参照は再バインドすることはできない。本来のC++参照と同じく、一度初期化してしまえば他のものを指すように変更することはできない。

int i, j;
any<typeid_<>, _self&> x(i), y(j);
x = y; // error

メモ

他の演算と同じく、参照のx=yはiとjに対して作用する。もしassignable<>がコンセプトに含まれていればこのような代入は可能であるが、xは依然としてiへの参照を保持する。

参照は他のanyへバインドできる。

typedef mpl::vector<
    copy_constructible<>,
    incrementable<>
> requirements;

any<requirements> x(10);
any<requirements, _self&> y(x);
++y; // x is now 11

もし参照が参照元オブジェクトの寿命が切れるか初期化された後に使用されると未定義動作である。

typedef mpl::vector<
    copy_constructible<>,
    incrementable<>,
    relaxed
> requirements;
any<requirements> x(10);
any<requirements, _self&> y(x);
x = 1.0;
++y; // undefined behavior.

これは参照が値から構築される場合にのみ適用される。もし参照が他の参照から構築される場合、新しい参照は古い参照に依存しない。

any<requirements> x(10);
boost::shared_ptr<any<requirements, _self&> > p(
    new any<requirements, _self&>(x));
any<requirements, _self&> y(*p); // equivalent to y(x);
p.reset();
++y; // okay

constと非const参照の両方がサポートされている。

int i = 0;
any<incrementable<>, _self&> x(i);
any<incrementable<>, const _self&> y(x);

非const参照はconst参照へ変換できるが逆方向はできない。当然ながらconst参照には変更を加えるような演算は適用できない。

any<incrementable<>, _self&> z(y); // error
++y; // error

文法の限界
たいていの場合anyは基礎オブジェクトを使用するのと同じ文法を持っている。しかしながらこれを実装することができない幾つかの場合が存在する。any参照はプロキシであり本当の参照が要求される場所で使用することはできない。特に、forward_iteratorは(value_typeを固定せずに) ForwardIteratorを満たすように作成することはできない。他の違いとしては少なくともひとつ以上のany引数を取らない全ての演算は明示的に型情報を渡す必要性がある。静的メンバ関数とコンストラクタはこのカテゴリに属すことがある。これが意味することはジェネリックアルゴリズムがany引数に適用された場合に動かないかもしれないということだ。


多相レンジフォーマッター
(この節のコード例のソースはprint_sequence.cppを参照のこと)
この例では幾つかの異なった方法でシーケンスをフォーマット可能にするクラス階層を定義している。どんなシーケンスやストリーム型でも扱うことができるようにしたい。レンジフォーマットすることは個別の要素をフォーマットすることとは独立しているからである。そこでこのようなインターフェイスが必要になる。

class abstract_printer {
public:
    template<class CharT, class Traits, class Range>
    virtual void print(std::basic_ostream<CharT, Traits>& os, const Range& r) const = 0;
};

残念なことにこれは不可能だ。仮想関数はテンプレートにはなれないからだ。しかしながらBoost.TypeErasureを使用することによってこれと同じことをするクラスを定義することが可能になる。

#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/iterator.hpp>
#include <boost/type_erasure/operators.hpp>
#include <boost/type_erasure/tuple.hpp>
#include <boost/type_erasure/same_type.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/iterator.hpp>
#include <iostream>
#include <iomanip>
#include <vector>

using namespace boost::type_erasure;

struct _t : placeholder {};
struct _iter : placeholder {};
struct _os : placeholder {};

template<class T, class U = _self>
struct base_and_derived
{
    static T& apply(U& arg) { return arg; }
};

namespace boost {
namespace type_erasure {

template<class T, class U, class Base>
struct concept_interface<base_and_derived<T, U>, Base, U> : Base
{
    operator typename rebind_any<Base, const T&>::type() const
    {
        return call(base_and_derived<T, U>(), const_cast<concept_interface&>(*this));
    }
    operator typename rebind_any<Base, T&>::type()
    {
        return call(base_and_derived<T, U>(), *this);
    }
};

}
}

// abstract_printer - フォーマットするシーケンス用の抽象基底クラス
class abstract_printer {
public:
    // print - シーケンスを任意の方法で派生クラスのstd::ostreamに書き出す
    //
    // 要件: RengeはForward Rangeでその要素がosにプリントできる必要がある。
    template<class CharT, class Traits, class Range>
    void print(std::basic_ostream<CharT, Traits>& os, const Range& r) const {
        // 引数をキャプチャする
        typename boost::range_iterator<const Range>::type
            first(boost::begin(r)),
            last(boost::end(r));
        tuple<requirements, _os&, _iter, _iter> args(os, first, last);
        // そして実際の実装へ転送する
        do_print(get<0>(args), get<1>(args), get<2>(args));
    }
    virtual ~abstract_printer() {}
protected:
    // print引数のコンセプト要件を定義し
    // any型をtypedefする
    typedef boost::mpl::vector<
        base_and_derived<std::ios_base, _os>,
        ostreamable<_os, _t>,
        ostreamable<_os, const char*>,
        forward_iterator<_iter, const _t&>,
        same_type<_t, forward_iterator<_iter, const _t&>::value_type>
    > requirements;
    typedef boost::type_erasure::any<requirements, _os&> ostream_type;
    typedef boost::type_erasure::any<requirements, _iter> iterator_type;
    // do_print - このメソッドは派生クラスで定義されなければならない。
    virtual void do_print(
        ostream_type os, iterator_type first, iterator_type last) const = 0;
};

// separator_printer - シーケンスの要素を固定文字列で区切って書き出す
//   例えばもしseparatorが", "ならばseparator_printerはカンマ区切りリストを出力する
class separator_printer : public abstract_printer {
public:
    explicit separator_printer(const std::string& sep) : separator(sep) {}
protected:
    virtual void do_print(
        ostream_type os, iterator_type first, iterator_type last) const {
        if(first != last) {
            os << *first;
            ++first;
            for(; first != last; ++first) {
                os << separator.c_str() << *first;
            }
        }
    }
private:
    std::string separator;
};

// column_separator_printer - separator_printerに似ていて、n要素ごとに改行を挿入する
class column_separator_printer : public abstract_printer {
public:
    column_separator_printer(const std::string& sep, std::size_t num_columns)
      : separator(sep),
        cols(num_columns)
    {}
protected:
    virtual void do_print(
        ostream_type os, iterator_type first, iterator_type last) const {
        std::size_t count = 0;
        for(; first != last; ++first) {
            os << *first;
            boost::type_erasure::any<requirements, _iter> temp = first;
            ++temp;
            if(temp != last) {
                os << separator.c_str();
            }
            if(++count % cols == 0) {
                os << "\n";
            }
        }
    }
private:
    std::string separator;
    std::size_t cols;
};

// aligned_column_printer - シーケンスの列を縦方向に整形する
//   たとえば { 1, 2, 3, 4, 5 } というシーケンスが与えられると
//   aligned_column_printer はこのようにプリントする
//   1   4
//   2   5
//   3
class aligned_column_printer : public abstract_printer {
public:
    aligned_column_printer(std::size_t column_width, std::size_t num_columns)
      : width(column_width),
        cols(num_columns)
    {}
protected:
    virtual void do_print(
        ostream_type os, iterator_type first, iterator_type last) const
    {
        if(first == last) return;
        std::vector<iterator_type> column_iterators;

        // 最大列数を見つける
        std::size_t count = 0;
        for(iterator_type iter = first; iter != last; ++iter) {
            ++count;
        }
        std::size_t rows = (count + cols - 1) / cols;
        count = 0;
        for(iterator_type iter = first; iter != last; ++iter) {
            if(count % rows == 0) {
                column_iterators.push_back(iter);
            }
            ++count;
        }

        iterator_type last_col = column_iterators.back();

        // 完全な行をプリントする
        while(column_iterators.back() != last) {
            for(std::vector<iterator_type>::iterator
                iter = column_iterators.begin(),
                end = column_iterators.end(); iter != end; ++iter)
            {
                static_cast<std::ios_base&>(os).width(width);
                os << **iter;
                ++*iter;
            }
            os << "\n";
        }

        // 最後の列がない行をプリントする
        column_iterators.pop_back();
        if(!column_iterators.empty()) {
            while(column_iterators.back() != last_col) {
                for(std::vector<iterator_type>::iterator
                    iter = column_iterators.begin(),
                    end = column_iterators.end(); iter != end; ++iter)
                {
                    static_cast<std::ios_base&>(os).width(width);
                    os << **iter;
                    ++*iter;
                }
                os << "\n";
            }
        }
    }
private:
    std::size_t width;
    std::size_t cols;
};

int main() {
    int test[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    separator_printer p1(",");
    p1.print(std::cout, test);
    std::cout << std::endl;
    column_separator_printer p2(",", 4);
    p2.print(std::cout, test);
    std::cout << std::endl;
    aligned_column_printer p3(16, 4);
    p3.print(std::cout, test);
}

型安全なprintf
(この節のコード例のソースはprintf.cppを参照のこと)

この例では型安全なprintfを実装するために本ライブラリを使用している。

この例はC++11の機能を使用している。動かすためには比較的新しいコンパイラを使用する必要がある。

#include <boost/type_erasure/builtin.hpp>
#include <boost/type_erasure/operators.hpp>
#include <boost/type_erasure/any_cast.hpp>
#include <boost/type_erasure/any.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/io/ios_state.hpp>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <string>

namespace mpl = boost::mpl;
using namespace boost::type_erasure;
using namespace boost::io;

// 引数を参照でキャプチャし、各々がストリーム出力操作を提供しなければならない他は何も要求しない。
typedef any<
    mpl::vector<
        typeid_<>,
        ostreamable<>
    >,
    const _self&
> any_printable;
typedef std::vector<any_printable> print_storage;

// 実装関数の先行宣言
void print_impl(std::ostream& os, const char * format, const print_storage& args);

// print
//
// 古典的なCのprintf関数のようにストリームに値を書き出す。
// 引数は以下のパターンにマッチするフォーマット文字列内の修飾子に基づいてフォーマットされる
//
// '%' [ argument-number '$' ] flags * [ width ] [ '.' precision ] [ type-code ] format-specifier
//
// このフォーマット文字列内のほかの文字はそのままストリームに書き出される。
// このシーケンスに加えて、"%%" は'%'文字を出力するために使用できる。
// 各要素の詳細は以下のとおりである。
//
// argument-number:
//   値は 1 から sizeof... T の間でなければならない。これはフォーマットする引数の
//   インデックスを表す。インデックスが指定されていない場合に引数は
//   順番に処理される。もしインデックスが一つの引数に指定された場合は
//   他のすべての引数についても指定しなければならない。
//
// flags:
//   以下の文字の零個以上の組み合わせから構成される。
//   '-': Left justify the argument
//   '+': Print a plus sign for positive integers
//   '0': Use leading 0's to pad instead of filling with spaces.
//   ' ': If the value doesn't begin with a sign, prepend a space
//   '#': Print 0x or 0 for hexadecimal and octal numbers.
//
// width:
//   プリントする最小幅を表す。これは整数または一つの'*'のどちらでもよい。
//   アスタリスクは次の引数(これはint型でなければならない)をwidthとして
//   読み取ることを意味する。
//
// precision:
//   数値の引数においてはプリントする桁数を指定する。
//   文字列(%s)において precision はプリントする最大文字数を指定する。
//   長い文字列は切りつめられるだろう。widthと同様にこれは整数もしくは
//   一つの'*'を指定できる。アスタリスクは次の引数(これはint型でなければ
//   ならない)をwidth [訳注:precisionの間違いと思われる]として読み取るこ
// とを意味する。もしwidthとprecisionの両方が'*'として指定された場合、
//   widthが先に読み込まれる。
//
// type-code:
//   これは無視されるがC printfとの互換性のために提供されている。
//
// format-specifier:
//   以下の文字の内何れか一文字でなければならない:
//   d, i, u: The argument is formatted as a decimal integer
//   o:       The argument is formatted as an octal integer
//   x, X:    The argument is formatted as a hexadecimal integer
//   p:       The argument is formatted as a pointer
//   f:       The argument is formatted as a fixed point decimal
//   e, E:    The argument is formatted in exponential notation
//   g, G:    The argument is formatted as either fixed point or using
//            scientific notation depending on its magnitude
//   c:       The argument is formatted as a character
//   s:       The argument is formatted as a string
//
template<class... T>
void print(std::ostream& os, const char * format, const T&... t)
{
    // 引数をキャプチャする
    print_storage args = { any_printable(t)... };
    // そして実際の実装へ転送する
    print_impl(os, format, args);
}

// このprintのオーバーロードはストリームが指定されない場合にstd::coutへ書き出す。
template<class... T>
void print(const char * format, const T&... t)
{
    print(std::cout, format, t...);
}

// ここからの実装は分割コンパイルが可能である。

// 整数をパースする単項関数
int parse_int(const char *& format) {
    int result = 0;
    while(char ch = *format) {
        switch(ch) {
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            result = result * 10 + (ch - '0');
            break;
        default: return result;
        }
        ++format;
    }
    return result;
}

// printf 実装 
void print_impl(std::ostream& os, const char * format, const print_storage& args) {
    int idx = 0;
    ios_flags_saver savef_outer(os, std::ios_base::dec);
    bool has_positional = false;
    bool has_indexed = false;
    while(char ch = *format++) {
        if (ch == '%') {
            if (*format == '%') { os << '%'; continue; }

            ios_flags_saver savef(os);
            ios_precision_saver savep(os);
            ios_fill_saver savefill(os);

            int precision = 0;
            bool pad_space = false;
            bool pad_zero = false;

            // 引数インデックスをパースする
            if (*format != '0') {
                int i = parse_int(format);
                if (i != 0) {
                    if(*format == '$') {
                        idx = i - 1;
                        has_indexed = true;
                        ++format;
                    } else {
                        os << std::setw(i);
                        has_positional = true;
                        goto parse_precision;
                    }
                } else {
                    has_positional = true;
                }
            } else {
                has_positional = true;
            }

            // フォーマット修飾子をパースする 
            while((ch = *format)) {
                switch(ch) {
                case '-': os << std::left; break;
                case '+': os << std::showpos; break;
                case '0': pad_zero = true; break;
                case ' ': pad_space = true; break;
                case '#': os << std::showpoint << std::showbase; break;
                default: goto parse_width;
                }
                ++format;
            }

        parse_width:
            int width;
            if (*format == '*') {
                ++format;
                width = any_cast<int>(args.at(idx++));
            } else {
                width = parse_int(format);
            }
            os << std::setw(width);

        parse_precision:
            if (*format == '.') {
                ++format;
                if (*format == '*') {
                    ++format;
                    precision = any_cast<int>(args.at(idx++));
                } else {
                    precision = parse_int(format);
                }
                os << std::setprecision(precision);
            }

            // 型指定子をパース(と無視)する
            switch(*format) {
            case 'h': ++format; if(*format == 'h') ++format; break;
            case 'l': ++format; if(*format == 'l') ++format; break;
            case 'j':
            case 'L':
            case 'q':
            case 't':
            case 'z':
                ++format; break;
            }

            std::size_t truncate = 0;

            // フォーマット記号をパースする
            switch(*format++) {
            case 'd': case 'i': case 'u': os << std::dec; break;
            case 'o': os << std::oct; break;
            case 'p': case 'x': os << std::hex; break;
            case 'X': os << std::uppercase << std::hex; break;
            case 'f': os << std::fixed; break;
            case 'e': os << std::scientific; break;
            case 'E': os << std::uppercase << std::scientific; break;
            case 'g': break;
            case 'G': os << std::uppercase; break;
            case 'c': case 'C': break;
            case 's': case 'S': truncate = precision; os << std::setprecision(6); break;
            default: assert(!"Bad format string");
            }

            if (pad_zero && !(os.flags() & std::ios_base::left)) {
                os << std::setfill('0') << std::internal;
                pad_space = false;
            }

            if (truncate != 0 || pad_space) {
                // これらはstd::setwで取り扱うことができない。stringstreamに書き出して
                // pad/truncate を手動で行う。
                std::ostringstream oss;
                oss.copyfmt(os);
                oss << args.at(idx++);
                std::string data = oss.str();

                if (pad_space) {
                    if (data.empty() || (data[0] != '+' && data[0] != '-' && data[0] != ' ')) {
                        os << ' ';
                    }
                }
                if (truncate != 0 && data.size() > truncate) {
                    data.resize(truncate);
                }
                os << data;
            } else {
                os << args.at(idx++);
            }

            // positionalとindexed argumentsをフォーマット文字列内で同時に持つことはできない。
            assert(has_positional ^ has_indexed);

        } else {
            std::cout << ch;
        }
    }
}

int main() {
    print("int: %d\n", 10);
    print("int: %0#8X\n", 0xA56E);
    print("double: %g\n", 3.14159265358979323846);
    print("double: %f\n", 3.14159265358979323846);
    print("double: %+20.9e\n", 3.14159265358979323846);
    print("double: %0+20.9g\n", 3.14159265358979323846);
    print("double: %*.*g\n", 20, 5, 3.14159265358979323846);
    print("string: %.10s\n", "Hello World!");
    print("double: %2$*.*g int: %1$d\n", 10, 20, 5, 3.14159265358979323846);
}

複数のシグネチャを持つBoost.Function
(この節のコード例のソースはmultifunction.cppを参照のこと)

この例では複数のシグネチャをサポートするBoost.Functionの拡張を実装する。

この例はC++11の機能を使用している。動かすためには比較的新しいコンパイラを使用する必要がある。

#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/builtin.hpp>
#include <boost/type_erasure/callable.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/variant.hpp>
#include <boost/phoenix/core.hpp>
#include <boost/phoenix/operator.hpp>
#include <boost/range/algorithm.hpp>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>

namespace mpl = boost::mpl;
using namespace boost::type_erasure;
namespace phoenix = boost::phoenix;

// まず初めにmultifunctionテンプレートを宣言する。
// multifunction は Boost.Function に似ているがこれがひとつのシグネチャしか
// 取れないのに対して、いくつでもシグネチャを取ることができる。
template<class... Sig>
using multifunction =
    any<
        mpl::vector<
            copy_constructible<>,
            typeid_<>,
            relaxed,
            callable<Sig>...
        >
    >;

// variantを処理するために multifunction を使ってみよう。
// 簡単な再帰variantを定義することからはじめる。
typedef boost::make_recursive_variant<
    int,
    double,
    std::string,
    std::vector<boost::recursive_variant_> >::type variant_type;
typedef std::vector<variant_type> vector_type;

// ここでvariantの末端ノードに対して演算可能なmultifunctionを定義する。
typedef multifunction<void(int), void(double), void(std::string)> function_type;

class variant_handler
{
public:
    void handle(const variant_type& arg)
    {
        boost::apply_visitor(impl, arg);
    }
    void set_handler(function_type f)
    {
        impl.f = f;
    }
private:
    // boost::apply_visitorと組み合わせて使うクラス
    struct dispatcher : boost::static_visitor<void>
    {
        // 末端用 
        template<class T>
        void operator()(const T& t) { f(t); }
        // vector用、再帰的に要素を演算する
        void operator()(const vector_type& v)
        {
            boost::for_each(v, boost::apply_visitor(*this));
        }
        function_type f;
    };
    dispatcher impl;
};

int main() {
    variant_handler x;
    x.set_handler(std::cout << phoenix::val("Value: ") << phoenix::placeholders::_1 << std::endl);

    x.handle(1);
    x.handle(2.718);
    x.handle("The quick brown fox jumps over the lazy dog.");
    x.handle(vector_type{ 1.618, "Gallia est omnis divisa in partes tres", 42 });
}

コンセプトの定義
コンセプトはanyに格納される型の制約の集合を定義するものである。
コンセプトには三種類ある。

  1. 本ライブラリは事前定義された多くのコンセプトを持っている。これら大半はユーザー定義のコンセプトと同等だが、幾つかのものは特別な扱いを要求する。
  2. ユーザーは自身のプリミティブなコンセプトを以下に示すように定義できる。マクロBOOST_TYPE_ERASURE_MEMBERとBOOST_TYPE_ERASURE_FREEはこの形式のコンセプトを定義する。
  3. 全ての要素にコンセプトを持つMPL Forward シーケンスはこれもまたコンセプトである。これはコンセプトの合成を容易にする。

全てのプリミティブなコンセプトはそれぞれ一つの関数を定義する。プリミティブなコンセプトはクラステンプレートを特殊化してapplyという名前のstaticメンバ関数を持たなければならない。これはcallによってこの関数がディスパッチされるときに実行される。このテンプレートはテンプレート型パラメータのみ受け取ることができ、非型テンプレートパラメータやテンプレート・テンプレートパラメータは許可されない。

コンセプトのこのテンプレートパラメータはplaceholderを含むかもしれない。以下のことが考えられる。

  • それぞれのテンプレート引数は cv修飾 かつ/または 参照修飾 されたplaceholder型かもしれない。
  • もしテンプレート引数が関数型ならばその引数と戻り値型はcv/参照 修飾されたplaceholderかもしれない。

これ以外のplaceholderは無視される。

コンセプトはanyを生の値から構築するかbindingを構築することによりインスタンス化される。コンセプトが特定の型バインディングの集合でインスタンス化されるときそれぞれのplaceholderは非cv修飾で非参照な型へバインドされる。テンプレート引数リスト内のplaceholderがバインドされる型にそれぞれ置き換えられた後、以下のことが守られる。

  • バインドされるコンセプトのapplyの引数の数はバインド元のコンセプトの引数と同じでなければならない。
  • バインドされるコンセプトのapplyの引数と戻り値はバインド元のコンセプトの対応する引数と戻り値から以下のように継承できる。もしバインド元のコンセプトの引数が任意のcvと参照修飾子を持つplaceholderならば、バインドされるコンセプトの引数はplaceholderを置き換える事ができる。それ以外の場合、バインド元のコンセプトの引数はバインドされるコンセプトの引数と同じでなければならない。
// 正しい。
template<class T = _self>
struct foo1 {
  static void apply(const T& t) { t.foo(); }
};

// 間違い。applyのシグネチャがプライマリテンプレートのものと異なる。
template<>
struct foo1<int> {
  static void apply(int i);
};

// 間違い。コンセプトはテンプレートでなければならない。 
struct foo2 {
  static void apply(const _self&);
};

// 間違い。applyはstaticでなければならない。
template<class T = _self>
struct foo3 {
  void apply(const T&);
};

// 間違い。applyはオーバーロードできない。
template<class T = _self>
struct foo3 {
  static void apply(T&);
  static void apply(const T&);
};

// 間違い。最上位のplaceholdersしか見つからない。
template<class T>
struct foo4;
template<class T>
struct foo4<boost::mpl::vector<T> > {
  static void apply(const T&);
};

// 間違い。テンプレートのテンプレートパラメータは許可されない。
template<template<class> class T>
struct foo5
{
    static void apply(T<int>&);
};

事前定義されたコンセプト
下の表においてTとUは演算を適用する型である。Rは戻り値である。Tの規定値は常にanyのデフォルトの挙動に適合する_selfである。これらのコンセプトは普通のセマンティックスを想定している。つまり比較オペレータは常にboolを返し、引数と戻り値には適切に参照が追加される。

注意書きされている場合を除いて、本ライブラリによって定義されたプリミティブコンセプトはコンセプトマップを提供するために特殊化することができる。copy_constructibleやイテレータコンセプトは合成されているので特殊化することはできない。 constructible, destructible, typeid_, same_typeはライブラリ内で特別な扱いを要求するため特殊化できない。

表 33.1. 特殊メンバ

concept notes
constructible<Sig> -
copy_constructible<T> -
destructible<T> -
assignable<T, U = T> -
typeid_<T> -

表 33.2. 単項演算子

operator concept notes
operator++ incrementable<T> 独立した後置インクリメントはない
operator-- decrementable<T> 独立した後置ディクリメントはない
operator* dereferenceable<R, T> Rは一般に参照であるべき
operator~ complementable<T, R = T> -
operator- negatable<T, R = T> -

表 33.3 二項演算子

operator concept notes
operator+ addable<T, U = T, R = T> -
operator- subtractable<T, U = T, R = T> -
operator* multipliable<T, U = T, R = T> -
operator/ dividable<T, U = T, R = T> -
operator% modable<T, U = T, R = T> -
operator& bitandable<T, U = T, R = T> -
operator| bitorable<T, U = T, R = T> -
operator^ bitxorable<T, U = T, R = T> -
operator<< left_shiftable<T, U = T, R = T> -
operator>> right_shiftable<T, U = T, R = T> -
operator== and != equality_comparable<T, U = T> !=は==を用いて実装される
operator<, >, <=, and >= less_than_comparable<T, U = T> すべては<から実装される
operator+= add_assignable<T, U = T> -
operator-= subtract_assignable<T, U = T> -
operator*= multiply_assignable<T, U = T> -
operator/= divide_assignable<T, U = T> -
operator%= mod_assignable<T, U = T> -
operator&= bitand_assignable<T, U = T> -
operator|= bitor_assignable<T, U = T> -
operator^= bitxor_assignable<T, U = T> -
operator<<= left_shift_assignable<T, U = T> -
operator>>= right_shift_assignable<T, U = T> -
operator<< ostreamable<Os = std::ostream, T = _self> -
operator>> istreamable<Is = std::istream, T = _self> -

表 33.4 その他の演算子

operator concept notes
operator() callable<Sig, T> Sigは関数型でなければならない. Tはconst修飾されるかも知れない
operator[] subscriptable<R, T, N = std::ptrdiff_t> Rは一般に参照になるべき。Tはconst修飾することもできる。

表 33.5 イテレータコンセプト

concept notes
iterator<Traversal, T, Reference, Difference> イテレータの値型を制御するにはsame_typeを使用する。
forward_iterator<T, Reference, Difference> -
bidirectional_iterator<T, Reference, Difference> -
random_access_iterator<T, Reference, Difference> -

表 33.6 特殊なコンセプト

concept notes
same_type<T> 二つの型が同じ型であることを示す

リファレンス
(原文へ)


根拠

なぜデストラクタの存在を明示的に指定しなければならないのか?
参照を使用するときにはデストラクタは不要である。暗黙的に指定しないことでprivateまたはprotectedなデストラクタを持つ型を参照でキャプチャすることが可能になる。一貫性のために値でキャプチャするときも同様に指定しなければならない。

なぜ非メンバ関数なのか?
anyのメンバはカスタマイズされうる。フリー関数を使用することによって我々はユーザーがしたいことを何も邪魔しないことを保障できる。

なぜplaceholderが_a,_bという名前で_1,_2ではないのか
本ライブラリの初期バージョンでは_a,_bなどの代わりに_1,_2などの名前を使用してた。これは一定の混乱を引き起こす。なぜなら番号のplaceholderはすでに幾つかのBoost/Std Bind, Boost.Phoenix, Boost.MPLを含む他のライブラリにおいて少し違った別の意味で使用されているからである。私は最終的にこのplaceholderは位置パラメータよりも名前つきパラメータに相当しているため、番号よりも文字の方がより適切だと判断した。

参照にboost::refを使ったらどうか?
Boost.Functionはboost::refを使うことにより関数オブジェクトに参照を格納することを可能にする。しかしながら、一般的なケースでは参照と値を同じ方法で扱うことは一貫性に欠けた理解するのが難しい挙動を引き起こす。もしBoost.TypeErasureがこのように参照を扱った場合、あなたがanyをコピーしたとき新しいオブジェクトが本当の複製なのか同じ基礎オブジェクトへの単なる新しい参照なのかどうか見分けがつかなくなる。Boost.Functionはこのことを考えなくても良い。なぜならば格納された関数オブジェクトに対する変更を伴う操作はなにも公開されないからだ。

他に提案されていた方法は初回のみ参照を維持するものだ。

int i = 2;
any x = ref(i);
any y = x; // makes a copy

残念ながらこれは全ての利用法をカバーできない。このような参照を関数から返す信頼できる方法がないのだ。加えて望む望まないにかかわらず参照かどうかを追跡するフラッグを追加する必要がありオーバーヘッドが加わる。(これをvtableの"clone"メソッドに保持する代替方法はBoost.TypeErasureが使用する分離されたvtableを考えると実装が途方もなく複雑になる上依然としてオーバーヘッドがある。)

将来
幾つかのアイディアがある。これらが将来実装される保障はどこにもない。

  • SBOの使用
  • vtableのレイアウトについてより細かい制御を可能にする
  • 変換の際にサブテーブルの再利用を試みる
  • "dynamic_cast"を可能にする。これはコンセプトマッピングの大域レジストリの作成を要求する。
  • コンパイル時間コストの最適化

謝辞
anyという名前と私のplaceholderシステムの祖先はAlexander Nasonov作の DynamicAnyによる。

レビューマネージャのLorenzo Caminitiと正式レビューに参加してくれた全ての皆さんに感謝する。

Christophe Henry
Paul Bristow
Karsten Ahnert
Pete Bartlett
Sebastian Redl
Hossein Haeri
Trigve Siver
Julien Nitard
Eric Niebler
Fabio Fracassi
Joel de Guzman
Alec Chapman
Larry Evans
Vincente J. Botet Escriba
Marcus Werle
Andrey Semashev
Dave Abrahams
Thomas Jordan

関連するもの
似たライブラリが幾つか存在する。私の知る限り3つある。

  • Boost.Interfaces by Jonathan Turkanis
  • Adobe Poly
  • Boost.dynamic_any by Alexander Nasonov