2014-03-18

2014-01-pre-Issaquah-mailingのレビュー:N3890-N3899

N3890: Container<Incomplete Type>

一部のコンテナーのvalue_typeに、不完全型(Incomplete type)を許すよう、制限を緩和する提案。

この提案により、以下のようなパターンが合法になる。

// 再帰的なクラス宣言
struct Foo
{
    std::deque<Foo> deq ; 
} ;

クラスは、クラス定義の閉じカッコ、'}'を持って、完全型になる。したがって、クラス定義の中では、クラス名はまだ不完全型なのだ。クラス定義の中でテンプレート実引数にクラス名を渡すと、当然不完全型になる。

ここで、std::dequeの実装が、sizeof(value_type)のような記述をしていた場合、ill-formedとなる。

std::dequeの実装で、sizeof(value_type)のような、value_typeが不完全型であるとill-formedとなるような記述を避けることは可能である。しかし、現行のC++標準規格は、value_typeに不完全型を許していないので、上記のコードがC++98/11/14で通るかどうかは、実装依存である。

有名どころのC++実装で言えば、libstdc++のdequeは不完全型に対応しているが、libc++と、Microsoftの不自由なC++コンパイラーに付属のSTLは、ill-formedとなる。どちらの実装も、この点においては規格準拠である。

しかし、C++98時代から、このような再帰的なクラス定義のパターンを使いたいという需要は相当にあったlibstdc++の多くのコンテナーが、不完全型に対応しているのは、そのためである。

N3890提案は、多くのコンテナーを不完全型に対応させることを提案している。std::arrayのような特殊なコンテナーは対応できないが、set/mapやunordered_set/unordered_mapまで対応するという。

さて、既存のC++実装を、不完全型に対応させるのは、技術的に難しくはない。技術的な困難はないものの、現実的な困難はある。

  • LLVMのSTL実装であるlibc++は、対応可能である。ただし、std::dequeのABI互換性が壊れてしまう
  • GCCのSTL実装であるlibstdc++は、対応可能である。ただし、std::dequeのABI互換性が壊れてしまう
  • 不自由なソフトウェアであるMSVCのSTL実装は、デバック可視化機能のために、コンパイル時定数を維持したい部分がかなりある。このため、std::dequeのようなクラスの対応は予定していない。std::listやstd::forward_listの対応には興味を示している。

また、コンテナーにユーザー定義のstd::lessやstd::hashを与える場合は、その型も不完全型をサポートしていなければ、コンテナーは不完全型対応にはならない。対応しなければ、コンテナーが不完全型に対応しないだけなので、既存のコードが壊れることはない。

libc++のコンテナーを不完全型に対応させた実験的実装が、GitHubで公開されている。

lichray/libcxx at container_incomplete

それにしても、一昔前は、C++の実験的実装といえばGCCだったのに、いまではすっかりLLVMにお株を奪われている。LLVMのコードは読みやすく、ハックしやすいからであるが、このままではGCCの牙城が危うい。

[title要素と論文のタイトルが異なる悪く書かれたHTML] N3891: A proposal to rename shared_mutex to shared_timed_mutex

現行規格では、shared_mutexはtimed mutex(TimeLockable要件を満たす、すわなち、try_lock_for/try_lock_untilを提供している)かつ、shared mutex(同一の、例えばスレッドなどの実行媒体から、複数回ロックできる)である。

とすれば、shared_mutexは、実際には、shared_timed_mutexとしたほうがいいのではないか。そうすることによって、timed mutexの要件は満たさないが、shared mutexとしての要件は満たすクラスを効率的に実装できる自由度を、C++実装に与えることになる。

そういうわけで、現行のshared_mutexを、shared_timed_mutexに変更する提案。

N3892: C++ Ostream Buffers

現行規格では、ストリームに対する出力操作は、競合を引き起こさないことが規定されている。しかし、其の出力がどうなるかについては規定されていない。したがって、複数の実行媒体(execution agent)からストリームに出力した場合の結果がどうなるかは、規定されていない。

このため、C++利用者は、独自の方法で同期を取らなければならない。独立したソースコードは独立した同期方法を取るため、複数のソースコードを混在させることが難しい。そのため、ストリーム出力に対する同期方法は標準化されるべきである。

ストリームの同期方法は、過去にも何度か提案された。

