2016-03-07

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

P0063R1: C++17 should refer to C11 instead of C99

C++規格が参照するC言語規格をC99からC11にする変更。

C11の変更がC++とかち合う部分や、C++とCの文面を厳密に解釈した際の奇妙な結果が考察されていて面白い。t

P0067R1: Elementary string conversions, revision 1

整数と浮動小数点数を文字列と相互変換するライブラリの提案。

このライブラリはロケールのように実行時にフォーマットを切り替える機能がなく、かつ、動的メモリ確保も行わない。

用途は、国際化対応が必須ではないパフォーマンスが重要なテキストベースフォーマットのパース。例えば、JSONやXMLなど。

[PDF] P0072R1: Light-Weight Execution Agents

スレッドより制約がある実行単位(SIMDやGPGPUなど)を、実行媒体(execution agent)として扱うための定義を文面に追加する提案。

[PDF] P0073R1: On unifying the coroutines and resumable functions proposals

コルーチンとレジューム可能関数の統一に向けて。

[PDF] P0075R1: Template Library for Parallel For Loops

インデックスベースの並列forループアルゴリズムを追加する提案。

std::for_loop( std::seq, 0, n, [&]( auto i ){ A[i] = B[i] ;} ) ;

これは、以下と同じ意味だ。

for ( unsigned i = 0 ; i != n ; ++i )
    A[i] = B[i] ;

seqをparにすれば、並列実行版になる。

アルゴリズムには、通常のレンジ版と、カウント版がある。レンジ版は、開始インデックスと終了インデックスを取り、イテレーター風にHalf Open Rangeとして終了インデックスに到達するまでインデックスをすすめる。カウント版は、開始インデックスとイテレート回数を取る。インデックスは指定された回数だけ進められる。カウント版のアルゴリズムは、末尾が"_n"という名前になっている。

std::for_loop_n( i, n, f ) ;

は、

for ( unsigned count = 0 ; count != n ; ++count )
    f( ++i ) ;

のような意味になる。

またこの提案はstride版のアルゴリズムも提供する。これは末尾が"_strided"となっている。stride版はインデックスの刻み幅を指定できる。

std::for_loop_strided( i, n, m, f ) ;

は、

for ( auto I = i ; I != n ; I += m )
    f( I ) ;

のような意味になる。

Reductionのサポート。Reductionとは、ロックを使わずに一つの変数を並列に変更でき、最終的な値はシリアルに実行した場合と同じものを得る方法である。その仕組みは、並列実行には変数のviewを見せておき、複数のviewから最終的な値を計算する方法を提供することによって実現している。

float f(int n, float x[]]) {
    float s = 0;
    for_loop(par, 0, n, reduction(s,0.0f,std::plus<float>()),
        [&](int i, float& s_) {
            s_ += x[i] ;
        });
    return s;
}

この例では、変数sの最終的な値は、途中の変更をすべて加算することで得られるので、そのようなreductionを与えている。for_loopの最後の実引数は、Variable Templatesになっていて、reductionをいくつでも受け取ることができる。パラーメーターパックの最後が呼び出し可能な関数オブジェクトとなる。reductionを使った数だけ、関数オブジェクトの呼び出しに実引数が追加される。

inductionのサポート。inductionはループのイテレート回数に応じて線形に増える値である。そのような値をユーザーが手で計算すると間違いの元なので、ライブラリが用意されている。


float* zipper(int n, float* x, float *y, float *z) {
    for_loop(par, 0, n,
        induction(x),
        induction(y),
        induction(z,2),
        [&](int i, float* x_, float* y_, float* z_) {
            *z_++ = *x_++;
            *z_++ = *y_++;
        });
    return z;
}

inductionもreductionと同じく、いくつでも使える。使った数だけ実引数に渡される。上記のコードは、以下のコードと同じ意味である。


float* zipper(int n, float* x, float *y, float *z) {
    for_loop(par, 0, n,
        [&](int i) {
            *(z+i*2)++ = *(x+i)++;
            *(z+i*2)++ = *(y+i)++;
        });
    return z;
}

[PDF] P0076R1: Vector and Wavefront Policies

Parallerism TS(並列アルゴリズム)にベクトル実行ポリシー(SIMDやGPGPU)を追加したいが、単にpar_vecをシングルスレッドに誓言スルテイ土では、制限がゆるすぎてベクトル化できないので、制限を強めた2つの実行ポリシーを追加する提案。

P0077R1: is_callable, the missing INVOKE related trait

