2015-10-29

C++標準化委員会の文書のレビュー: P0040R0-P0048R0

P0040R0: Extending memory management tools

イテレーターを取り、イテレーターの範囲の未初期化のストレージに対して、構築や破棄をする関数destroy, uninitialized_move, uninitialized_move_n, uninitialized_value_construct, uninitialized_default_constructの追加。

たとえば、destroyは以下のように実装できる。

template < typename ForwardIterator >
void destroy( ForwardIterator begin, ForwardIterator end )
{
    using type = iterator_traits<ForwardIterator>::value_type ;
    while ( begin != end )
    {
        begin->~type() ;
        ++begin ;
    }
}

P0041R0: Unstable remove algorithms

unstable_removeアルゴリズムの提案。

従来のeraseは、消した要素の穴を、後続の要素をシフトさせることによって埋める必要がある。これは、要素の順序を変えない安定した動作が要求されているためである。

std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ;

// v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
std::remove( begin(v), end(v), 2 ) ;
// v = { 1, 3, 4, 5, 6, 7, 8, 9, ? }   

要素を一つづつコピー/ムーブして穴を埋める動作は、要素の具体的な型にもよるが、コストがかかる。

ここで、もし要素の順序を変える不安定な動作が許されるのであれば、末尾の要素で埋めるなどして、コピー/ムーブの回数を減らせる。

// v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
std::unstable_remove( begin(v), end(v), 2 ) ;
// v = { 1, 9, 3, 4, 5, 6, 7, 8, ? }   

そこで、unstable_removeの提案となる。他にも、unstable_remove_if, unstable_remove_copy, unstable_remove_copy_ifも追加される。

提案では拡張案として、メンバー間数版であるeraseにもunstable_eraseを加える案が考察されている。

P0042R0: std::recover: undoing type erasure

function, any, variant, optionalなど、C++にはtype erasureを使ったライブラリが多い。問題は、ライブラリごとに元の型を得る方法がバラバラで、全く統一されていないということだ。たとえば、functionはtargetメンバー関数を使うし、anyはany_castというフリー関数を使う。variantはstd::getをオーバーロードしていて、std::optionalは、型が入っていないとbad_optional_access例外を投げる。

今後、type erasureを使うライブラリが増えるたびに、bad_library_name_accessなる例外クラスが増えるのでは、ますます標準ライブラリを肥大化させるだけだ。元の型を得るという同じような処理は、共通の汎用的な方法で扱えるべきだ。

そのために、この提案では、ErasureClassコンセプトを定義し、type erasureを行うクラスが実装すべきメンバーの要件を定義している。

そして、ErasureClassコンセプトを使って元の型を取り出すフリー関数recoverが追加される。

void f() { }

int main()
{
    function< void () > a = &f ;
    std::experimantal::any b = &f ;

    auto c = a->target() ;
    auto d = any_cast< void (*)() >( b ) ;
}

このようにバラバラの方法で取り出していたのが、

auto c = std::recover<void (*)()>(a) ; 
auto d = std::recover<void (*)()>(b) ; 

このように統一化された方法で取り出すことができるようになる。型チェックに失敗すると、bad_recovery_access例外が投げられる。

[PDF注意] P0043R0: Function wrappers with allocators and noexcept

functionをアロケーターに対応させるにあたって、type erasureを使ってアロケーターを格納する方法を採用した。これは様々な問題を生む。たとえばnoexceptにできなくなる。

そこで、functionの挙動変更と、ひとつのアロケーターオブジェクトを要素であるfunctionに渡すfunction_containerを提案している。

文章がやたらと不親切で読みにくい上に、提案している機能が不必要に複雑で、一般人が使いこなせるとは思えない。

[PDF注意] P0044R0: unwinding_state: safe exception relativity

uncaught_exceptionsをdeprecatedさせて、新たにunwinding_stateを導入する提案。

uncaught_exceptionは、ハンドルされていない例外が存在するかどうかをbool値で返す。つまり、例外がthrowされたことによるstack unwindingが発生しているかどうかを調べることができる。これは、デストラクターの挙動を変えるのに使える。

struct X
{
    ~X()
    {
        if ( std::uncaght_exception() )
        {
            // 通常の破棄
        }
        {
            // do_somethingが例外を投げたので
            // stack unwinding中
        }
    }
} ;

int main()
{
    try {
        X x ;
        do_something() ;
    } catch ( ... ) { }
}

