2015-02-23

C++標準 2015-02 mid-meetingのレビュー: N4370-N4380

N4370: Networking Library Proposal (Revision 4)

Boost.Asioを土台にしたネットワークライブラリの提案。

N4371: Minimal incomplete type support for standard containers, revision 2

一部のSTLのコンテナーの要素型に不完全型をサポートする提案。

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

struct Entry
{
    std::vector<Entry> v ;
} ;

クラスは、定義の終了である}を持って、完全型になる。クラス定義内ではまだ不完全型である。したがって、vectorにテンプレート実引数に渡す時点では、まだ不完全型なのだ。これをサポートするかどうかは、これまで規格上は未規定だったのだが、この提案では、vector, list, forward_listに限ってはサポートするとしている。

これ以外のコンテナーについては、更に議論をして緩和していくという。

N4372: A Proposal to Add a Const-Propagating Wrapper to the Standard Library

クラスのデータメンバーがポインターの場合、ポインターの参照先は、constメンバー関数からでも変更できる。


struct A
{
    void f() ;
    void f() const ;
} ;

class B
{
    std::unique_ptr<A> ptr ;

public :
    // コンストラクターやデストラクターなど

    void f()
    {
        ptr->f() ; // 非const版が呼ばれる
    }

    void f( ) const
    {
       ptr->f() ; // 非const版が呼ばれる
    }
} ;

これは完全に規格通りの挙動だが、意味的にはここでconst版が呼ばれて欲しい。そこで、そのようなconst性を伝播させるライブラリ、propagate_constを提案している。


class B
{
    std::propagate_const< std::unique_ptr<A> > ptr ;

public :
    void f()
    {
        ptr->f() ; // 非const版が呼ばれる
    }

    void f( ) const
    {
       ptr->f() ; // const版が呼ばれる
    }
} ;

N4373: Atomic View

非atomicオブジェクトをatomic操作できるラッパーライブラリ、atomic_viewの提案。

// 並列実行される何らかのタスク
void spawn_task( std::experimental::atomic_array_view<int> v ) ;

void f( int * ptr, std::size_t size )
{
    // 非atomic操作
    std::for_each( ptr, ptr + size, []( auto & elem ) { elem = 0 ; } ) ;

    {
        std::experimental::atomic_array_view<int> v( ptr, size ) ;

        // vを経由してしか操作できない。

        spawn_task( v ) ;
        spawn_task( v ) ;
        spawn_task( v ) ;
    // vが破棄される
    }

    // これ以降、配列に直接操作が可能
}

atomic_array_view<T>は、既存の非アトミック型の配列をラップして、アトミックな操作ができるようにしてくれる。atomic_array_viewでラップする前に起こったアクセスは、atomic_array_viewのコンストラクターの実行が完了する前に起こる(happens before)。

atomic_array_viewのオブジェクトが存在する間は、配列に直接アクセスすることはできず、atomic_array_viewのオブジェクトを通してしかアクセスできない。

atomic_array_viewはコピーすることができる。その際には、アトミック操作を実現するためのロックなどのリソースが、もしあれば、共有される。最後のatomic_array_viewのオブジェクトが破棄された後に、元の配列は直接アクセスすることが出来るようになる。

配列ではなく単一のオブジェクトに対するarray_viewである、atomic_concurrenty_view<T>も存在する。

これらのatomic_viewには、二つの利用方法が想定されている。ひとつには、High Performance Computing用途で、巨大な配列を、まず競合しない方法で初期化し、次に並列に変更し、その後に競合しない方法で読み書きを行うような処理に使える。

もうひとつは、既存のコードで非atomicな型を使っていて、atomic<T>に置き換えるコストが現実的ではない場合に使うことができる。

N4374: Linux-Kernel Memory Model

これまで、Linuxカーネルのメモリーモデルは、memory-varriers.txtatomic_ops.txtにラフにドキュメント化されていた。これはLinuxカーネルの開発には用を為すが、厳密な規格を書き起こすには不適切である。N4374は、初めてLinuxカーネルのメモリーモデルを厳密にドキュメントする試みである。

Linuxカーネルのメモリーモデルがまとめられていて、C++との対応も考察されている。Linuxカーネルで使われている既存のメモリーモデルをC++コミュニティに紹介することで、今後の規格化に役立てる意図がある。

N4375: Out-of-Thin-Air Execution is Vacuous

オブジェクトへの並列アクセスによる競合により、本来現れるはずのない値が現れてしまう、Out Of Thin Air(OOTA) Effectの具体的な例と、それに酔ってもたらされる害悪を紹介した論文。

N4376: Use Cases for Thread-Local Storage

TLSは20年以上も使われている実績ある機能ではあるが、最近、SIMD畑とGPGPU畑の連中が、TLSの有用性に疑問を持っていて、会議でもそう主張している。この論文はTLSの有用性を解説している。

ただし、SIMDやGPGPUによる軽いスレッド風並列処理を行う際に、TLSは共有する実装がもっとも効率的であり、悩ましいところで、論文ではそこの考察も行っている。

[PDF] N4377: C++ Extensions for Concepts PDTS

Concept Liteのドラフト

N4378: Language Support for Contract Assertions

契約プログラミングのためのcontract_assertライブラリ。

