2014-07-post-Rapperswilのレビュー: N4109-N4121
2014年7月の論文集の最後。
[一発目からPDF] N4109: A proposal to add a utility class to represent expected monad - Revision 1
HaskellのEitherに似たライブラリ、expected<T, E>の提案。expectedは、T型か、あるいはエラー通知のためのE型を格納するクラスである。
前回からの変更点で最も大きなものは、テンプレート仮引数の順番を変えた。expected<E, T>だったものを、expected<T, E>にした。
[論文のpreconditionとしてPDFを禁止したい] N4110: Exploring the design space of contract specifications for C++
C++のコア言語で契約(contract)をサポートする提案。
関数の宣言に契約を指定して、契約に違反した場合、std::terminateを呼び出す。
用語の説明。
- エラー状態
- プログラムが復帰できるかできないかにかかわらずエラーとなる状態
- Precondition
- 関数を実行する前に満たしている必要のある状態
- postcondition
- 関数を実行後に満たしているべき状態
- invariant
- 関数の実行前、実行後に満たしているべき条件のkと
- 契約(contract)
- ある関数に指定された一連のprecondition, postcondition, invariantのこと。
- 契約違反
- 関数を実行前にそのpreconditionかinvariantが満たされていないこと、もしくは、関数実行後にpostconditionかinvariantが満たされていないこと。
契約をコア言語でサポートするために、関数の宣言として、precondition, postcondition, invariantを記述できるようにし、契約違反であれば、std::terminateを呼び出す。あるいは契約違反ハンドラーを設定できるようにする。
論文では、すでに契約をサポートしている言語として、Eiffiel, Ada2012, Dを挙げ、サンプルコードを示している。
またC++にはすでに、防衛的プログラミングという名前で高級なassertマクロが提案されている。
論文は、ライブラリによる契約の実装では、ツールによる解析や、コンパイラーが契約条件を把握することによるよりよいコードの生成などができないとしている。
だいぶ野心的な提案だが、すでにサポートしている言語は複数あるので、机上の空論というわけでもない。ただ、記述するのは面倒そうだが。
[PDFバリヤー、PDFは跳ね返る] N4111: Static reflection (rev. 2)
静的リフレクション機能の提案の一つ。クラスのメンバーなどといったプログラムの構造をテンプレートメタプログラミングの要領で取得できるライブラリの提案。論文としてはN3996の改訂版だが、内容的には全面的に書きなおしになっている。
ただ問題は、テンプレートメタプログラミングの延長線上でプログラムの構造を取得するようになっているので、設計がBoost.MPLもびっくりの複雑なものになっているし、実際に使うのも冗長なコードを書かねばならないだろう。
個人的には、機能的には面白いものの、文法が悲惨すぎて破綻する印象しかない。たしかに、テンプレートメタプログラミングの要領で操作できるのは魅力的でもあるのだが、その文法があまりにも冗長すぎる。
[論文にPDFフォーマットを使うなというNBコメントを出したい] N4112: File System PDTS National Body Comments Record of Response
File System TSに対するNBコメント。
[静的リフレクションの前にPDFを何とかして欲しい] N4113: Reflection Type Traits For Classes, Unions and Enumerations (rev 3)
静的リフレクション機能として、テンプレートメタプログラミングの要領でプログラムの構造を取得できるライブラリの提案。こちらはN4027の改訂版。
これはまだ小規模で理解可能な範囲だ。
N4114: Defaulted comparison operators
クラスの演算子==, !=, <, >, <=, >=をデフォルト生成する機能を追加する提案。明示的なデフォルト演算子。
クラスのメンバーの等号や大小比較ができるとき、メンバーごとの比較をして等価か大小を決定する演算子オーバーロードを書くのは、極めて機械的で面倒な作業である。いちいちメンバーを手で列挙しなければならないし、特に大小比較の場合は、極めて面倒なコードを書かなければならない。そのような作業は、コンパイラーにデフォルトの実装として生成させてしまえばよい。
この演算子を自動生成させるには、明示的に指定する必要がある。なぜならば、いままで存在しなかった演算子が勝手にデフォルト定義されてしまっては、既存のコードを壊す恐れがあるからである。
会議の結果、以下の二つの文法が提案されている。
struct Thing
{
int a, b, c;
std::string d;
};
bool operator==(const Thing &, const Thing &)= default;
bool operator!=(const Thing &, const Thing &)= default;
このように、explicited defaultedの文法を流用して、明示的に実装を生成するように指示する。
メンバーがprivateの場合は、演算子はfriendを指定子なければならない。その場合は、以下のように書く。
class AnotherThing
{
int a, b;
public:
// ...
friend bool operator<(Thing, Thing) = default;
friend bool operator>(Thing, Thing) = default;
friend bool operator<=(Thing, Thing) = default;
friend bool operator>=(Thing, Thing) = default;
};
会議による議論では、このように演算子を個別に冗長に指定するのは面倒であるので、もっと短く書きたいという意見が多かった。会議で提案された短い文法は以下の通り。
struct Thing
{
int a, b, c;
std::string d;
default: ==, !=, <, >, <=, >=;
};
この簡潔な文法は、冗長でコピペを誘発する文法に比べて優れているし、最適な引数の型などは、コンパイラーがよきにはからって正しく、パフォーマンス上優位になるよう、決定してくれる。
その他の演算子も同様に生成できるのではないかという点については、論文では、それほど役に立たないとしている。
論文では、mutableメンバーの扱いをどうするかで、議論が沸かれているとしている。解決方法としては、mutableメンバーは比較対象から除外するか、除外せずに対等に扱うかだ。論文著者は除外する方を好むとしている。標準化委員会のデッドロックを避けるために何らかの決定をせねばならず、提案では、mutableメンバーを含むクラスの場合は、デフォルト実装の生成は行われないとするそうだ。こうすることで、将来解決するために保留できる。
totally orderedではない型はどうすればいいのだろうか。例えば、ポインターの値の大小比較は、同じ連続したストレージ上から確保されたメモリーでなければ意味をなさない。IEEE浮動小数点数にはNaNがあり、NaNの比較はかなり特殊な定義がなされている。
議論の結果として、
- 浮動小数点数型のメンバーが存在する場合、明示的なデフォルト演算子は生成は生成できない。
- ポインター型のメンバーが存在する場合、等価比較の明示的なデフォルト演算子は生成できる。
- ポインター型のメンバーが存在する場合、大小比較の明示的なデフォルト演算子は生成できない。
筆者が思うに、この決定はどうかと思う。結局この機能は、一般的な比較演算子を簡単に生成する機能なのだから、浮動小数点数やポインターといったありふれた型の存在によって、明示的なデフォルト演算子が使えないとあっては、とても使い道が制限される。
そういう責任はユーザー側にまかせて、コンパイラーはユーザーが指定したならば生成するべきであると思う。
N4115: Parameter Pack Searching
パラメーターパックの中に指定の型が含まれているかどうかを調べるtraits、is_contained_in<T, P>と、パラメーターパックが別のパラメーターパックの中の型をすべて含むかどうかを調べるcontains_type< T, P >の提案。
まず、パラメーターパックを格納するクラステンプレート、packer。
template <class... T> struct packer { };
packerはtupleに似ているが、tupleのようにメンバーを持たないので、必ずインスタンス化できる。
パラメーターパックPにT型が含まれているかを調べるis_contained_in traits。
template <class T, class... P> struct is_contained_in;
template <class T, class... P>
constexpr bool is_contained_in_v = is_contained_in<T,P>::value;
その実装例
template <class T> struct is_contained_in<T> : false_type { };
template <class First, class... Rest>
struct is_contained_in<First, First, Rest...> : true_type { };
template <class T, class First, class... Rest>
struct is_contained_in<T, First, Rest...>
: is_contained_in<T, Rest...> { };
あるpacker Tのパラメーターパックが、別のpacker Uのパラメーターパックをすべて含んでいるかどうかを調べるcontaines_type。
template <class T, class U> struct contains_types;
template <class T, class U>
constexpr bool contains_types_v = contains_types<T,U>::value;
その実装例
template <class... TPack>
struct contains_types<packer<TPack...>, packer<>> : true_type { };
template <class... TPack, class UFirst, class... URest>
struct contains_types<packer<TPack...>, packer<UFirst, URest...>>
: integral_constant<bool,
is_contained_in<UFirst, TPack...>::value &&
contains_types<packer<TPack...>, packer<URest...>>::value> { };
まあ、あっても困らない小粒なライブラリだ。
[PDFは消え去るべき] N4116: Nested Namespace Definition (rev 1)
名前空間のネスト。
namespace A { namespace B { namespace C {
} } }
という冗長なコードを、
namespace A::B::C {
}
と書けるようになる。
議論ではnamespaceのinlineや別名をどうするかという問題も出たそうだが、とりあえずそれは保留して、最小限の提案だけをする。つまりinline namespaceには対応しない。
別名の問題というのは、以下のようなコードが通らないということだ。
namespace A::C {}
using namespace B = A;
namespace B::C {} // ill-formed
ただし、もともと名前空間の別名は、このようには使えなかった。
namespace A { }
using namespace B = A;
namespace B { } // ill-formed
つまり、現状維持ということになる。
N4117: C++ Standard Library Active Issues List
N4118: C++ Standard Library Defect Report List
N4119: C++ Standard Library Closed Issues List
標準ライブラリに持ち上がっている問題、解決された問題、議論の結果問題ではないとされた問題の一覧。
N4120: Null Coalescing Conditional Operator
ある値を使う前に、値のnullチェックをするような処理は、プログラミングで頻出する処理である。これは、現在のC++14の文法では、条件式を使うことで実現できるが、条件式を使うのは色々とめんどくさい。
x ? x : y ;
という形になるのだが、xが長い複雑な式である場合、二度重複して書かなければならない。また、このままではxは二回評価されてしまうので、評価されることによる副作用を避けたいのであれば、まず一時オブジェクトに式を評価した結果を格納しなければならない。
auto && temp = x ;
temp ? temp : y ;
このため、他の言語にも存在し、またGCCの独自拡張で、Clangにも実装されている文法を、標準に提案する。この文法を使えば、以下のように書ける。
x ? : y ;
これは、以下のコードとほぼ同じ意味を持つ。
auto && temp = x ;
temp ? temp : y ;
違いは、tempはxと同じ値カテゴリーだとみなされるということだ。
なかなか小粒ではあるが便利であれば嬉しい提案だ。
N4121: Compile-Time String: std::string_literal<n>
Library Fundamentals TSに、std::string_literal<n>を追加する提案。
このライブラリは、コンパイル時の文字列を扱うためのライブラリである。リテラル型なのでコンパイル時に扱える。つまり、constexprオブジェクトにできるし、constexpr関数で使うこともできる。
コンパイル時に文字列を扱うクラステンプレートの実装方法としては、二つの方法がある。この論文で提案されているのは、
template < std::size_t n >
struct string_litera
{
char data[n] ;
} ;
という形だ。文字数をテンプレート引数で受け取って、配列で文字列を持つ。これはリテラル型なので、コンパイル時に扱える。
もうひとつの実装方法としては、非型テンプレートパラメーターパックで文字を受け取ることだ。
tempalte < char ... data >
struct string_literal
{ } ;
まず、両方共、相互に簡単に変換可能である。
この二つの利点と欠点を考えると、パラメーターパックを使う実装は、文字列ごとに別のインスタンス化が必要で、文字列を名前マングリングで受け取り、りんカーを酷使する、スケールしない実装方法である。パラメーターパックによるコンパイル時文字列の実装、またテンプレートメタプログラミングは、たまたまチューリング完全であったために酷使された利用法であって、近代的なC++14では、constexprオブジェクトのほうが使いやすい。
静的リフレクションを入れるにも、何はともあれコンパイル時文字列は必要になるので、いずれ必要になるライブラリである。
ドワンゴ広告
今日は不自由なソフトウェアであるスーパーマリオランド2 6つの金貨で遊びながら通勤し、また昼休みにも6つの金貨で遊んだ。ドワンゴには路地裏と呼ばれているスペースがあり、ゲーム機が置かれているが、あまり遊ばれているのをみたことはない。なんでもぷよぷよ勢のために稼働するメガドライブなどが維持されているのだとか。
もちろん、今日はドワンゴ勤務中にこの記事を書いた。次の論文集は10月だが、明日からはBoost勉強会大阪の発表資料を作成するので、暇にはならない。
ところで、そろそろまたドワンゴのセミナールームを使ってC++勉強会を開くべきだろうか。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0