本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

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