N3535: C++ Stream Mutexesは、ストリームに対するmutexを標準化しようという提案だった。2013年春の会議で、本格的なmutexではなく、バッファーに対する同期であるべきだとする合意がなされた。

それを受けて、N3678: C++ Stream Guardsでは、ストリームに対するバッチ処理という概念のライブラリが提案された。バッチ処理は、mutexでもバッファーでも、あるいは其の両方を組み合わせても実装できる。会議では、単にバッファーを扱うだけにしては、複雑で問題が多すぎると指摘された。

なかなか決まらないので、後発の提案、N3665: Uninterleaved String Output Streamingでは、単に複数の実行媒体から出力操作されても、混ざらずに出力されることが規格上保証された最小バッファー長を規定しようという提案がなされた。これも否決された。

そこで、今回の論文だが、バッファーに対する操作を明示的に行えるライブラリの提案となっている。以下のように使う。

// N3892提案の例
#include 
#include 

int main()
{


{// ブロックスコープ
  std::ostream_buffer bout(std::cout) ;
  bout << "Hello, " << "World!" << std::endl;
}// ここで出力される。

}

std::ostream_bufferは、自動変数として使う。出力をバッファーし、破棄されるタイミングで、混ざらずに一気に出力される。

N3893: C++ Standard Library Active Issues List (Revision R86)
N3893: C++ Standard Library Defect Report List (Revision R86)
C++ Standard Library Closed Issues List (Revision R86)

標準ライブラリに指摘されている既知の問題点集。

[今回最後の不快なPDF] N3896: LIBRARY FOUNDATIONS FOR ASYNCHRONOUS OPERATIONS

この論文は、futureのthenによって指定する継続は、コールバックで指定する継続に比べてパフォーマンスが悪いと指摘する

futureは、処理を発行してfutureオブジェクトが返されたあとに、thenで継続をセットなければならない。thenで継続をセットする前に発行した処理が完了した場合、まず同期が発生してfutureの状態が変更され、しかし継続はセットされていないのでそのままで、そのあとにthenで継続がセットされて、この際にも同期が発生する。

コールバックは、処理を発行する際に、同時に継続もセットするので、無駄な同期が発生しない。

なぜ同期の発生回数にこだわるかというと、一回同期するのに、論文著者によれば、論文筆者の環境では、mutexやアトミック操作による同期は10-15ナノ秒かかる。また、std::futureによってブロックされていたスレッドにコンテキストスイッチして、ブロックから目覚めるのに、3マイクロ秒かかるとしている。

一方、64バイトのUDPパケットをあるホストのユーザースペースのアプリケーションから、別のホストのユーザースペースのアプリケーションまで、10GbEネットワークで送るのにかかる時間は、2マイクロ秒である。

論文筆者は、マイクロ秒単位のパフォーマンスの差が問題になる金融取引などの仕事をしているらしく、パフォーマンスの低いfutureではお話にならないとバッサリ切り捨てている。

しかし、futureもfutureで、利用価値はある。いや、世の中の並列実行は、futureとコールバックだけではない。resumable functionやcoroutineと呼ばれている、函数の栃生で実行を中断、再会できるようなものや、さらにはファイバーなどと呼ばれている、通常のスレッドより軽い協調的なマルチタスクによる実行単位など、様々な方法がある。もちろん、ユーザーが独自実装した方法も使いたい。

論文は、Boost 1.54のAsioを引き合いに出して、多様性があり、拡張性もあるasyncのインターフェースと実装方法を紹介している。

N3897: Auto-type members

non-static data memberの宣言の型指定子にauto指定子を使えるようにする議論のまとめ論文。これは提案ではない。

もし、議論されている内容が提案されて可決されたのならば、以下のようなコードが書けるようになる。

// N3897議論が提案されて可決された場合に書けるようになるコード
struct Foo
{
    auto a = 0 ; // int
    auto b = 0.0 ; // double
    auto * c = this ; // Foo *
    auto d = c ; // Foo *
    auto & e = *this ; // Foo &
} ;

さて、このようなコードを書きたいだろうか。筆者にはよくわからない。論文では、Faisal Valiが実験的な実装を行ったそうだ。

このような機能を追加するには、様々な懸念が噴出しているので、これが提案されるかどうかはわからない。