問題は、stack unwindingの最中にも、例外スコープは作成されるということだ。

struct Y
{

    ~Y()
    {
        if ( std::uncaught_exceptions() )
        {
            try {
                X x ;
                do_recovery_thing() ;
            } catch( ... ) { }
        }
    }
}

この場合は、通常のtryスコープを抜けたxの破棄であっても、xのデストラクターはstack unwinding中であると認識してしまう。

これを防ぐために、uncaught_exceptionsが追加された。これは、現在ハンドルされていない例外の数をint値で返す。

問題は、未ハンドルの例外の数を返すということは、現在の未ハンドルの例外数をどこかに保存しておいて、それを比較しなければならないということだ。

struct X
{
private :
    static int e ; // 別のところで書き込まれる
public :
    X() : e( std::uncaught_exceptions() ) { }

    ~X()
    {
        if ( std::uncaught_exceptions() == e )
        {
            // 通常の破棄
        }
        else
        {
            // stack unwinding中
        }
    }

} ;

uncaught_exceptionsを正しく使うには、このように例外が発生する前に例外数を記録しておいて、例外が発生しているかもしれない箇所で、前回の例外数と異なるかどうかを比較しなければならないのだ。

そもそも、例外数を返すというのは、Itanimu ABIに例外数を記録するためのグローバル変数があるからだ。他の実装も考えられる。例えば、ARM EHABIは、現在未ハンドルの例外をリンクリストで管理することを規定している。

現在の例外数を取得することに需要はない。需要は、現在stack unwinding中かどうかを判定することだ。そのため、例外ハンドルの状況が変わったかどうかだけを調べる方法があればよい。

そこで、unwindling_stateクラスが提案されている。このクラスは、非staticメンバーのガードオブジェクトとして使うことができる。

struct X
{
private :
    std::unwinding_state s ;
public :

    ~X()
    {
        if ( 1s )
        {
            // 通常の破棄
        }
        else
        {
            // stack unwinding中
        }
    }

} ;

unwinding_stateのコンストラクターは、現在のstack unwindingの状態を記録する。operator boolは、現在のunwindingがオブジェクト構築時に保存したunwindingと同一かどうかをboolで返す。

operator boolはexplicitとなっているので、lvalueとして使わなければならない。以下のような誤った記述はできない。

if ( unwinding_state() )

コピーやムーブは普通にできる。

[PDF注意] P0045R0: Overloaded and qualified std::function

オーバーロード可能なfunctionの提案。既存のstd::functionにVariadic Templatesで複数のシグネチャを指定可能にするというもの。

以下のようなコードが書けるようになる。

int main()
{
    std::function< void ( int ) , void ( std::string ) > f ;

    f = []( int ) { } ;
    f = []( std::string ) { } ;

    f = []( auto ) { } ;

    f( 0 ) ;
    f( std::string("hello") ) ;
}

これを実現するために、target型ごとにグローバルなtupleを作ってディスパッチさせる、いわゆるvtableを実装している。正直ついていけない。

しかし思うのだが、コア言語にあるvtableが非効率的なので独自vtableを自作しましたというのはなんだか本末転倒ではないか。もちろん、通常の関数ポインターまで含めたtype erasureを、本来その目的に設計されていなかった組み込みvtableで実装するので、そういう非効率さが発生するのだが。

P0046R0: Change is_transparent to metafunction

連想コンテナーでHeterogeneous lookupを実現するために、比較関数にis_transparentというネストされた型があるかどうかをヒントにしてきたが、これは使いにくいので、permits_heterogenous_lookupというtraitsを追加する提案。

正直すごく使いづらい。

P0047R0: Transactional Memory (TM) Meeting Minutes 2015/06/01-2015/09/21

トランザクショナルメモリー会議の議事録

P0048R0: Games Dev/Low Latency/Financial Trading/Banking Meeting Minutes 2015/08/12-2015/09/23

ゲーム開発、低レイテンシー、アルゴリズム取引、銀行に関する会議の議事録。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

1 comment:

Anonymous said...

現在、anyかvariantを欲しています。
夢はオレオレスクリプトを構築することですが遠い夢です。
モックとしてBASIC IIを作りたいのですが、
どうにも標準ライブラリで手抜きするにはまだ尚早に思いますね。
実力がないのは重々承知ですが、このレベルでもデカいものが作れるようになってほしいです。