ISO/IEC JTC1/SC22/WG21 2013-03-pre-Bristolが公開された。
ちなみに、今回から標準C++財団のWebサイトであるisocpp.orgの方でも、論文が公開されている。従来、C++標準化委員会の論文は、死んだ木時代(dead-tree era)からの伝統であるISOとANSI/INCITSの規則に基づき、基本的に会議前と会議後に、ISO/IEC JTC1/SC22/WG21 - The C++ Standards Committeeで公開されていた。幸い、JSTORや政府のような情報特権を維持し、文字通り百年も前に書かれた論文すらいまだに閲覧だけで何十万ドルもの購読料を要求するようなくだらない勢力とは違い、今までの論文は1992年からすべて公開されている。過去のMLのアーカイブなども公開する議論があるが、まだなかなか進んでいない。
しかし、納期を定めて論文を集めてまとめて発表というのは、この時代にはあまりにも遅すぎる。もはや、論文を公開、流通させるコストは実質無料になったのだ。論文は書かれた時点で広く公開して世間の評価を求めるべきである。
いや、実際のところ、この2013年は、情報を公開するコストが最も高い時代である。今から50年後の未来、読者は孫に囲まれてこう物語るだろう。
「ああ、そうじゃとも、お若いの。ワシらがお前さんぐらいの時分には、硬い円盤とて、両手に余るほどの大きさなのに数テラバイトと記憶密度の極めて薄く、シーク時間が10ミリ秒もあって、シーケンシャルアクセスすらたったの100MB/secというどうしようもなく遅い記憶媒体を、みんな使っておったよ。全部シーケンシャルコピーするだけでも何時間もかかったのう。いまから思えば信じられんわい。爪の先に印刷可能な容量数百ペタバイトのストレージ、しかも全コピーは数マイクロ秒という今の時代には考えられんことじゃな。しかし、お前さん方には悪いことをしたのう。当時、ニコラ・ガッデム・テスラがトーマス・ドッシュ・エジソンという悪の親玉に宗教裁判で負けて、しかもわしらはエジソンを偉大な発明王よと褒め称えたんじゃから。もしニコラ・ガッデム・テスラが正当に評価されておれば、来年始まる全世界統一無線送電はあと20年早く行われていたかもしれんのう。それから、アーロン・シュワルツの宗教裁判がなければ、今までに書かれた全論文を全人類の小指に印刷する論文アーカイブ計画はもっと早く達成できていたじゃろうて。まあ、ガリレオ・ガリレイの失敗から何も学ばず、当時まだカルト宗教の総本山をのうのうと生かしておいたワシらじゃから、当然の失敗だったのかもしれんのう」
そういうわけで、標準C++財団のWebサイトで、論文が書かれ次第、バラバラに公開するようになった。もちろん、従来の死んだ木時代の慣習も維持されるので、ISO/IEC JTC1/SC22/WG21の方でもまとめて一括して公開している。
[失礼にもPDF]N3525: Polymorphic Allocators
実行時ポリモーフィックアロケーターを追加する提案。
従来のアロケーターは、テンプレート引数経由で渡す、コンパイル時ポリモーフィックアロケーターであり、非ジェネリックなコードからは使えない。しかし、世の中のコードがすべてジェネリックであるわけもなく、これでは使いづらい。そこでこの提案。
実際、今のアロケーターはイケてないにも程がある。まだどのコンパイラーもテンプレートの実装が貧弱だった時代は、コンパイラー付属の標準ライブラリの実装でもアロケーターの汚らしいハードコードが行われていたし、今でもやはり使いづらい。
問題は、コンテナーにアロケーターを指定した場合、
- コンテナの要素にもそのアロケーターを適用させたいこと
- アロケーターのオブジェクトの型は、ストレージ上に構築する型から独立しているべき
という、至極当然のことができない。1.に関しては、C++11に入ったscoped allocatorで、とりあえずは解決した。あロケーターを直接使わず、allocator tratis経由で使えばの話だが。
だいたい、アロケーターを変えるだけで異なる型になるというのはとても使いづらい。promiseやfutureは、type erasureのテクニックを使ってアロケーターの型を隠している。ただし、type erasureは既存のコードに適用できないし、それにtype erasureのテクニックを使うには、深いC++の知識が必要だ。日本でC++でtype erasureをまともに書ける人間は数えるほどしかいないだろう。それに、書けるからといって、わざわざ書きたいかというと、書きたいわけがない。
というわけで、C++11のscoped allocatorと組み合わせる、実行時ポリモーフィズムを実現するアロケーター、polymorphic_allocator(仮称)を追加する提案。アロケーターを指定するテンプレート実引数にはこれを指定して、アロケーターの実装は実行時に切り替えることができるようになる。
N3526: Uniform initialization for arrays and class aggregate types
アグリゲートの初期化の構文を統一する提案。
アグリゲート(配列かクラスで、特定の条件を満たすもの)は、Cから受け継いだ従来のリスト初期化で初期化できる。
int aggr_array[2] = {1, 2}; struct aggr_t { int a; int b; } instance = {3, 4};
アグリゲートを含むアグリゲートの初期化は、以下のようになる。
int aggr_array[2][2] = {{1, 2}, {3, 4}}; struct aggr_t { struct { int a; int b; } x, y; } instance = {{1, 2}, {3, 4}};
とまあ、ここまではいいのだが、クラスと配列を混ぜると、初期化の構文が一致しなくなる。
struct aggr_t { int a; int b; } array_of_aggr[2] = {{1, 2}, {3, 4}}; struct aggr_ex_t { int x[2][2]; }; aggr_ex_t bad = {{1, 2}, {3, 4}}; // Error: Too many initializers, see below for details aggr_ex_t good = {{{1, 2}, {3, 4}}};
クラスの配列の初期化はいいのだが、配列をメンバーにもつクラスは、{}が余分に必要になる。しかし、根本的には変わらないはずであり、t{}は特に何も機能しておらず冗長で、構文が非統一である。
この問題は、std::arrayを多次元に拡張しようとした際に発見された。現行の文法に従うと、多次元版のstd::arrayの初期化が非常に厄介なことになってしまう。
// 多次元arrayの実装は省略 // 一次元、統一感あふれる初期化 int _a[2] = {0, 0}; array<int, 2> a = {0, 0}; // 二次元、ネイティブな配列に比べて余分な{}が必要 int _b[2][2] = {{1, 1}, {2, 2}}; array<int, 2, 2> b = {{{1, 1}, {2, 2}}}; // 三次元、もうわけわからん。 int _c[2][2][2] = {{{3, 3}, {4, 4}}, {{5, 5}, {6, 6}}}; array<int, 2, 2, 2> c = {{{{{3, 3}, {4, 4}}}, {{{5, 5}, {6, 6}}}}};
さすがにこれは構文を統一すべきだろうというわけで、構文の統一が提案されている。
N3527: A proposal to add a utility class to represent optional objects (Revision 2)
optional<T>の提案。T型を格納しているかしていないクラス。Boostのoptionalが元になっている。
こんなコードは書きたくない。
bool get_value( int & output ) ; void f() { int value ; bool b = get_value( value ) ; if ( b ) { // valueには妥当な値が入っている } }
このコードは、valueに妥当な値が書き込まれたかどうか、別の変数を使って確認させている。
こんなコードはもっと書きたくない。
int get_value( ) ; void f() { int value = get_value() ; if ( value != -1 ) { // valueには妥当な値が入っている } }
これは、-1という値が、いわばエラーコードであり、それ以外ならば妥当な値が入っているとみなすものである。
int型やポインター型ならばまだしも、ユーザー定義のクラスの場合は悲惨だ。わざわざ妥当な値が格納されているかどうかを、プログラマーごとに非互換な方法で実装したがる。その結果、非互換な確認方法が余にあふれている。
そもそも、そのオブジェクト自体が特殊な状態を表すことによって、妥当な値を格納しているかどうかを独立して実装するのは非効率的だ。そのため、妥当な値を格納しているかどうかをしめすクラスを標準で定義すべきだ。optionalならば、以下のように書ける。
std::optional<int> get_value() ; void f() { auto o = get_value() ; if ( o ) {// *oが妥当な値 auto & valid_value_ref = *o ; } }
optionalのoperator boolが妥当な値を格納しているかどうかを返し、operator *が格納している値を返す。
N3528: Minutes of Feb 5 2013 SG1 Phone Call
並列ループのクリティカルセクションに関する電話会議の議事録。
[失礼なPDF]N3529: SG5: Transactional Memory (TM) Meeting Minutes 2012/10/30-2013/02/04
[不快なPDF] N3544: SG5: Transactional Memory (TM) Meeting Minutes 2013/02/25-2013/03/04
Transactional Memoryに関する会議の議事録。
[失礼極まりないことにPDF]N3530: Leveraging OpenMP infrastructure for language level parallelisation
言語による並列プログラミングを、既存のOpenMPバックエンドで実装するとしたらどのようになるかという論文。もちろん、言語に組み込むからには、プロプロセッサではなくてキーワードが付加される。
[不敬罪が適用されるべきPDF]M3531: User-defined Literals for Standard Library Types (version 3)
多くの標準ライブラリに便利なユーザー定義リテラルを用意する提案。
実行時に長さを指定する動的配列クラスdynarrayの提案。
std::vectorは便利だが、必要以上に汎用的で、格納できる要素数を増減できる。実行時の配列を作る際に長さが決定、それ以上増やす必要がない場合、std::vectorは鶏を割くのに牛刀を用いるの感がある。そこで、実行時に固定長を指定できる標準ライブラリdynarrayの提案。
コア言語の配列自体を実行時に要素数指定できるようにしようという提案もあるが、こちらはライブラリでの実装の提案となっている。かねあいはどうするのか。もちろん、コア言語の配列とライブラリは違うわけで、範囲チェックが行えたりアロケーターを指定できたり、その他諸々のライブラリならではの利点もあるわけだが。
標準ライブラリconcurrent queueの提案。リファレンスを返すdequeとは違い、値を返す。これにより複数のスレッドから同時にアクセスできるよう実装できる。また、失敗する可能性があるNon-WaitingやNon-Blockingな操作も提供。
伝統的に、UNIXではプロセスとパイプを使ってマルチスレッドプログラミングを気軽に行なっている。例えば、ログファイルからエラーメッセージを抽出して、特定のメールアドレスだけに絞り込み、結果を整形するには、以下のように書ける。
cat log.txt | grep '^Error:' | grep -v 'test@example.com' | sed 's/^Error:.*Message: //' > output.txt
これはマルチスレッドパイプラインである。それぞれのプロセスは独立して並列実行される上に、デッドロックやデータ競合や未定義動作は一切存在しない。
もちろん、すべてのマルチスレッド処理が、このようなパイプライン処理に落とし込めるわけではない。しかし、パイプラインに落とし込めるような処理は、このように書けばとても簡潔かつ安全に実装できる。
残念ながら、C++にはこのようなマルチスレッドパイプラインを手軽に書けるライブラリが存在しない。mutexやthreadといった基本的すぎるライブラリを駆使してパイプラインを実装するのは苦痛だ。そこで、パイプラインを手軽に実装できるライブラリを提案する。
具体的には、利用者が入力元、出力先、そして間の入力を受け取って出力を返す機能を実装すれば、残りのパイプ処理やスレッドプール実行などの処理を引き受けてくれるライブラリということになる。
処理は、expression templateの技法により、f1 | f2 | f3 のようにわかりやすく記述できる。
これはすばらしいライブラリだと思う。ぜひとも入って欲しい。
ストリームに排他的処理を追加する提案。現行のストリームは競合しないことが保証されているが、出力結果がどうなるのかはわからない。ストリームに排他的処理の機能を提供することで、出力結果を保証できるようにする拡張の提案となっている。
クラスのメンバーのoperator deleteをオーバーロードする際には、ストレージのサイズが引数として渡される。しかし、グローバルなoperator deleteをオーバーロードしても、サイズは渡されない。これにより、近代的なアロケーターのテクニックである、小さな単位のストレージには、ストレージに直接サイズ情報を埋め込まず、同じサイズごとに共通のヒープを用意してそこから確保するという実装のパフォーマンスが低下する。というのも、ストレージのアドレスからヒープを検索しなければならないからだ。
というわけで、従来の宣言に加えて、以下のグローバル名前空間のoperator deleteがオーバーロードできるようになる。
void operator delete(void*, std::size_t) throw(); void operator delete[](void*, std::size_t) throw();
これもぜひ入るべき提案である。
N3537: Clarifying Memory Allocation
規格を厳密に解釈すると、アロケーターはメモリ確保要求の前後のつながりから、動的に挙動を変えてはならないとしている。また、すべてのメモリ確保要求に対応するよう、内部的な確保関数の呼び出しを行わなければならないとも読める。
実行時の特性に合わせて、メモリ確保の戦略を変えるのは、アロケーターの実装として妥当なものであり、実装例も多数ある。重要なのは、アロケーターは利用可能なメモリーを返すことであって、その内部ではない。したがって、このような制約は廃止すべきだという提案。
N3538: Pass by Const Reference or Value
プログラマーはパフォーマンスを重要視する。そのため、機械的に値渡しよりconstなlvalueリファレンスで渡すことを好む。リファレンスというのは、内部的にはアドレスを渡しているわけであり、多くのアーキテクチャでは、int型のような基本的な型は、アドレス渡しと全く同じレジスタ経由で渡されるため、パフォーマンスは変わらない。むしろ、アドレス渡しには、エイリアシングの問題もある。Cのrestrictは問題を完全に解決しない。従来、プログラマーはエイリアシングの問題を単に無視してきたし、よくてドキュメントにエイリアシングをサポートしないと書くぐらいだった。エイリアシングに対応するためにアドレスを調べて分岐を書くと、近代的な深いパイプラインのアーキテクチャでは、値渡しか参照渡しかという違いが吹き飛んでしまうほどのペナルティが発生する。
どの型ならば値渡しでも効率がいいのかということは、環境ごとに異なるので、移植性の高いコードで書くことはできない。さらに、ABIにも関係するので、そう簡単に環境ごとに変えるわけにもいかない。
そこで、最適な渡し方を、コンパイラーに決めさせるための構文を用意しようという提案。
それにしても、提案されている文法がキモい。宣言子の文法を拡張しているので、あたかもポインターとして*を指定するように、|を指定する。
type f( const type | input ) ;
N3542: Proposal for Unbounded-Precision Integer Types
無制限精度整数型の提案。つまりは、メモリの許す限りの精度で計算できる整数ライブラリということだ。
普通、このような基本的なライブラリはとっくの昔に標準で入っているべきなのだが、むしろC++だからこそ、なかなか標準では決まらなかったライブラリとも言えるだろう。Boostでも、この機能を提供するライブラリは、似たようなものがいくつもレビューに書けられ、その都度否定されるという歴史をたどってきた。去年、ようやくBoostでMultiprecisionがレビューを通過して採択されたそうだ。
もちろん、C++用のライブラリ、あるいはC++から使えるCのライブラリで、やれFFTだアセンブリだといった最先端で最速の実装は多数ある。問題は、モダンなC++にふさわしい洗練されたインターフェースを提供しているライブラリとなると、これがなかなか難しい。演算子のオーバーロードがあるC++では、むしろ作りやすいはずなのに、やはりC++は難しいのか。あるいは、数学者というのは、見た目がきれいだが難しい言語より、見た目が暗号的だが習得の簡単な言語を好むのか。大規模な数値計算の分野で、Pythonが多用されているのも、やはりわかりやすさのためなのか。
[屈辱的なPDF] N3543: Priority Queue, Queue and Stack: Changes and Additions
Priority queue, queue, stack, heapは、すでにコンテナーアダプターとして提供されているが、機能が貧弱すぎる。そこで、新しいアダプター群を追加する提案。どうやら従来のアダプターを拡張するのは無理だと判断したらしい。
ヒープへのイテレーターを追加し、ヒープの要素を変更できるようにし、ある種のヒープに対する効率的なマージも提供し、またヒープをソートを安定ソートにできるようにし、ヒープ同士の比較もできるようにしている。
[不愉快なPDF] N3545: An Incremental Improvement to integral_constant
integral_constantにconstexprなoperator()を追加する提案。これにより、いままで、integral_constantを返すtype traitsを使うには、
std::is_arthmetic<T>::value
としなければならなかったのが、
std::is_arithmetic<T>{}()
と書けるようになる。
これはすばらしい提案だ。ぜひとも入るべきだ。
[不当なPDF] TransformationTraits Redux
メタ関数をエイリアステンプレートでラップする提案。わざわざnested typeを指定する必要がなくなる。
従来のメタ関数の戻り値は、nested typeで受けるしかなかった。
meta_function<T>::type
しかし、C++11にはエイリアステンプレートがある。エイリアス宣言でメタ関数をラップすれば、冗長なnested type指定は省略できる。
template < typename T > using meta_function_t = meta_function<T>::type ;
こうすれば、meta_function_t<T>とかいただけで、meta_function<T>::typeと同等になる。
これは、私も以前から考えていたものであり、当然入るべきだと思っていたのだ。C++11では時間が足りなかったのが残念なくらいだ。
[虫唾の走るPDF] N3547: Three <random>-related Proposals
乱数ライブラリ関連の提案。
一つ目は、algorithmにsampleを追加。C++11のドラフトの段階で、sampleとsample_nを追加する提案があった。これは、SGIのSTL実装にあったもので、それぞれ有名なsamplingのアルゴリズムである、algorithms S (“selection sampling technique”) and R (“reservoir sampling”)の実装である。詳しくはDonald E. Knuth: The Art of Computer Programming, Volume 2: Seminumerical Algorithms (Third Edition)の§3.4.2を参照。
個人的には、このようなアルゴリズムを自力で正しく書けるわけがないので、あると嬉しい。議論の結果、時期早尚なので見送りになった。
今がその見直しの時だというわけで、新しい設計のsampleが提案されている。なんでも、アルゴリズムによって関数テンプレートを分けるのではなく、イテレーターの種類によってアルゴリズムをtag dispatchingによって選択するようになっている。つまり、入力イテレーターがrandom access iteratorの場合はアルゴリズムRを使い、そうでない場合はSを使う。
そのほか、この論文にはやたらとKnuthネタが多い。まあ、randomからして、標準にknuth_bという引数定義済みのtypedef名(The Art of Computer Programmingで説明されているアルゴリズムと一致するもの)があるぐらいなのだから、Knuthネタがまじるのも当然といえる。
二つ目のrandom自体に対する変更は、もっと初心者フレンドリーにしようということで、グローバルなURNGオブジェクトを返す関数(global_urng)とその初期状態ランダム化の関数(randomize)、さらには範囲を指定したint型とdouble型の値を返す関数(pick_a_number)を用意している。
論文では、randomに寄せられた苦情として、あまりにも初心者向けではないというものが多かったらしい。randomについては私もいくつか記事を書いているが、その記事にはてなブログとかTwitterなどで間接的に寄せられた感想は、「めんどくさすぎる。もっと簡単な方法はないのか」であった。randomの設計はすばらしいのだが、どうも初心者には汎用的すぎるのだろう。
グローバルなURNGとその初期状態のランダム化は、当然用意されているべきだと思う。たしかに、URNGの状態をクラスのオブジェクトとして持てるのは便利だが、プログラム中で共有できるURNGオブジェクトは当然欲しい。その初期状態のランダム化方法も標準で用意されていて欲しい。
pick_a_numberはどうかと思うが、いちいちuniform_distributionの特殊化のオブジェクトを作るというのは、たしかに初心者には面倒に感じるかもしれない。
例えば、六面ダイスのプログラムを実装してみよう。このプログラムは、実行するとランダムで1から6までの数字のうちどれかを出力して終了するものとする。このプログラムは以下のように書ける。このコードのバグに注意、私は試しただけで、正しく動くことを証明していない(Knuth先生ごめんなさい)
#include <iostream> #include <random> int main() { std::default_random_engine engine ; std::random_device rd ; engine.seed( rd() ) ; std::uniform_int_distribution<int> dist( 1, 6 ) ; std::cout << dist( engine ) << std::endl ; }
このコードを書くには規格書を読まなければならなかった。また、このコードを理解するには、randomの設計思想、すなわちエンジンとディストリビューションというコンセプトについて理解しなければならない。また、random_deviceというクラスの理解も必要だ。どうもジェネリックなものの考え方が要求される。それに、もっと複雑なプログラムとなると、やはりプログラム中で共通なエンジンのオブジェクトがほしくなり、グローバルなエンジンのオブジェクトにアクセスできるための何らかの手段を講ずるだろう。
この提案では、上記のコードは以下のように書ける。
#include <iostream> #include <random> int main() { std::randomize() ; std::cout << std::pick_a_number( 1, 6 ) << std::endl ; }
多くの初心者の乱数の需要はこの程度で足りるのだろう。
3つ目の提案。これは私にとっては当然だが、いろいろと反発が予想されそうな提案だ。ズバリ、cstdlibとstdlib.hの乱数関連のものを、deprecated扱いにしてしまおうというものだ。
個人的には、これは当然の処置である。C++11で追加されたrandomは圧倒的に既存の貧弱なrand(笑い)を凌駕しており、もはやrandは使われるべきではないのだ。
また、algorithmのrandom_shuffleもdeprecatedにしようと提案されている。かわりに新しく追加された、randomフレンドリーなshuffleを使うべきである。
randomが初心者フレンドリーではないというのはよく言われることであるし、まあ、悪くない提案ではないかと思う。
[これもPDF] N3548: Conditionally-supported Special Math Functions for C++14
C++11では採用されなかった数学用関数を、C++14に、"conditionally-supported"として追加する提案。実装はこの関数を提供する義務はない。
この分野には疎いのでよくわからない。
[意味もなくPDF] N3549: s/bound/extent/
いままで、歴史的に、配列にはboundという言葉を使ってきたが、この言葉は、extentと言ったほうがいい。そのため、規格の文面における配列のboundという言葉をextentに置き換える、文面の一貫性を改善する提案。
この手の用語の統一は度々起きてる。
[無神経なPDF] N3550: Proposed C++14 Value Classification
lvalue/xvalue/prvalueの分類の定義の文面を大幅に変更する提案。正直value categoryは鬼門だ。
[PDFは帰れ] N3551: Random Number Generation in C++11
まるでrandomの入門書のような論文。ランダムの設計思想である、engineとdistributionの明確な分離やその使い方を説明している。最後にN3547での提案内容に触れている。特に目新しいことが書いているわけではない。
まあ、randomの入門用にはいいと思うが、C++WGの論文を参考書がわりにする変わり者がどのくらいいるのか。
ただ、現状でC++11を学ぼうと思ったら、規格とC++WGの論文を読むのが一番だが。
[なぜ複雑なレイアウトの必要がないのにPDFを選択するのか] N3552: Introducing Object Aliases
オブジェクトエイリアスの提案。
昔々、N1785でオブジェクトテンプレートが提案された。この提案は受け入れられるなかったが、いまのエイリアス宣言の文法を流用して、オブジェクトにもエイリアスという概念を持ち込もうという提案している。
古くはゼロックスのFortranマニュアルにすら、こう書かれている。
DATA文の主目的は、定数に名前を与えることである。円周率を使う場面で毎度3.141592653589793と記述するより、DATA文で変数Piにその値を与えて、定数として使うことができる。これは、円周率の値を変更する必要に迫られた時も、プログラムの変更を容易にする。
円周率が定数だとか変更するものだとか書かれているのはなかなかおもしろいが、プログラミングの世界では、定数に名前をつけることは良い習慣であるとされている。有効精度やハードウェアの制約や、あるいは新しい計測方法によって既知の値より高い精度が求められた場合など、定数の定義を変更するだけですむ。
問題は、C++における定数の定義は、型が固定されているということだ。プリプロセッサーマクロにしろ、コンパイル時定数となる変数にしろ、型が固定されている。
#define pi 3.141592653589793 constexpr double pi = 3.141592653589793 ;
このように定数を定義すれば、doubleの時はいいが、floatとかlong doubleとかintとか、他の型で計算するときも、定数の方はdouble型になってしまう。あるいは、別の名前をつけるという手もある。たとえば、double型はpi、float型はpif、long double型はpilなどと別の名前をつける。しかし、この方法では型に対して汎用的なコードを書くことが出来ない。
そのため、オブジェクトをテンプレート化できるようにしてはどうか、というのが、オブジェクトテンプレートの動機だ。
C++11の今日では、メタプログラミングが発達し、constexprができ、エイリアス宣言やそのテンプレートであるエイリアステンプレートという機能もある。この機能を組み合わせて、オブジェクトをエイリアステンプレート化できるようにしてはどうか。例えば、
template < typename T = double > constexpr T & pi_constant() { static constexpr T value = static_cast<T>(3.141592653589793L) ; return value ; } template < typename T = double > using pi = pi_constant<double>() ; template < typename T > T area_of_circle( T radius ) { return pi<T> * radius * radius ; }
このようにすれば、あとはpi_constantテンプレートを変更するだけで、どのような型にも対応できる。定数piはオブジェクトエイリアスとなり、型に対して汎用的に使えるようになるのだ。
もちろん、これは関数テンプレートのシンタックスシュガーに過ぎず、constexprがある今、既存の関数テンプレートでも十分に実現できることだ。ただし、関数テンプレートの場合、関数呼び出しの()を書かなければならない。
template < typename T > T area_of_circle( T radius ) { return pi<T>() * radius * radius ; }
しかし、この問題について調査したところ、プログラマーは皆、関数呼び出しの()は冗長だと回答した。たかが定数にアクセスするがごときに括弧を書くというのは、良くて「不自然」、悪くて「不快」だという。数学的には、定数とは無引数関数として表現できるが、プログラマーとしては、コードを書くが如きにそんなモデル化は受け入れ難い。
あまりに単純すぎるシンタックスシュガーをあまり好まない私としては、どうも納得できない。というのも、結局<T>で型実引数を与えなければならないわけだから、()を書かなくていいぐらいの違いはささいなことだとおもうのだが。
ただし、すでに型に対するエイリアスがあり、型を返すメタ関数をエイリアステンプレートでラップすれば、nested typeを指定しなくてもいい以上、オブジェクトに対するエイリアスを追加するというのも悪くないかもしれない。
[ぴーぴーうるさいひよこたちに挨拶でもしてやろうかな] N3553: Proposing a C++1Y Swap Operator
スワップ演算子の追加。
なんと演算子の追加である。近年、多くの研究者が、スワップはコピーやムーブと同じく基本的な操作であり、むしろコピー代入よりも基礎的な操作であると主張している。実際、ムーブの概念が導入された今、スワップを基本的な操作とみなすのは、理にかなっている。
そのため、スワップ演算子の追加を提案する。
現在の提案では、演算子は、operator :=: が提案されている。つまり、スワップは以下のように書く。
int a = 1 ; int b = 2 ; // a == 1, b == 2 a :=: b ; // スワップ // a == 2, b == 1
スワップ演算子の優先度は、代入演算子と同じく、ほぼ最下位になる。また、評価結果は、左辺オペランドとなる。
他の演算子と同じく、非クラス型にはデフォルトのオーバーロードが提供される。クラスはスワップ演算子を暗黙に生成される特別なメンバー関数として持つ(条件次第でdeleted定義される)
その他、他にも多数の細かい規則があるが、演算子であるがゆえの規則だ。
ライブラリとしては、スワップ可能かどうかを返すis_swappable<>やis_nothrow_swappable<>が追加される。
これは採用すべきだ。
[ピーピーディーエフエフ] N3554: A Parallel Algorithms Library
algorithmを並列版に拡張する提案。従来のalgorithmは暗黙にシーケンス実行版であり、std::seq, set::par, std::vecというポリシーベースのタグディスパッチにより、明示的にシーケンス実行、パラレル実行、ベクトル実行を指定する。
void f( std::vector<int> & vec ) { // 従来のソートは暗黙にシーケンス実行 std::sort(vec.begin(), vec.end()); // 明示的なシーケンス実行のソート std::sort(std::seq, vec.begin(), vec.end()); // パラレル実行のソート std::sort(std::par, vec.begin(), vec.end()); // ベクトル実行のソート std::sort(std::vec, vec.begin(), vec.end()); }
シーケンス実行というのは、従来通りの実行だ、パラレル実行は、処理を複数のスレッドに分散することを許可するものである。ベクトル実行は、パラレル実行と同じだが、イテレーターやファンクターに強い制約を課すことにより、実装によるさらなる最適化を許すものである。
さらに、実装はこれ以外の実行モードを独自拡張として提供することもできる。
今回の提案では、識別子をparallel_から初めたり、あるいは別の名前空間std::parallel::を使うのではなく、タグで実行モードを決定する。これにより、実行モードを切り替えることが可能になる。例えば、コンパイル時に型に応じて実行モードを切り替えたり、実行時に要素数に応じて実行モードを切り替えたりできる。
論文では、既存の実装による利用例をすべて補足できるようにしたことや、新しく追加するアルゴリズムや、既存のアルゴリズムをどのように並列実行に対応させるのか、あるいは対応させないアルゴリズムなど、様々なことについて細かく解説している。
N3555: A URI Library for C++
なぜか論文が公開されていない。N3507: A URI Library for C++の改訂版だそうだが。
[こいつもPDF] N3556: Thread-Local Storage in X-Parallel Computations
並列実行がC++に入る機運が高まっている。秘匿に並列実行と言っても、様々なものがある。命令単位で並列のSIMDから、自動的にスレッドプールに分散されるものや、あるいはGPUのような並列実行用のハードウェア上で実行されるものや、あるいはプロセス単位などなど。
この論文では、そのような様々な並列実行の文脈で、スレッドローカルストレージ(TLS)へのアクセスはどうなるのか。動作は保証されるのか保証されないのか。あるいは利用はできるがその利用法が制限されるのか。などのことについて考察している。そして、TLSの動作保証としてありうる5段階のレベルと、用語の定義を提案している。
[またPDFだよ] N3557: Considering a Fork-Join Parallelism Library
並列プログラミングのうち、fork-joinのライブラリによる実装の考察。
並列プログラミングは、次期C++の主要な機能のひとつになるだろうと思う。この論文の筆者Pablo Halpernは、もともとfork-joinを言語側でサポートするべきだという意見を表明していたが、色々と反対意見が多かったため、とりあえずはライブラリ実装の設計を考察している。
考察結果としては、ライブラリで実装するには、引数渡しの方法や遅延戻り値を実現する言語機能が欠けており、例外の扱いも面倒で、まあ、実際にライブラリ実装はたくさんあるし有用性も証明されているがなんともかんともと、直接はっきりとは書いていないものの、どうも筆者のライブラリではなく言語側でサポートしたい意思が伺える内容になっている。
[PDF有害論]N3558: A Standardized Representation of Asynchronous Operations
futureに非同期処理を追加する提案。futureは非同期だが、結局結果を取り出すgetはブロックする。
この提案では、futureに多数の機能を付け加える。たとえば、futureが完了次第、次の動作に受け渡すthen、futureがネストしている場合に一気に値を取得するunwrap、値がブロックせずに得られるかどうかを確かめるready、複数のfutureをまとめて、どれかひとつが完了するか、あるいは全部が完了するまでまつwhen_any/when_all、あるいはreturnせずに途中で値を用意するmake_ready_future。
非同期I/Oをかじった経験からみると、when_anyやwhen_allが今までなかったのは信じられない。
[びっくするほどPDF! びっくりするほどPDF!] N3559: Proposal for Generic (Polymorphic) Lambda Expressions
ジェネリックlambdaの提案。たぶんC++14に入る。
lambdaの何がジェネリックになるかというと、引数だ。従来、以下のような関数に渡すlambda式を書けなかった。
template < typename Func > viod f( Func func ) { func( 0 ) ; func( 0.0 ) ; }
これは、lambdaがジェネリックではなく、仮引数の型を明示的に指定しなければならないからだ。しかし、従来のクラスによる関数オブジェクトは、普通にジェネリックにできるのだ。
struct Func { template < typename T > void operator ()( T t ) const ; } ;
クラスでできるのだから、lambdaでも当然できてしかるべきだ。ジェネリックlambdaでは、これができるようになる。
C++11には時間がなかったので入らなかった機能だが、色々と文法上の問題が多い、例えば、もっとも簡単な、型を省略するという文法は、問題が多い。
[]( value ) { } ;
今回の提案では、autoを使うことになった。
[]( auto value ) { } ;
また、少しでも反対意見のあった機能は削られた。つまり、N3418で提案されていた、lambda式の本体省略は、今回の提案からは外れる。
// N3418で提案されていた本体省略機能 []( value ) value + 1 ;
これは、今の提案で書きなおすと以下のようになる。
[]( auto value ) { return value + 1 ; }
どうやら、世の中のlambda信者は、わずかなタイプ数の増加にも耐えられない人種らしく、冗長な文法を極端に嫌う。おそらく、今回のautoキーワードの記述強制は、禿げ上がるほどのストレスを与えることだろう。
それから、関数テンプレートをポインター型にキャストしたときにインスタンス化されてポインターに変換されるように、キャプチャーなしジェネリックlambdaもポインター型に変換できる。
auto (*p) ( int, int ) -> void = []( auto a, auto b ){ } ;
また、Clangベースの実験的な実装が公開されている。
Generic Lambdas in C++ using Clang by faisalv
[汚らしいPDF] N3560: Proposal for Assorted Extensions to Lambda Expressions
lambda式の改良案4つ。これもいくつかは上記のClangベースのジェネリックlambda実装で実験的に実装されている
1. lambda式でテンプレートの文法が使えるようにする(実装済み)
ジェネリックlambda式を導入するからには、やはり明示的にテンプレート仮引数を書きたい場合もある。そのために、使い慣れたテンプレートが書けるようにする。
auto f = []< typename T >( T t ) { return t ; } ;
lambdaキャプチャーと実引数リストの間に記述する。
2. lambdaの本体を式にできるようにする(実装済み)
せっかくlambdaなんだから、return文ひとつですむようなlambda式に、わざわざ冗長な{}を書きたくない。そのため、lambda式の本体を式のみの記述にできるようにする提案。
// [] ( auto x, auto y) { return x + y ; } と同じ auto plus = []( auto x, auto y ) x + y ;
これにより、こんなにかっこいいlambdaをネストしたコードが書ける。
auto curry3 = [](auto f) [=](auto a) [=] (auto b) [=] (auto c) f(a, b, c) ; auto sum = [](auto a, auto b, auto c) a + b + c ; auto val = curry3(sum)(1)(2)(3); // val = 1 + 2 + 3
ちなみに、現行のlambda式で書くとこんなに野暮臭くなる。
auto curry3 = [](auto f) { return [=](auto a) { return [=] (auto b) { return [=] (auto c) { return f(a, b, c); }}}}; auto sum = [](auto a, auto b, auto c) { return a + b + c; }; auto val = curry3(sum)(1)(2)(3);
ちなみに、クラスで書くめちゃくちゃ読みにくいので書かない。
ただし、この提案は結構な文法上の曖昧性をもたらす。一番問題になるのは、コンマがlambda式の本体の一部なのかどうかが曖昧になる文脈だろう。この提案では、括弧で囲まない限り、コンマはlambda式の本体の式の終了を意味するようになるとしている。
3. 戻り値の型の指定にもautoを使えるようにする。
ジェネリックlambda式では特に必要になる機能だ。
auto L = [=](auto f, auto n) -> auto & { return f(n) ; } ; auto M = [=](auto f, auto n) -> auto * { return f(n) ; } ; auto N = [=](auto f, auto n) -> auto { return f(n) ; } ;
また、decltype(auto)のような使用方法も許されるらしい
4. ジェネリックlambda式でVariadic auto仮引数
[]( auto ... args ) { } ;
まあ、見ての通りの機能。
また、今回の提案では、時間が足りないため、lambdaの文法を通常の関数にも広げるだとか、名前付きlambda、通常の関数におけるauto仮引数などの機能はパスするそうだ。C++1yにはぜひとも欲しい機能だ。
[カァーッペッDF] N3561: Semantics of Vector Loops
この論文の著者は先月の会議で、ベクトルループ内のクリティカルセクションは未定義動作が当然ということを話したが、そもそもベクトルループってよく分からんから解説してくれよと言われたので、その解説の論文。パラレルループよりも制約が多く難しいベクトルループを解説している。
[また性懲りもなくPDF] N3562: Executors and schedulers, revision 1
関数オブジェクトで表現される細かい処理群を実行するexecuterライブラリの提案。このライブラリはthread poolでもあるが、もっとスレッドプール以外の一般の処理をも扱うそうだ。
[こんなにPDFが多いのは何かの陰謀か] N3563: C++ Mapreduce
mapreduceライブラリの提案の改訂版。
[あのなぁピー坊] N3564: Resumable Functions
Resumable functionの提案。
名前の通り、途中で中断し、その後中断したところから実行を再開する関数。これはfutureとの合わせ技で使う。
たとえばこんなコード
future<int> f(shared_ptr<stream> str) { shared_ptr<vector<char>> buf = ...; return str->read(512, buf) .then([](future<int> op) { // lambda 1 return op.get() + 11; }); } future<void> g() { shared_ptr<stream> s = ...; return f(s).then([s](future<int> op) { s->close(); } ); }
ストリームからの読み出しが完了してからなにか処理をしたい場合、これまでは関数を分けて指定子なければならなかった。それが、
future<int> f(stream str) resumable { shared_ptr<vector<char>> buf = ...; int count = await str.read(512, buf); return count + 11; } future<void> g() resumable { stream s = ...; int pls11 = await f(s); s.close(); }
このように簡潔に書けるようになる。
resumable関数が中断した時点で、呼び出し元に処理が戻る。呼び出し元は、戻り値のfutureのオブジェクトを経由して結果にアクセスする。futureの完了を待つという事は、つまり中断した関数の実行が、ストリームからの読み出しが終了した後、再開する。
resumable関数は、明示的にresumable文脈依存キーワードを指定する。さらに、中断箇所ではawait(キーワード、もしくはresumable関数内の文脈依存キーワード)を指定する。戻り値の型は、futureかshared_futureでなければならない。
resumable関数の呼び出し側としては、通常のfutureを返す関数と何らかわりなく使える。
論文ではさらに、実装方法について説明している。教育が難しそうな機能だ。
[前に論文はHTMLで書こうっていうN3325の勧告はどうした] N3565: IP Address Design Constraints
IPアドレスを扱うライブラリの設計の議論の次第を紹介する論文。これは最終決定ではなく、あくまで議論の内容の要約である。
まず、IPアドレスを扱うクラスについてだ。現在、IPv4アドレスとIPv6アドレスがある。この2つのアドレスをどうやってクラスで表現するかという問題がある。
まず、どちらのアドレスもひとつのクラスで表現する設計。これはRFCでも推奨されている設計である。また、将来新たなアドレスが登場した時でも、既存のクラスを対応させればいい。ただし、これにはパフォーマンス上の問題がある。というのも、IPv4とIPv6では、表現に必要なオブジェクトのサイズが異なるから、無駄が生じるのだ。
IPv4とIPv6を表現するクラスを分けるのは、RFC非推奨であり、さらに将来性が暗い。ただしパフォーマンスの問題を解決できる。
折衷案として、IPv4とIPv6でクラスを分けるが、ただし相互に変換可能にする設計案がある。IPv6からIPv4への変換は実行時に例外を投げる。IPv4からIPv6への変換は、常に成功する。これはパフォーマンスも良く、将来性もある設計だが、複数の型が必要で、やはりRFC推奨ではない。
既存の実装であるASIOライブラリは、3つのクラスを使う設計をしている。つまり、IPv4/IPv6を表現するクラスと、そのクラスを統括するクラスだ。これはパフォーマンスの問題はない。ただし、型が複数となり、中途半端にジェネリックで、RFC非推奨だ。
AISOの設計を発展させて、3つ目の管理クラスをunionにする案。これはパフォーマンスがよく、完全にジェネリックな設計だが、型が複数となり、RFC非推奨だ。
まあ、IPV4ごときを格納するのにIPv6分のメモリを浪費したくないのはC++ならではといったところか。
N3566: C++ Standard Evolution Active Issues List
C++ Standard Evolution Closed Issues List
今回から新しくできたC++のEvolution(拡張)のissueリスト。なかなか興味深いものが並んでいる。
N3568: Shared Locking Revision 1
multiple-readers / single-writer locking patternのためのライブラリ提案の改定案。
ストリームライブラリにquotedマニピュレーターを追加。これは空白を含む文字列をそのまま出力、入力する設定に切り替えるためのマニピュレーターである。
Boostにも同様のものが数年前からある。
Boost "quoted" I/O manipulator - 1.53.0
C++にSIMDライブラリを追加する提案。
SIMDとは、例えば128bitの大きなレジスタを用意し、そのレジスタを32bitの整数4つとみなして同時に4つの32bit整数に対して演算を行うことである。最近はレジスタ長も増えており、たとえばIntelのAVXは256bit長だし、Intel MICは512bitもの長さのレジスタがあるという。
ベクトル演算はうまく使えば素晴らしい性能をひき出すが、そのうまく使うという事が難しい。直接アセンブリを書くのが最も手っ取り早いが、アセンブリ言語は書きにくく、移植性も低い。
コンパイラーでコードを解析し、ベクトル化できる部分を自動的にベクトル化するということは、もうだいぶ昔から行われている。しかし、残念ながらいまだにコンパイラーはそれほど賢くない。
独自拡張のプリプロセッサマクロやキーワードなどを使い、ベクトル化のためのヒントを記述する実装も多数ある。しかし、これは移植性が低い上に、やはり使いづらい。
Compiler Intrinsicといって、レジスターを型として提供し、SIMD命令を薄いC言語の関数として提供する機能もある。これは厄介なレジスター管理をコンパイラーに任せることができるが、すべてが薄い関数のラッパなので、コードがとても分かりにくい。C++らしいライブラリが求められる。
このため、C++でSIMD演算用の薄いラッパークラスとしてのライブラリが提案されている。これはBoost.SIMD(現時点では、まだBoostに採用されていない)を元にしたものである。
このライブラリは、SIMDレジスターをクラステンプレートで提供する。クラステンプレートはテンプレート引数から適切な長さのレジスター(あるいは複数のレジスター)を使う。演算子オーバーロードにより、演算は通常の式で書ける。
また、SIMD版のtransform/accumulate(イテレーターの代わりにポインターを取る)アルゴリズムも提供される。
また、このSIMDライブラリ用に適切にアラインされたストレージを確保するアロケーターも提供される。
このライブラリの実装には、コア言語側に追加の機能は必要ない。ただし、Compiler Intrinsicのような、コンパイラー側のサポートが必要となる。
保守的で職人気質な人間の多い動画業界では、Compiler Intrinsicはいまだに鼻で笑われているが、まあ、彼らはHD動画をリアルタイムでエンコード、デコードできるよう強い圧力を受けているので、しかたがないのだろうか。あるいは、Intrinsicから生成されるコードは、まだまだ手書きよりはるかに劣るのか。
N3572: Unicode Support in the Standard Library
Unicodeライブラリの提案。エンコード情報付きの文字列クラスや、コードポイントの種類取得(数値、文字、シンボル、句読点などなど)の機能を提供するライブラリとなっている。
N3573: Heterogenous extensions to unordered containers
どうも説明不足でよくわからない。unorderedコンテナのキーとして別の型を使えるようにする変更と、ハッシュ関数を検索ごとに変えられるようにする変更のようだが。
N3574: Binding stateful functions as function pointers
関数オブジェクトを関数ポインターに変換するthunkライブラリ、bound_functionの提案。このbound_functionはextern "C"リンケージのC関数ポインターへの変換関数を持つ。bound_functionの提供するC関数ポインターを呼び出すと、bound_functionのコンストラクターに渡されたオブジェクトをthisとしてoperator ()を呼んでくれる。
どうしても既存のC関数ポインターが必要なライブラリで、statefulな関数オブジェクトを使いたい需要は相当にある。BoostのVaultに、ユニークな型を渡せば、ユニークなstatic変数にオブジェクトのアドレスを格納して関数ポインターを提供するクラステンプレートが転がっていたが、このライブラリはそのような必要もなく、しかもextern "C"リンケージの関数ポインターを変えす。
おそらく、実装にはコンパイラーマジックが必要になると思うのだが。
N3575: Additional Standard allocation schemes
一般的に使われている各種の特性を持つ、汎用ではないストレージの確保解放のためのライブラリの提案。これはアロケーターではなく、アロケーターを実装するための低レベルのライブラリとして設計されている。
まずはheap。heapオブジェクトは、確保されたストレージを所有する。heapオブジェクトを破棄すれば、そのheapオブジェクトから確保されたストレージはすべて解放されたことになる。
また、このheapのthread unsafeに使うためのアダプターであるunserialized_heap_allocator。このアダプターを経由してheapを使った場合、スレッドセーフにするための排他的な保護が行われない。これにより動機のオーバーヘッドを回避できるが、複数のスレッドから同時にunserialized_heap_allocatorを使った場合の挙動は未定義である。
小さく同一のサイズのストレージを大量に確保、開放する利用例に最適化されたobject_poolと、object_poolの同期処理を無効化するアダプターであるunserialized_pool_allocator。
そしてarena。これは連続して確保した一連のストレージを一気に開放するワーキングメモリーような用途に最適化された戦略を取る。関連するストレージの境目は、gateクラスのオブジェクトを破棄することで指示する。そして、gateを作らずに解放しないストレージを確保するarena_allocator(これはよくわからない)。また、arenaはデフォルトでthread unsafeらしく、スレッドセーフな確保をしたければアダプターのconcurrent_arenaを使う必要がある。このconcurrent_arenaには、lock_freeというstaticデータメンバがあり、concurrent_arenaの実装がロックフリーの環境であればtrueとなるそうだ。そしてconcurrent_arena_allocator。
論文では、標準ライブラリのコンテナーのデフォルトアロケーターの指定を取り除く提案もしている。これにより、実装はコンテナーごとに異なる戦略のアロケーターを使うことができ、パフォーマンスの向上につながる。ただし、std::allocatorがデフォルトであることをあてにした既存のコードを壊してしまうという互換性の問題があるのだが。
[PDFの悪夢再び] N3576: SG8 Concepts Teleconference Minutes - 2013-03-12
会議の議事録。static ifの作業を少し送らせて、当座はconcept liteに注力することや、concept lite関連の議論が主な内容。concept liteの目標をC++14にするのかC++17かTSかという、とても気になる議論は、まだ決められるわけがないということで投票もなし。
[HTMLが恋しくなるPDF] N3577: Fall 2013 JTC1/SC22/WG21 C++ Standards Committee Meeting
2013年の9月にシカゴで行われる会議の会場となるホテル設備の案内。
[PDFである必要はあんまりない] N3578: Proposing the Rule of Five
ユーザー定義されたデストラクター、ムーブコンストラクター、ムーブ代入演算子があるときは、コピーコンストラクターとコピー代入演算子は暗黙に生成されない。ただし、C++11ではdeprecated扱いながら生成される。
C++14では、このdeprecatedな挙動を規格から完全に取り除こうではないかという提案。規格1つ分の間、警告したのだから、十分な準備期間を与えたはずだ。
C++では、昔から三原則(The rule of Three)と非公式に呼ばれている原則があった。これは、デストラクター、コピーコンストラクター、コピー代入演算子のうち、どれか一つでも明示的に定義したならば、残りの二つもおそらく定義する必要があるだろうという原則だ。
この原則に、ムーブコンストラクターとムーブ代入演算子も加わるので、五原則となる。
N3569: A type trait for signatures
与えられた仮引数の型を実引数に与えて呼び出した場合に呼び出される関数型を返すtype traits、std::signatureの追加。
struct C { int operator()(double d, int i); int operator()(double d1, double d2); } ;
このような関数オブジェクトCがあったとして、std::signature<C(int, int)>::typeは、int (double,int)になる。何故か論文ではint(double,double)を返すと書いているが、これは誤りである。
いわば、オーバーロード解決の結果の関数型を返すメタ関数だと考えれば良い。このメタ関数は、よりパーフェクトなforwardingを実装するのに使える。たとえば、std::threadやstd::asyncのようなライブラリは、リファレンスはコピーして渡す。これは、現行のC++にstd::signatureがなく、実際に呼び出される関数の仮引数の型を取得できないためである。std::signatureがあれば取得できるので、よりパーフェクトなforwardingが実現できる。
この提案は思わず笑ってしまった。こうきたか。ぜひとも入って欲しい。
[PDF死んでくれ] N3580: Concepts Lite: Constraining Templates with Predicates
この論文のサンプルコードはコンパイラーで検証されていないらしく、文法誤りが多すぎる。
C++11には却下されたConcept機能のうち、テンプレート利用者側が与えるテンプレート実引数の要件チェックだけに焦点を絞ったConcept Liteの提案。
一言で言ってしまえば、コンパイル時計算の結果次第で、実体化されたテンプレートの特殊化を利用できないようにする機能だ。
C++11で提案されたコンセプトはとても膨大だった。テンプレート利用側のテンプレート実引数の型が要件を満たしているかどうかの検証はもちろんのこと、テンプレートコードが要件を満たしているかどうかの検証や、要件を満たさない型に対して、要件を満たすようにマップするConcept Mapや、仕様を直接コードとして記述するaxiomを含む大掛かりなもので、しかもテンプレート自体を、旧来のコンセプト検証を行わない、いわばuncheck template(unconstrained template)と、コンセプト検証を行うcheck template(constrained tempalte)に分断するというものだった。
今回はConcept Lite(軽量コンセプト)と題して、すでに書いたように「テンプレート利用者側が与えるテンプレート実引数の要件チェック」だけを提供する機能となる。一気に入れるのではなく、段階的に完全なコンセプトをいれる予定だ。
この説明だけではわからないだろうから、簡単な例で説明しよう。
struct X { X( int ) { } ~X() { } } ; template < typename T > void f( ) { T t ; // デフォルトコンストラクターとデストラクターが必要 } int main() { f<X>() ; // エラー }
このコードがエラーとなる理由は、関数テンプレートfで、テンプレート仮引数Tにはデフォルトコンストラクターとデストラクターを要求しているのに、クラスXにはデフォルトコンストラクターがないからだ。
残念ながら、このコードをコンパイルしても、わけのわからないコンパイルエラーが表示される。
なんとかして、テンプレート仮引数Tには、デフォルトコンストラクターとデストラクターが必要であるという事を明示できないか。そうすれば、エラーメッセージはもっとわかりやすくなる。
それを可能にするのが、今回のConcept Liteだ。以下のように書ける。
template < typename T > constepxr bool DefaultConstructible() { return std::is_default_constructible<T>::value ; } template < typename T > constexpr bool Destructible() { return std::is_destructible<T>::value ; } template < typename T > requires DefaultConstructible<T>() && Destructible<T>() void f() { T t ; }
このように、boolを返すconstexpr関数で、デフォルトコンストラクターとデストラクターが必要だということを明示的に記述できる。この機能があれば、コンパイルエラーは、「テンプレート仮引数Tに対するテンプレート実引数XはDestructibleを満たさない」という、非常に分かりやすいものにできる。
また、これはコンパイルエラーにするための機能ではない。関数テンプレートに適用した場合は、オーバーロード解決の候補から外すという機能となっている。そのため、汎用的すぎて誤爆してしまう関数テンプレートを候補から外すこともできる。これはライブラリ実装者にはとても重宝する機能である。従来のタグディスパッチや意図的にtemplate substitutionを失敗させる変態的な技法を使わずともすむのだ。しかも、コンパイル速度は従来の技法より速くなる。
もちろん、これは全てコンパイル時に行われるので、実行時には何の影響もない。
クラステンプレートやエイリアステンプレートにも、この機能は使える。また、変わったところでは、クラステンプレートのメンバーにも使える。
template < typename T > struct X { requires hoge<T>() void member1() ; requires hage<T>() void member2() ; } ;
また、簡単なコンセプトは、requiresを使わず、直接テンプレート仮引数のclass/typenameキーワードの代わりに書ける。
template < DefaultConstructible T > void f() ;
ところで、そのコンセプトの要件定義に使うconstexpr関数だが、まともに書こうとするとつらい。例えば、operator ==とoperator !=で等価比較できる型の要件定義は以下のようになる。
template<typename T> concept Equality_comparable() { return has_eq<T>::value && is_convertible<eq_result<T>, bool>::value && has_ne<T>::value && is_convertible<ne_result<T>, bool>::value; }
明らかに、このconstexpr関数はとてつもなく書きにくいし読みにくい。has_eqはTにoperator ==が適用できるときにtrueを返すメタ関数であり、has_neはそのoperator !=版だ。さらに、is_convertibleでoperator ==や!=の式を評価した結果の型がboolに変換可能かどうかを確かめている。
要件定義をもう少し自然に書けるよう、requires expressionが提案されている。
template<typename T> constexpr bool Equality_comparable() { return requires (T a, T b) { bool = {a == b}; bool = {a != b}; }; }
このように、自然に書くことができる。
requires式は、文法要件を記述するための式である。便宜上の型のオブジェクトを定義し、そのオブジェクトに対して、任意の式を記述できる。その式が、テンプレートが実体化されて実際に使われた場合に合法である場合のみ、trueを返す。requires式内のすべての式がtrueの場合のみrequires式はtrueを返す。
もちろん、式が妥当かどうかを検証するのみならず、式を評価した結果の型が、ある型に変換可能かどうかも検証できる。この例では、boolに変換可能かどうかを調べている。
また、エイリアステンプレートを使うことで、ネストされた型名があるかどうかも調べられる。
template < typename T > using Value_type = T::value_type ; template < typename T > constexpr bool Readable() { return requires ( T t ) { typename Value_type<T> ; const Value_type<T> & = { *t } ; } ; }
このコードは、エイリアステンプレートValue_typeを介して、T::value_typeがあるかどうか、そして、T型のオブジェクトにoperator *を適用した結果が、T::value_typeへのconstなlvalueリファレンス型に変換可能かどうかを調べている。
この提案は、完全なConceptをC++に提供する第一歩となる。
論文によると、将来的には完全なコンセプトを提供するにとどまらず、セマンティックも言語で表現できるようにしようという野望があるらしい。またaxiomみたいなのを提案するのだろうか。
論文ではconstrained template同士の優先順位や同一性の比較方法を論じているが、いやはや、たしかに、宣言やpartial orderingやオーバーロード解決で使われる以上考えなければならないが、とてもむずかしい。最終的には、atomic propositionに分けて比較。constrained templateは、nonconstrained templateより優先されるらしい。
template<Floating_point T> class complex; // #1 template<typename T> requires Floating_pont<T>() class complex; // #2 template<typename T> requires is_same<T, float>::value || is_same<T, double>::value || is_same<T, long double>::value class complex; // #3
この例で、#2は#1の再宣言である。もし、Floating_pointの定義が#3のatomic propositionsに分解できるならば、#3も同じ宣言だということになる。
異なるconstraintsは別の宣言になるので、たとえばdistanceをイテレーターカテゴリーに応じて宣言することも可能になる。
template<Input_iterator I> ptrdiff_t distance(I first, I last); // #1 template<Random_access_iterator I> ptrdiff_t distance(I first, I last); // #2
クラスはもともとオーバーロードできないが、特殊化は複数記述できるので、似たようなことができる。
template<Arithmetic T> class complex; template<Floating_point T> class complex<T>; // #1 template<Integral T> class complex; // #2 complex<int> g; // Selects #2
オーバーロード解決のルールも変わる。constrained templateの場合は、インスタンス化の前に要件チェックが入るし、best viable functionを選択する際にも、もっともconstrainedなテンプレートが選ばれる。partial specilizationも概ね似たような形になる。
もちろん、型だけではなく、非型にもコンセプトが適用できる。
論文ではさらに実装経験について触れている。GCCベースの実験的な実装では、is_sameなどのtype traitsをコンパイラー支援を受けたIntrinsicを導入し、<type_traits>を大幅に書き換えたところ、25%のコード量削減を実現したそうだ。コンパイル速度については、まだ計測していないが、インスタンス化の回数が減ったことにより、向上するのではないかと見込まれている。
ちなみに、この論文で提案されているConcept Liteの実験的な実装が以下から手に入る。GCCを土台にした実装となっている。
http://concepts.axiomatics.org/~ans/
デリミタ付きでostreamに流しこむdelimited_iteratorの提案。ostream_iteratorは、デリミタではなくてサフィックスなので、最後の要素にも出力されてしまう。
N3582: Return type deduction for normal functions
普通の関数にも戻り値の型推定を行う提案の改定案。前回のN3386からの変更点は、
decltype(auto)の追加。これにより、リファレンスなどを正しく扱える。
template < typename T > auto f( T const & x ) -> decltype(auto) & { return g(x) ; }
なお、このdecltype(auto)は、一貫性を保つため、autoが使える文脈ならばどこでも使える。
trailing-return-typeでautoの使用
[]() -> auto & { return f() ; }
std::initializer_listの推定の禁止。
std::initializer_listはautomatic storage durationをもつストレージ上に確保されるので、関数の戻り値に使うというのはありえない。そこで、推定されないようにする。
[PDFを使う理由が微塵も感じられない] N3583: Exploring constexpr at Runtime
constexpr関数は、コンパイル時にも実行時にも使える。しかし、これはとても深刻な問題を引き起こす。
一つには、コンパイル時に検出できるはずのエラーが、実行時エラーになってしまうということだ。
constexpr int inverse( int v ) { return 1 / v ; } int main() { // コンパイル時エラー // ゼロ除算 constexpr int c = inverse(0) ; }
このコードは、ゼロ除算を行うためにエラーとなる。もちろん、inverseはコンパイル時に実行されるので、コンパイル時にエラーになる。しかしもし、変数の定義がconstexprではなくconstだったらどうなるだろうか。
// GCC 4.7とClang 3.2でコンパイル成功 // 実行時エラー const int r = inverse(0) ;
今のところ、GCC 4.7でも、おそらく4.8でも、Clang 3.2でも、このコードはコンパイルが通ってしまう。理由は、inverseの実行は実行時に遅延されるからだ。これは規格準拠の実装である。constの場合は、constexpr関数をコンパイル時実行しなくてもいいのだ。もちろん、規格はこの場合においてコンパイル時実行することも許容しているが、保証していない。現在知る限り、このコードをコンパイル時実行するC++コンパイラーは存在しない。
コンパイラーがいずれもこのコードをコンパイル時実行しないのには理由がある。コンパイル時間の短縮のためだ。実際に評価するまでコンパイル時定数になるかどうかも分からないのに、わざわざコンパイル時定数ではなくてもいい場所で、コンパイル時実行する価値はない。
しかし、inverse(0)という式自体は、コンパイル時にエラーかどうか判定できる式である。これは問題だ。
もうひとつは、constexpr関数は、必ずしも実行時実行して優れた実装であるとは限らないことだ。例えば、平方根を計算するconstexpr関数を考えよう。平方根の計算は、一般にBabylonian methodと呼ばれる方法で計算する。
これをconstexpr関数で実装すると、再帰関数になるが、それは実装上の詳細なので省略する。
問題は、このconstexpr関数実装のsqrtは、ほとんどの環境で、標準のstd::sqrtより格段に遅いということだ。なぜかというと、多くの実装のstd::sqrtは、実行環境独自の命令などを使い、高速に平方根を計算する実装になっているからだ。まともに計算して勝ち目はない。
現状では、コンパイル時sqrt関数と、実行時sqrt関数は、別の名前をつけて使い分けなければならない。
このふたつの問題をなんとか解決できないものか。それには、constexpr関数をコンパイル時にしか評価できないように制限するとか、あるいはコンパイル時と実行時で実装を選択できるようにするとかの機能が必要だ。では、どのような機能があればいいのか。
案A:qualifierやattributeによるコンパイル時実行の強制
まず思いつくのは、qualifierやattributeによって、constexpr関数をコンパイル時実行のみに限定する機能だ。これは少なくとも、コンパイル時にエラーを検出したいというinverseの問題は解決してくれる。
ただし、やはりコンパイル時と実行時で別の名前を使わなければならないので、sqrtの問題は解決できない。しかも、constexprコンストラクターは、別名をつけることが出来ないので、コンパイル時と実行時で別名のクラスを使わなければならない。
案B: 末尾再帰保証
constexpr関数の再帰を、末尾再帰のみ許すように制限して、規格で実装に末尾再帰の最適化を保証させる。これはSchemeに影響されている。
sqrtの問題を直接解決は出来ないものの、末尾再帰保証によって、汎用的な処理ならば、実行時関数と同等のパフォーマンスを持つ実装にできる。しかし、下位互換性を失ってしまうし、問題の本質的な解決にはならない。
それに、論文では触れていないが、constexpr関数を拡張して、ループを含む任意の文を記述できるようにしようという議論がある。
休憩:constexprのコンパイル時実行の優先保証
constexpr関数をコンパイル時実行できる文脈ならば、かならずコンパイル時実行されるように規格で保証してはどうか。これは、この次の二案を実装するにあたり、重要になる。
案C: traitsやconceptによる、実装選択
traitsやconceptによって、コンパイル時と実行時で実装を選択できるようにしてはどうか。たとえば、コンパイル時実行されている場合にtrueを返す、std::evaluated_during_translation()のようなものを追加してはどうか。
constexpr int inverse( int v ) { static_assert( std::evaluated_during_translation(), "inverse may not be used at runtime. " "Please declare the target variable as constexpr." ) ; return 1 / v ; }
と、このようにコンパイル時にstatic_assertなどでエラーにすることもできるし、コンパイル時に実装を選択することもできる。
しかし、こんなtraitsを導入したら、当然SFINAEで使うやからが出るにきまっている。
template <typename T> constexpr typename std::enable_if<std::evaluated_during_translation(), T>::type sqrt(T value) { /* コンパイル時実装 */ } // 実行時実装 template <typename T> typename std::enable_if<! std::evaluated_during_translation(),T>::type sqrt(T value) { std::sqrt(value); }
問題は、現在主流のコンパイラーのテンプレートの実装は、一箇所だけでインスタンス化して、その結果を保持して使いまわすというところにある。これでは擬似コイントスをしているに過ぎない。コンパイル時と実行時のどちらの文脈でインスタンス化されるかによって結果が変わるのであって、利用箇所すべてで結果が変わるわけではない。
しかも、このようなコードはODR違反となる。
そのため、このようなtraitsを導入するならば、SFINAEの文脈での扱いについて、相当の考慮が必要だ。
案D: constexprをオーバーロード可能なシグネチャにする。
現在、constexpr qualifierは、非staticメンバー関数のみで、オーバーロード可能だ。
// Overload non-static non-const class member on constexpr compiles struct test_a { bool is_constexpr() { return false; } constexpr bool is_constexpr() { return true; } // OK };
といっても、これは実は、constexprというシグネチャでオーバーロードしているのではない。constexprは、非staticメンバー関数のconst qualifierもかねるので、それでオーバーロード出来るだけだ。
// Overload member on both const and constexpr fails struct test_b { bool is_constexpr() { return false; } bool is_constexpr() const { return false; } constexpr bool is_constexpr() { return true; } // エラー }
constexprかどうかでオーバーロードできるようにすれば、コンパイル時と実行時の実装が分けられて都合がいいのではないか。
論文では、オーバーロードかつコンパイル時実行できる文脈でのコンパイル時実行保証が一番お気に入りのようだが、明確な結論は出していない。思うに、この問題は、もっと議論が必要だろう。
N3584: Wording for Addressing Tuples by Type
Tupleの要素に型でアクセスできるようにする提案。
tuple<string, string, int> t("foo", "bar", 7); int i = get<int>(t); // i == 7 int j = get<2>(t); // 上と同じ。j == 7 string s = get<string>(t); // コンパイル時エラー、曖昧。
なんだかTupleがコンパイル時setみたいだ。
[PDF地獄] N3585: Iterator-Related Improvements to Containers (Revision 2)
イテレーター関連のライブラリの細かい改良提案の改訂版。前回のN3450との違いが書いてないが、とくに提案の違いはなさそうだ。
もう一度紹介すると、コンテナの最後の要素を返すメンバー関数last()、特定のコンテナのオブジェクトには紐付けられていないnullイテレーター、mapイテレーターで、pairのかわりに、keyだけ、valueだけを返すイテレーターアダプターのselector、イテレーターとインデックスの相互変換機能。
[PDF煉獄] N3586: Splicing Maps and Sets
associative containerとunordered associative containerにsplice機能を追加する提案。
spliceというのはstd::listの機能で、listのオブジェクトのノードを、別のlistのオブジェクトに挿入できる機能である(ただし、アロケーターが等しくない場合の挙動は未定義)。listはその仕組み上、アロケーターの互換性さえあれば、内部のノードの所有権を別のlistのオブジェクトに動かすことができる。
これはムーブとは違う。ムーブとは代入元を破壊する代入であり、クラスがポインターやハンドルなどのリソースを参照するようなメンバーを持つ場合にはパフォーマンス上の利点があるが、クラスそのもののサイズが大きい場合には、役にはたたない。たとえ、クラスのサイズはそれなりでも、何百万個もオブジェクトを作るような場合には、確保解放を回避するというのは、パフォーマンス上とても重要である。emplaceは、オブジェクトを構築するときにはいいが、やはりオブジェクトの境を超えるとストレージの確保、解放が発生する。list同士ならば、spliceでノードの所有権を融通し合う事で、とても効率のいい移動が行える。
と、これがlistのspliceだ。しかし連想コンテナの場合はどうだろうか。まあ、連想コンテナでも内部のノードの融通はできる。しかし、それ以上のことがしたい。
たとえば、mapのキーを変更したいとする。従来は、新たにキーと値のペアを作って挿入し、古いペアは消すしか方法がなかった。なんとか確保したストレージはそのままに、その上でキーを変更できないだろうか。
このために、連想コンテナとunordered連想コンテナに、removeというメンバー関数を新たに追加する。このremoveは、eraseに似ていて、選択したノードのunlinkとそれに伴う内部実装のツリーのリバランスを行うが、ノードのストレージを解放しない。開放する代わりに、node_ptrというunique_ptrに似た独自のスマートポインターにノードの所有権を移し、戻り値として返す。このnode_ptrは、ノードへのポインターと、アロケーターのコピーを保持する。これにより、元のコンテナーの寿命が付きた後でも、ノードだけ生き延びることができる。また、insertにもnode_ptrのオーバーロードを追加することにより、node_ptrのコンテナへの挿入は、node_ptrが所有するノードの所有権を移す処理になる。node_ptrとして一旦とり出せば、ストレージの再確保なしで、キーや値を変更をして、元のコンテナーに割い挿入できるし、別のコンテナーに挿入することもできる。
なお、ノードが見つからなかった場合は、空のnode_ptrが返される。空のnode_ptrを挿入しようとしても、何も起こらない。
たとえば、mapのあるノードを別のオブジェクトに移す場合は以下のようになる。
map<int,string> src, dst; src[1] = "one" ; src[2] = "two" ; dst[3] = "three" ; // イテレーター版 dst.insert(src.remove(src.find(1))); // キー版 dst.insert(src.remove(2));
コンテナの前要素のマージも可能だ。
set<int> src{1, 3, 5}; set<int> dst{2, 4, 5}; dst.insert(src); // srcをdstにマージ // src == {5} // dst == {1, 2, 3, 4, 5}
キーの値を変えて再挿入もこのとおり。
struct record { ... }; typedef map<int, record> table_type; table_type table; table.emplace(38, ...); auto elem = table.remove(38); elem->first = 97; table.insert(move(elem));
これはすばらしい提案だと思う。ぜひとも入るべきだ。
[PDFヘル] N3587: For Loop Exit Strategies
この論文の筆者は、常に以下のようなコードを書いている。
auto i = c.begin(); // Unfortunate that i is required here. for (; i != c.end(); ++i) { if (some_condition(*i)) break; do_something(*i); } if (i == c.end()) // Extra test here. { do_stuff(); } else { do_something_else(*i); }
つまり、ループを途中で終了したかどうかによって、その後の処理内容を分ける必要のある処理だ。その際、中断した場所のイテレーターも欲しい。
このコードはどうみてイケてない。イテレーターiをfor文のスコープ外で使えるようにするため、外で宣言しなければならないからだ。Range-based forは、イテレーターを外で宣言する方法がないため、もっとイケてないコードになる。そのため、筆者はこのような場合にrange-based forは使わない。
この問題は、for文を抜ける際に、通常終了したか、途中終了したかによって実行される文を指定できれば解決できる。
for ( ... ) { // ループ本体 } then { // 通常終了:ループの条件がfalseになった } else { // 途中終了: breakで抜けた }
残念ながら、いまさらthenなどというキーワードを導入するわけには行かない。そこで、この論文では、以下のような文法を提案している。
if for ( ... ) { // ループ本体 } { // 通常終了:ループの条件がfalseになった } else { // 途中終了: breakで抜けた }
論文ではこれが実現可能な文法だと主張しているが、正直キモい。ちょっと考えたdあけで、以下のような文法が浮かんだのだが、なにか問題があるだろうか。
for ( ... ) { // ループ本体 } break { // 途中終了: breakで抜けた } else { // 通常終了:ループの条件がfalseになった }
順番は逆になり、elseの意味も変わったが、こっちほうが分かりやすいと思うのだが。この文法ならば、breakかelseだけを記述することも可能にならないだろうか。ただし、breakなしでelseだけだとさすがに分かりにくいだろうか。それならばcontinueはどうか。
for ( ... ) { // ループ本体 } break { // 途中終了: breakで抜けた } continue { // 通常終了:ループの条件がfalseになった }
あるいは文脈依存キーワードfinalは
for ( ... ) { // ループ本体 } break { // 途中終了: breakで抜けた } final { // 通常終了:ループの条件がfalseになった }
何にせよ、文を何の区切りもなくふたつ続けてかくif forの文法は分かりにくい。この問題は、さらに文法上のバイク小屋議論が必要であろうと思う。
標準ライブラリはshared_ptrのためのmake_sharedを提供しているが、unique_ptrのためのmake_uniqueを提供していない。make_uniqueは簡単な実装だが、Variadic Templateやperfect forwardingを正しく使うのは、初心者には難しい。make_unique<T[]>はもっと難しい。標準で提供すべきだ。
というわけで、make_uniqueの提案。これによりC++初心者に「new/deleteは絶対使うな」ということができる。
[PDFパーガトリー] N3589: Summary of Progress Since Portland towards Transactional Language Constructs for C++
[PDF注意] N3591: Summary of Discussions on Explicit Cancellation in Transactional Language Constructs for C++
Transactional Memoryに関する議論において、合意のとれたことを要約している。
N3592: Alternative cancellation and data escape mechanisms for transactions
Transactional Memoryのキャンセル機能の提供方法について考察している。
N3510の改訂版。文字列をデリミタで区切って分割するstd::splitの提案。
前回からの変更点は、std::splitは型変換によらず、直接Rangeを返すようになったこと。std::splitはstd::string_refのみを返す。std::stringへの変換はユーザーの仕事。splitの結果をフィルターしたりする機能は取り除かれた。同等の機能は汎用的なRangeアダプターライブラリで提供されるべきだとしている。
文字列を連結するstd::joinライブラリの提案。文字列の他にも、intなどの基本型や、N3609で提案されているstring_viewに対応した型を文字列として変換して連結できる。また、型から生成される文字列の書式を実装できるFormatterという機能も提供している。
とうとうストリームから独立した型安全のprintf代替ライブラリがでてきたか。
[うざいPDF] N3595: Simplifying Argument-Dependent Lookup Rules
またもやADL改造論。今回は、「そもそもテンプレート実引数の型が属する名前空間がassociated namespaceに追加されるのはおかしいだろ、何の役に立つんだよ? やめろよ」ということで、廃止する。誰もまともな使い方を挙げられなかったそうだ。たしかに、まともな使い方がわからない。
もし、従来のADLの挙動がほしければ、attributeで指定する。
// N3595提案 template < typename T > struct X { } ; template < typename T > struct [[full_adl]] Y { } ; namespace ns { template < typename T > void f( T ) { } struct M { } ; } int main() { X<ns::M> x ; f( x ) ; // エラー、fが見つからない Y<ns::M> y ; f( y ) ; // OK、ns::fを発見 }
また、テンプレート仮引数が複数ある場合、個々に設定することもできる。
// N3595提案 template < typename [[full_adl]] T, typename U > struct X { } ;
この例では、Tの属する名前空間はassociated namespaceに追加されるが、Uの属する名前空間は追加されない。
また、現在のADLのルールはおかしいところがある。関数テンプレートのテンプレート実引数を明示的に渡すとADLが無効になってしまうのだ。
namespace ns { struct X { } ; template < int hint > void f( X ) { } } int main() { ns::X x ; f<0>( x ) ; // エラー、ADL無効 }
このような場合は、ADLが有効になってほしい。このような場合、ADLが無効にならないように変更する。
inline frined関数の適正化
クラス内でfriend関数を定義した場合、見つかる順番がややこしい。クラス内で定義しようがしまいが、name lookupの挙動を同じにする。
ADLの無効化
現行規格では、関数名を括弧で囲むと、ADLが無効になる。
(f)( x ) ; // ADL無効
これは非常に分かりにくい。そのため、ADLを無効化するattribute、[[no_adl]]を追加する。
[[no_adl]] f ( x ) ; // ADL無効
さらに、ADLを選択的に無効化することもできる。
f( [[no_adl]] x, y , [[no_adl]] z ) ;
この例では、実引数xとyの型の属する名前空間を、associated namespaceに追加しないようにするが、yの型については通常通りassociated namespaceに追加する。
過去に何度もADL改造の提案は出されてきたが、はたして今回は通るのだろうか。
[キモいPDF] N3596: Code Reuse in Class Template Specialization
クラステンプレートの特殊化に関連する、コードの重複を防ぐためのふたつの機能の提案。
ひとつ、クラステンプレートの明示的特殊化や部分的特殊化は、たいていこんな感じになる。
template < typename T > class my_class { public : using value_type = T ; value_type f() { /* 実装 */ } // その他、多数のメンバーと実装 } ; template < typename T > class my_class< std::complex<T> > { public : // 一部のメンバーの実装だけ変更 using value_type = std::comlex<T> ; value_type f() { /* 特別な実装 */ } // 残りはprimary templateと全く変わらない多数のメンバーとその実装。 } ;
この例では、std::complexに対して何か特殊化すべきことがあるが、ほとんどのメンバーとその実装は変わらないということだ。つまり、コードが重複してしまう。しかも、ただ重複するだけではなく、primary templateでTだった型が、std::complex<T>になる。つまり、その置換を手作業で行わなければならない。
さらに、primary templateのコードに変更があった場合は、すべての明示的特殊化と部分的特殊化の重複コードまで手作業で対応しなければならない。
これは人間のやることではない。必要なだけ宣言すれば上書きされて、明示的に宣言しなかったものはprimary templateの実装を受け継ぐような機能があればどうか。例えばこんな文法。
template < typename T > class my_class < std::complex<T> > = default { public : value_type f() { /* 特別な実装 */ } } ;
これにより、冗長で問題あるコードの重複を省くことができる。
なお、この機能は、派生も受け継ぐ。派生の結果継承されるメンバーについて色々とあるがそれは些細なことだ。
class Base { public : void f() { } } ; template < typename T > class Derived : public Base { } ; template < > class Derived< int > = default { // Baseから派生 } ;
これは欲しい機能だ。
もうひとつの機能は、Consistent Specializationと題されている機能だ。Dependent class templateから派生するクラステンプレートを書く際の頭痛の種がある。
template <typename T> class base { public: typedef T value_type; value_type f1() { return value_type() ; } protected: // 論文ではprivateだがpublicかprotectedが必要 value_type x; } ; template <typename T> class derived // 論文ではaccess-specifierがないがpublicかprotectedが必要 : public base<T> { int f2() { // 論文ではbaseだが誤り typename base<T>::value_type y; // #1 y= this->f1(); // #2 this->x+= y; // #3 } } ;
#1では、base<T>::が必要である。単にvalue_typeと書いたのでは、dependent nameかどうか分からないからだ。そして、typenameも必要である。value_typeは型か非型かもわからないからだ。#2, #3でも、this->が必要である。というのも、f1はxだけではdependend nameかどうかわからないし、それにoperator ()やoperator +=を適用できるかどうかも分からない。
これは例えば、baseに以下のような明示的特殊化があるかもしれないからだ。
template < > class base< int > { public: int value_type ; int f1 ; using x = int ; } ;
さて、このような明示的特殊化は合法なC++だが、実際にこんな元のテンプレートとはかけはなれて、しかも名前だけ同じ特殊化を書く人間がどこにいるのだろうか。書けるからといって、こんなことになにかまともな利用方法があるのだろうか。
もし、元のテンプレートから変わらないと保証できれば、わざわざdependent nameにしたりtypenameを書く必要がなくなるのだ。
そこで、primary templateから名前と型、非型が変わらないという保証をつける文法を追加してはどうか、例えば以下のように。
template <typename T> class [[consistent]] base { public: typedef T value type; value type f1() {} private: value type x; }; template <> class base<float> { float value type() {} // エラー、typeは型でなければならない }; template <> class base<int> { typedef double∗ x; // エラー、xはデータメンバーでなければならない };
このように、特殊化でprimary templateから外れた宣言を書くとエラーになる機能を提供すれば、もう派生先からdependent nameやらtypenameやらを明示的に書く必要がなくなる。なぜならば、primary templateの宣言を尊重するからだ。
まあ、悪くはない機能だと思うが、どうもニッチすぎる機能な気がする。Dependent nameから派生するようなクラステンプレートを書く人間なら、この程度のことに躓くわけがない。それに、論文のタイトルである「再利用」とはちょっと違う気がする。
N3597: Relaxing constraints on constexpr functions
constexprの制限を大幅に緩和する提案。ほぼ普通の実行時関数のように書けるようになる。
もちろん、ローカル変数の書き換えもできるし、for文だって使えるようになる。constexprコンストラクターもメンバーの変更ができるようになる。
もちろん、夢はさらに大きく持つべきだ。例えば、constexprデストラクターの可能性についても触れられている。lambdaは難しいし、例外はやってできなくはないが、特に利用例も思い当たらないのでサポートしないほうがいいだろうとしている。
N3598: constexpr member functions and implicit const
constexprメンバー関数は暗黙にconst修飾されるが、これには問題が多い。というのも、コンパイル時と実行時の両方で使える便利なconstexprメンバー関数が書きにくい。
もう一度決めてしまったことを、今更変えるのはとても難しいのだが、constexprメンバー関数の暗黙のconst修飾を廃止できないか。あるいは、新しいconstではなくする修飾子を追加できないか。
それとも現状維持で、ユーザーには恥ずべきconst_castの利用を強いるべきか。
個人的には、どうせconstexprメンバー関数を書くような人間は初心者ではないし、下位互換を壊しても問題ないのではないかと思うのだが。
N3599: Literal operator templates for strings
現行規格には、文字列をテンプレート実引数をして受け取るリテラルオペレーターテンプレートはない。当時はそれなりの理由で却下されたのだが、今思えばそれほど問題でもないし、需要もあるし、付け加えてはどうか。
ちなみに、今あるのはこのようなテンプレートだ。
template < char ... pack > void operator "" _str() { } int main() { 123_str ; // call operator ""_str< '1', '2', '3'> }
これを、"abc"_strと使えば、 operator ""_str< 'a', 'b', 'c'>が呼び出されるようにしたいということだ。
ただし、この論文で提案されているテンプレート宣言は、たんにchar型の非型Variadic template parameterではなく、以下のようになる。
template<typename CharT, CharT ...String>
一つ目のテンプレート仮引数に文字型が入り、続いて文字がVariadic templateで渡される。
N3600: C++ Latches and Barriers
標準ライブラリにlatcheとbarrierを追加する提案。
latcheとは、ある操作が完了するまでスレッドをブロックする仕組みである。latcheのオブジェクトは使い捨てであり、再利用できない。
barrierはlatcheに似ているが、オブジェクトを再利用できる。
N3601: Implicit template parameters
N3405の改定版。現在、任意の型の非型テンプレート仮引数を取りたい場合、ユーザーに型を明示的に指定させなければならない。そのため、template<typename T, T t>という形のテンプレート宣言が使われる。しかし、ユーザーに型を指定させるのは面倒なので、型を推定させる機能を追加する提案。
template < typename T, T t > struct temp { } ; struct X { void f( ) ; } ; using t1 = temp< decltype(&X::f), &X::f > ;
のかわりに、
template < using typename T, T t > struct temp { } ; struct X { void f( ) ; } ; using t1 = temp< &X::f > ;
と書けるようになる。Tの型は、tに対するテンプレート実引数から推定される。
あれば便利な機能だ。
N3602: Template parameter deduction for constructors
これもN3405の改訂版。これも昔から要望されている機能で、クラステンプレートのコンストラクターに渡された実引数から、テンプレート実引数を推定する機能。
例えばこのコード。
std::tuple< int, int, double > t( 1, 2, 3.0 ) ;
何をどうしようと、コンストラクターへの実引数の型は、コンパイル時に決定できる。ならば、このようなテンプレート仮引数の型の実引数をコンストラクターで受け取るようなクラステンプレートならば、コンストラクターの実引数リストから、テンプレート実引数リストを推定できるはずだ。ぜひともそうしようではないか。
std::tuple t( 1, 2, 3.0 ) ; // std::tuple< int, int, double >
これもあれば便利な機能だ。
N3603: A Three-Class IP Address Proposal
先ほど解説したIPアドレスを表現するライブラリの設計に関する議論N3565で提案されている。3クラス設計による提案。
N3604: Centralized Defensive-Programming Support for Narrow Contracts
実行時チェック用のライブラリを追加しようという提案。どの程度チェックするかとか、チェックに引っかかった場合の挙動を実行時に変更できるようにするだとかの機能がある。
提案では、高機能版assertプリプロセッサーマクロのようだ。
プリプロセッサーの廃止をこの目で見たい私としては、プリプロセッサーを使ういかなる提案にも反対する。
N3605: Member initializers and aggregates
メンバー初期化子とアグリゲート初期化を同時に使えないという制限を緩和する提案。
struct X { int a ; int b ; int c = 3 ; } ; int main() { X x = { 1, 2 } ; // エラー、コンストラクターがない }
メンバー初期化子を使うとアグリゲート初期化できなくなる理由は、メンバー初期化子の存在するクラスはアグリゲートの定義から外れるからだ。Xがアグリゲートではない以上、上記はアグリゲート初期化ではなくリスト初期化となり、リスト初期化用のコンストラクターを探すが、発見できずill-formedとなる。
しかし、単純にメンバー初期化子の非存在をアグリゲートの条件から外すと、アグリゲートとは何ぞやという哲学上の問題に発展してしまう。
それでも、この論文では、アグリゲートの定義を緩和して、メンバー初期化子が存在を許す変更の提案をしている。
N3411の改訂版。内容は、検索アルゴリズムとして有名なBoyer-MooreとBoyer-Moore-Horspoolを追加する提案。
N3607: Making non-modifying sequence operations more robust
std::equal, std::mismatch, std::is_permutationは、一組のイテレーターと、先頭要素へのイテレーターを取る。
これは、アルゴリズムの呼び出し元が、呼び出す前に、ふたつのrangeの範囲チェックを行わなければならない。
template < typename Iter1, typename Iter2 > bool checked_equal( Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2 ) { if ( std::distance( first1, last1) != std::distance( first2, last2 ) ) { return false ; } else { return std::equal( first1, last1, first2 ) ; } }
呼び出し元で毎度毎度範囲確認をさせるぐらいなら、最初から、このアルゴリズムで二組のイテレーターを取ればいい話なのだ。二組のイテレーターを取るオーバーロードを追加しようという提案。つまり、このcheckd_euqalのようなオーバーロードが追加される。
N3608: exchange() utility function, revision 2
N3511の改訂版。非アトミック版の汎用的なアルゴリズム、exchange関数テンプレートを追加する提案。これは第一実引数として渡したオブジェクトに第二実引数を代入し、第一実引数の代入前の値を返す関数テンプレートである。
// 実装例 template< typename T, typename U = T > T exchange( T & obj, U && new_val ) { T old_val = std::move( obj ) ; obj = std::forward<U>( new_val ) ; return old_val ; }
前回との変更内容は、デフォルトテンプレート実引数の追加。
N3609: string_view: a non-owning reference to a string, revision 3
文字列を表現するクラスの提案。
文字列を受け取る多くの文脈では、文字列であることが重要なのであって、その文字列の内部表現がchar *だろうがstd::stringだろうが、どうでもいいことである。このような内部表現の差異を吸収して、文字列としてみせてくれるためのクラス、string_viewライブラリの提案。
どうも任意の型の文字列としての表現クラス、いわゆるto_stringライブラリではない模様。
N3610: Generic lambda-capture initializers, supporting capture-by-move
汎用的に使えるlambdaキャプチャー初期化子の提案。これはムーブキャプチャーのサポートも含むが、汎用的とあるように、ムーブキャプチャー以外にも使える。
ムーブキャプチャーは、C++WGの日本支部としても2009年にNBコメントを出したが、名前付き変数からのムーブは危険な機能だとして却下され、フィンランド支部も2010年にNBコメントを出したが、もはやこの段階での変更は手遅れとして却下された。
この提案では、ムーブキャプチャーをするには以下のように書かなければならない。
void f() { std::vector<int> v ; [ v { move(v) }] { } ; }
もちろん、これは汎用的とあるように、ムーブ以外の一般的な用途にも使える。
int plus_one( int value ) { return value + 1 ; } int f() { int x = 0 ; [ x = plus_one( x ) ] { } ; }
どうも使い道がよくわからないが、まあともかく、上記のコードは誰もが期待するように動く。plus_oneはlambda式の記述時点で呼び出される。
なぜ&&を使わないのかというと、日本支部のNBコメントへの拒否理由と同じく、論文の主張するところでは、名前付き変数をlambda式の記述場所でムーブするのに&&のような一見無害そうに見える文法は危険だからという事らしい。
ムーブは明示的に書くべきだというのはわかるが、ただでさえキャプチャは冗長なのに、私はこんなに冗長なlambda式を書きたくないのだが。
現時点で、文面案はまだ出来上がっていないようだ。
N3611: A Rational Number Library for C++
有理数ライブラリの提案の改訂版。
[PDFチョベリバ] N2612: Desiderata of a C++11 Database Interface
C++でデータベース用の標準ライブラリに関する議論の声も上がっており、C++らしいライブラリのインターフェースはどのようになるのかという実験的な設計が行われている。この論文では、既存のデータベースライブラリのインターフェースを比較して、共通の必要なインターフェースを抽出する考察をしている。
[PDFチョムカ] N3613: "Static If" Considered
冒頭から、
最近C++に案されたstatic if機能は根本的に欠陥であり、採用は言語に壊滅的な被害をもたらす。この機能は一種類の文法で使われる文脈により異なる三種類のセマンティックを提供する。このセマンティックの主目的は、使わないブランチのパースを取りやめることである。これはプログラムの読解と保守とデバッグを困難にする。また、コンセプトのような機能の開発の将来性にも影響を与える。さらに、この機能の利用はC++のASTベースのツールの提供に深刻な障害をもたらし、故に支援ツールの点でC++を近代的な言語からさらに引き離す。C++を低級言語にするであろう。
この論文の目的は、static if提案がC++ソースコードに与える影響を検証するものである。
と、static ifの反対論となっている。
三種類のセマンティックとは、条件付きで取り入れられる文と、条件付きで取り入れられるインターフェースと、constrained tempalteである。constrained templateはConcept Liteで提供されるとして、その他のふたつは今プリプロセッサーに頼っている状態だ。プリプロセッサーに頼るよりは言語でサポートされたほうがいいと思うのだが。
ともかく、論文の主要は以下の通り。
一度コード中でstatic ifを使うと、それ以後その条件に依存するコードを書くたびにstatic ifを使わなければならず、コードが非常に読みにくくなる。
static_ifをサポートするならばstatic_forやstatic_while(ループ展開)もサポートしなければならないし、現にサポートしろという意見が上がってきている。。というのも、もしstatic ifだけサポートした場合、プログラマーはstatic ifとプリプロセッサーマクロを使ってstatic forをエミュレートしてしまうからだ。
すでにstatic_forやstatic_whileといった提案を耳にしている。static_switchもそう遠くはない。C++は低級となり、気まぐれなハッカーのお気に入りの遊び場に成り下がってしまう。
constrained template、つまりテンプレートに対する制約は、static ifよりもconceptで提供したほうがいい。
論文の結論は、static ifは却下すべきだとしている。
[PDFチョーMM] N3614: unwinding_exception
現行のstd::uncaught_exceptionは、実は正しくない。今現在stack unwindingが行われているかどうかを確認することはできるが、デストラクターの中から呼ばれた場合、そのクラスのオブジェクトがスタック上に構築されていてunwindingの一環としてデストラクターが呼ばれたのかどうかを知ることは出来ない。
そこで、std::unwinding_exceptionを追加する。これは、std::unwinding_exceptionがデストラクターの本体の中で呼ばれ、そのデストラクターがstack unwindingのために呼び出された場合にtrueを返す。
[PDFチョーSS] N3615: Constexpr Variable Templates
constexpr変数のテンプレート宣言を可能にする変更。つまり、constexpr変数テンプレートの提案。今までテンプレート宣言はクラスや関数やエイリアスの宣言に使えていたが、それに新たにconstexpr変数が加わるわけだ。これは、大きな変更ではなく、規格のごく一部の制限をとっぱらうだけで可能になる。
以下のように使う。
template < typename T > constexpr bool is_int = std::is_same< T, int >::value ; template < typename T > void f() { constexpr bool x = is_int<T> ; }
現在、テンプレート引数を与えるコンパイル時定数というものを直接表現する方法がない。最も近いものは、
クラステンプレートのconstexpr staticデータメンバー
これは、クラスのメンバーとして使わなければならないのみならず、staticデータメンバーなのでクラス内部とクラス外部の二箇所で記述しなければならず面倒だ。
constexpr関数テンプレートの呼び出し
これは変数ではなく関数だ。
constexpr変数テンプレートは、パラメート化されたコンパイル時定数を変数として直接表現する力を与えてくれる。
N3617: Lifting overload sets into function objects
lifting operatorまたはlifting lambdaと仮に呼ぶ機能の提案。関数のオーバーロードのセットや関数テンプレートをそのままポリモーフィックlambdaに変換できる機能。
従来、関数のオーバーロードのセットや関数テンプレートを、テンプレート実引数として渡すのは非常に困難を伴うことであった。
void func( int ) ; void func( double ) ; template < typename T > void func( T ) ; void f( std::vector<int> & v ) { std::for_each( v.begin(), v.end(), &func ) ; // エラー、型が曖昧 }
これは、テンプレート実引数には具体的な型が必要だが、関数のオーバーロードのセットや関数テンプレートの場合、その具体的な型が決定できないからである。これを解決するには、明示的に望む型のポインターにキャストしなければならない。
std::for_each( v.begin(), v.end(), static_cast< auto (*)(int) -> void >( &func ) ) ;
あるいは、lambdaにラップする。この場合も、明示的に型を指定しなければならない。
std::for_each( v.begin(), v.end(), []( int value ) { func( value ) ; } ) ;
もし、型を特定できない場合は、メンバーテンプレートを持つクラスでラップする必要がある。
struct func_wrap { template < typename T > void operator () ( T && t ) const { func( std::forward<T>(t) ) } } ; template < typename Iterator > void f( Iterator first, Iterator last ) { std::for_each( first, last, func_wrap() ) ; }
面倒なこと甚だしい。幸い、ポリモーフィックlambdaの提案もあるが、結局自分で書かなければならないことに変わりはない。
std::for_each( first, last, []( auto && ... pack ) { return func( std::forward<decltype<pack>(pack) ) ; } ) ;
これをみると、関数のオーバーロードのセットや関数テンプレートから、ジェネリックlambdaへの変換は、機械的にできることは明らかである。このようなlambda式を生成するシンタックスシュガーがあればいい。そこで、そのようなシンタックスシュガーを提案する。
std::for_each( first, last, []func ) ;
'[] id-expression' という文法から、
[](auto&&... vs){ return id-expression(std::forward<decltype(vs)>(vs)...); }
というジェネリックlambda式を生成する機能。この提案は当然ながら、ジェネリックlambda式の採用を前提にしている。
これはぜひとも欲しい機能だ。
N3618: What can signal handlers do? (CWG 1441)
もともとCWG 1441だったが、ちょっと複雑になってきたので論文とした。内容はシグナルハンドラの制限を緩和する提案。現状では、シグナルハンドラはvolatileでatomicなローカル変数すら扱えない。それはさすがに変だろうという事で、atomic変数を使えるようにする。
N3619: A proposal to add swappability traits to the standard library
スワップ演算子であるoperator :=:の提案と関連して、型がスワップ可能かどうかを返すtype traitsのis_swappableとのis_nothrow_swappable追加提案。
[最後の最後までPDF] N3620: Network byte order conversion
バイトオーダー変換ライブラリの提案。エンディアンとも呼ばれている、
内容は、<net>ヘッダーに、std::net名前空間スコープに、htonとntohのuint16_tとuint32_t版をそれぞれlとsのサフィックスで別名をつけて提供。また、constexprテンプレート版のhtonとntohの提供。
この更新ではとてもわくわくします。
ReplyDeleteユニコード関係は割と入ってほしいです。
それに、コンセプトライトもほしいですね。
それにそれに、多倍長演算系もいいですね。
2000年ごろ、C++を始めた身からすれば夢のようです。
ぜひ実装が伴ってもらって、自分にも利益が回ってきてほしいと思います。
ほんとに楽しみです。
OpenGLの拡張機能はハイパーテキストですらないただのテキストファイルで、表どころかポリゴンの頂点を説明する図すらアスキーアートで表現しているというのに。
ReplyDelete虫のブログはほんま参考になるで〜
ReplyDelete