is_callable traitsの提案。関数型をテンプレート実引数に与えると、戻り値の型を引数で関数呼び出しできるかどうかを返してくれる。

void f( int, int ) ;

std::is_callable_v< f( int, int ) > ; // true
std::is_callable_v< f( int ) > ; // false

[PDF] P0082R1: For Loop Exit Strategies (Revision 2)

イテレート文を通常通り抜けた場合(条件がfalseになった場合)と、早期に抜けた場合(break)に実行される文を記述できるようにする提案。

ループを実行したあとで、ループが全部回ったのか、途中でbreakしたのかによって、処理を分けたいことがある。その場合、以下のように書かなければならない。

auto it = get_begin(. . .); // Unfortunate that ‘it’ has to be out here.
auto end = get_end(. . .); // Unfortunate that ‘end’ has to be out here.
for (; it != end; ++it)
{
    if (some_condition(*it)) break;
    do_something(*it);
}
if (it == end) // Extra test here.
    do_stuff();
else
    do_something_else(*it);

もし、ループの条件を二回評価できない状況では、以下のようにbreakで抜けたかどうかを保持する変数を書かなければならない。

bool early = false;
while (some_condition())
{
    . . .
    if (test1()) { early = true; break; }
    . . .
    if (test2()) { early = true; break; }
    . . .
    if (test3()) { early = true; break; }
    . . .
}
if (early)
{ . . . }
else
{ . . . }

range-based forの場合はもっと悲惨だ。ループが途中で中断された時のイテレーターが欲しい場合、以下のように書かなければならない。

something_t last; // Extra construction here.
bool early = false;
for (auto&& element : container)
{
    if (some_condition(element))
    {
        last = element; // Extra copy here
        early = true;
        break;
    }
    do_something(element);
}
if (early)
    do_something_else(last);
else
    do_stuff();

そのため、この文書は、イテレート文から条件がfalseになって抜けたが、breakで抜けたかによって実行される文を記述する文法を提案している。


for ( unsigned i = 0 ; i != 10 ; ++i )
{
    if ( !do_something( i ) )
        break ;
}
catch default
{
    do_normal_exit_thing() ;
}
catch break
{// for文で宣言した変数を使うことができる
    do_break_exit_thing(i) ;
}

前回からの変更は文法だ。if forというわかりにくい文法から、catch default, catch breakというわかりやすい文法になった。

これはたまに欲しくなる。

P0088R1: Variant: a type-safe union (v6).

型安全なunion風ライブラリ、variantの提案。

variantは無効な状態、空の状態を許容するかどうかで議論がもめている。

[PDF] P0089R1: On Quantifying Memory-Allocation Strategies (Revision 2)

グローバルアロケーターのかわりにローカルアロケーターを使うことでどのような状況がパフォーマンスの向上につながるのかを検証した文書。

P0091R1: Template argument deduction for class templates (Rev. 4)

コンストラクターによるテンプレートの実引数推定の提案。

tuple<int, double> t{ 1, 2.3} ;

tuple t{ 1, 2.3 } ;

と書けるようになる。

P0096R1: Feature-testing recommendations for C++

C++の機能テストマクロをC++17に対応させる文書。

C++17に入る変更点の一覧にもなっている。

ドワンゴ広告

今日は社内で筆者のask.fmコーパスを用いて機械学習し、文章が質問かどうかを判定するプログラムの作成を試みた社員がいた。結果は、あまり精度がよくなかった。

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

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

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

2 comments:

Anonymous said...

今回はなかなかいいのが揃ってますね。
C++が参照するC規格を更新するとは思ってませんでしたが、かなり前衛的でよろしいと思います。
いわゆる、レキシカルキャストはほしかったのでさっさと入ってほしいです。
並列版ループはちょっと雑ですけど、ほかに案もないですしねぇ。
for文のブレークは結構めんどくさいですよね。特に2重ループのブレークはどうやって伝達するか非常に悩みます。まぁ、結局GOTO使うんですけど。ちょっとやりたくないです。今回の提案は悪くないです。
variantについては、ポインタ同様、空の状態を許容しつつ、異常な状態というのは内部的にどうにかすればいいと思いますよ。とりあえず、デフォルトで何らかの形態をとるというのはやめていただきたい。ENUMかなんかでチェックできればいいです。
タプルの変更はタイプ量が減るので早く入ってほしいです。

Anonymous said...

s/誓言スルテイ土/制限する程度/