contract_assertには、3種類のassertion levelが設定されている。min, on, maxだ。minは最小限、maxは最大限のレベルになっている。これ以外にも、完全に無効にするoffがある。assertion levelは、何らかの実装依存の方法で設定するようだ。レベルは、コンパイル時にpredefine macroで取得することができる。

#ifdef contract_assertion_level_max
#endif

これは、従来のNDEBUGのようなマクロに相当する。

contract_assertには、contract_assert_min/contract_assert_on/contract_assert_maxがある。それぞれ、レベルに対応していて、そのレベル以上の場合にチェックが走る。contract_assertマクロは、contract_assert_onと同じだ。

void * contract_memcpy( void * dest, const void * src, size_t n )
{
    // 軽い契約チェック
    // nullポインターではないかどうか調べる
    contract_assert( dest != nullptr ) ;
    contract_assert( src != nullptr ) ;

    // 重たい契約チェック
    // 環境依存の方法を使って有効なメモリ領域かどうかを調べる
    contract_assert_max( platform_specific::is_valid_memory_area( dest, n ) ) ;
    contract_assert_max( platform_specific::is_valid_memory_area( src, n ) ) ;

    char * d = static_cast<char *>(dest) ;
    char const * s = static_cast<char const *>(src) ;
    char const * const end = s + n ;

    for ( ; s != end ; ++s, ++d )
    {
        *d = *s ;
    }

    // 重たい契約チェック
    // 宇宙線やハードウェア破損などの可能性を考慮
    contract_assert_max( std::memcmp( dest, src, n ) == 0 ) ;

    return dest ;
}

契約違反時には、ハンドラーが呼ばれる。このハンドラーは呼び出し元にreturnしてはならない。デフォルトのハンドラーは、std::abortを呼び出す。set_contract_violation_handlerでカスタムハンドラーを設定できる。


int main()
{
    std::experimental::set_contract_violation_handler(
        []( std::experimental::contract_violation_info & info )
        {
// assertionレベルを表すenum値
// enum class contract_assertion_level { min, on, max };
            auto level = info.level ;

// contract_assertの式の文字列
// contract_assert( is_okay(x) ) ;の場合、"is_oaky(x)"
// phase 3なので、プリプロセッサーマクロ展開前の文字列が得られる
// 複数の連続した空白文字はスペース文字ひとつになる。
            std::cout << info.expression_text << '\n' ;

// contract_assertが存在するソースファイルの__FILE__
            std::cout << info.filename << '\n' ;

// contract_assertが存在するソースファイルの__LINE__に相当
            std::cout << info.line_number << '\n' ;

            // ハンドラーは呼び出し元に戻ってはならない
            std::abort() ;

        } ) ;

}

contract_violation_infoクラスのメンバーの中でも、expression_textが興味深い。Phase of translationのphase 3の文字列なので、マクロも展開されない。また、複数の連続した空白文字(スペース、改行、水平タブ、垂直タブ、ラインフィード)は、スペースひとつに変換される。

#define identity(x) x

// "identity ( x ) "
contract_assert(
    identity
    (
        x
    )
) ;

C++としてはだいぶ頑張ったようだ。

[PDFうざい] N4379: FAQ about N4378, Language Support for Contract Assertions

contract_assertに対するFAQ集。

興味深いものを紹介すると・・・

contract_assertはコンパイル時チェックやデッドコード除去、最適化、静的解析用途にも使えるように設計されている。

どうやってデッドコード除去を行うのか?

コンパイラーにとってasssert式とシンボルを関連付けるのはお手の物だ。

なぜ関数本体でしか使えないのか?

それ以上のものは、全く経験のないまったく新しい文法や機能を必要とする。そこまで冒険したくない。

N4380: Constant View: A proposal for a std::as_const helper function template

<utility>にstd::as_constの提案。

int main()
{
    std::string text("text") ;
    std::string const & const_ref = std::as_const(text) ;

    // std::string::const_iterator
    auto iter = std::as_const(text).begin() ;
}

as_const(obj)は、以下のように実装できる

template< typename T >
inline typename std::add_const< T >::type &
as_const( T &t ) noexcept
{
    return t;
}

as_constの提案理由としては、const版と非const版の同名のメンバー関数がある場合、const版のメンバー関数を明示的に呼び出すことだ。


int main()
{
    std::string s ;
    // std::string::iterator
    auto iter1 = s.begin() ;
    // std::string::const_iterator
    auto iter2 = std::as_const(s).begin() 
}

cbeginはこの目的のためにあるが、すべてのクラスに存在するわけではない。const版のメンバー関数を明示的に呼ぶ際に、const_cast< std::add_const< decltype( object ) >::type & >( object )と書くより楽になる。

xvalueやprvalueをサポートすることに意義があるかという点について、議論が分かれている。

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

どうやら、ドワンゴ社内に競技プログラミング部なるものができるそうだ。

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

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

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

1 comment:

Anonymous said...

やーっとネットワークライブラリ入るんですか。待ってマース。
不完全型ってダメだったんですね。不完全型を使った、ベクターとシェアードオブジェクト実装のNツリー考えてたのでうれしいです。
as_constって地味に便利かもしれない。
自分動けばいい人なんで契約関係の機能は入ってほしいですけど、あんまり好きではないです。自分には必要なのではあるのですが・・・。