コンパイラー屋も含む、多くのC++標準化委員が懸念を示している機能なので、なにか説得力のある利用方法を示すとか、現場の利用者からネツレツに使いたいという意見でも上がらない限りは、この機能はこのままお蔵入になるだろう。

もし、具体的な活用方法や、ぜひとも実務で使いたい、コレがなくては不便でならぬ。C++を使うかどうかはこの機能の有無にかかっているという強い意思があるのならば、筆者まで知らせてもらいたい。

N3898 HASHING AND FINGERPRINTING

これは提案ではなく、ハッシュ関数の設計と実装の紹介としての論文のようだ。

ユーザー定義型のハッシュ値を簡単に計算できるようにするために、char, bool, longなどの基本型を複数突っ込めば、ハッシュ値を計算してくれるライブラリの設計と実行を紹介している。

N3899: Nested Allocation

歴史的に、C++の実装では、automatic storageというものは、一般にスタックとよばれる確保済みのメモリ領域から確保している。スタックは環境によって様々ではあるが、かなり強い制限がある。しかし、スタックからメモリを確保するのは高速である。

C++では、allocaなどの例外的な関数を除き、スタックから直接確保するための方法を提供してこなかった。

C99はvariable length arrayを追加した。C++でも、似たような機能をruntime sized arrayとして提案中だ。

また、C++では、non staticデータメンバーにruntime sized arrayを認める提案もしている。この場合、クラスはautomatic storage上に確保しなければならない。しかし、これはクラスのオブジェクトはどのストレージにも構築できるいう、C++の利点が損なわれてしまう。これは極めて例外的な提案だ。

また、現在提案中のstd::dynarrayは、automatic storageに確保できる文脈では確保し、そうでない文脈ではヒープに切り替えるといった、柔軟な対応が可能なライブラリだ。しかし、これはいつスタック上に確保されるのかということが、規格ではなく、実装の都合で決まってしまう。およそ、スタックに確保されることを気にするC++ユーザーは、絶対にスタックに確保されるという保証が欲しいはずだ。

さらに、これらの提案のすべてに、再構築という大問題が立ちはだかる。以下のような問題だ。

// 再構築
void f( std::dynarray<int> & r )
{
    r~dynarray<int>() ; // デストラクターの明示的呼び出し
    new(r) dynarray<int>(2) ; // 再構築。いいのか?
}

int main()
{
    std::dynarray<int> a(1) ; // 構築
    f( a ) ; // リファレンス渡し
}

これはいったいどうなるのだろうか。このようなコードは認められなければならない。しかし、これを認める場合、オブジェクトの寿命がネストしてしまう。

論文では、まずそもそも根本的に、オブジェクトの寿命のネストをサポートするための規格が必要であると主張している。

結び

とにかく、これで2014-01-Pre-Issaquah論文集をレビューし終えた。筆者がドワンゴに入社して二ヶ月近くたつが、C++WG論文をいつもより丁寧にレビューするぐらいしかしていない。ドワンゴ社内は、現場のC++利用者の意見が聞ける環境なので、提案の問題点の発見に役立つ。

また筆者も、社内でC++に提案されている新機能について、論文をレビューしながら社内IRCでチャットという形で解説しているが、この提案というのは、仮に採択されるにしても、正式にドラフト規格に、取り入れられて、C++コンパイラーで実装されて、現場で使えるようになるまで、まず10年はかかる機能ばかりだ。今すぐに役立つ知識ではない。

そして、2014-02-Post-Issaquah論文集もあるので、もうしばらくはこの状態が続く。たまっているC++WG論文をレビューし終えたら、他のC++啓蒙活動も始めようと思う。

ドワンゴ広告

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

「C++11は十分に枯れて現場で使えてしまうので、もう飽きた。C++11をバリバリに書きつつ、10年後に現場で使えるようになるかもしれないC++17の新機能の話がしたい」という君。

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

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

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

1 comment:

Anonymous said...

10年かかってもいいです。ソレまで妄想しますから。
readyになった瞬間ディナーです。

ostream_bufferはautoで食えるようになるといいですね。
構造体などにautoを使いたいとは思わないですね。アレはメモリサイズを律するので切り詰めたいはずです。
スタック保証は結構重要だと思います。速度に影響しますし、完全性の観点から小さくありたいです。

いつもありがとうございます。勉強になります。