P0120R0: constexpr unions and common initial sequences
variant<A, B>に対して、variantの実装は、以下のような型を作り出す。
struct storage{
char discriminator;
union {
A a;
B b;
} u;
};
discriminatorは、どの型が現在有効になっているかをしめす変数だ。
しかし、以下のように実装したほうが、メモリ効率が良い。
struct wrapped_A{
char discriminator;
A data;
};
struct wrapped_B{
char discriminator;
B data;
};
union storage{
wrapped_A a;
wrapped_B b;
} u;
AやBのアライン要求がゆるい場合、メモリ効率が良くなる。
しかし、この実装はconstexprにできない。その理由は、現行規格の文面が不必要な制限を課しているからである。その制限を緩和する提案。
[PDF] P0121R0: Working Draft, C++ extensions for Concepts
コンセプトTSのドラフト
[PDF] P0122R0: array_view: bounds-safe views for sequences of objects
連続したストレージを多次元配列のように見せかけるラッパーライブラリ、array_viewの提案。
P0124R0: Linux-Kernel Memory Model
Linuxカーネルのメモリモデルの解説
背景事情としては、Linus Torvaldsから、CとC++が標準化したAtomic操作ライブラリは、設計がクソで、Linuxカーネルでは使うつもりはないと言われたので、Linuxカーネルでも使える設計にするために、まずLinuxカーネルのメモリモデルを詳細にまとめた文書を作成した。
[PDF] P0123R0: Unifying the interfaces of string_view and array_view
string_viewは、std::stringや、null終端されたchar *や、char *と文字数といった異なる連続したストレージ上に構築された文字列表現をラップして、共通の操作を提供してくれるライブラリだ。
array_viewは、連続したストレージ上の配列をラップして、多次元配列として使うことができるライブラリだ。
string_viewとarray_viewは似通っているため、インターフェースを統一したい。いっそ、string_viewはarray_viewのエイリアステンプレートでよい。つまり、以下のようになる。
template<class CharT, size_t Extent = dynamic_range>
using basic_basic_string_view = array_view<CharT, Extent>;
これにより、連続したストレージは同じ方法で扱える。C++プログラマーが覚える必要のあるライブラリが減る。
これまで提案されていたstring_viewは、basic_stringとの互換性のため、findなどの同じメンバー関数を提供していた。この提案では、メンバー関数として提供されることはない。フリー関数を使うべきだ。
std::string s("hello") ;
std::string_view sv("hello") ;
auto pos1 = s.find("ll") ;
auto pos2 = sv.find("ll") ; // エラー
提案されているstring_viewは、null終端されている保証を持たない。null終端された文字列が欲しい場合は、フリー関数ensure_z()に渡すと、null終端された文字列が得られる。これは、文字列リテラルに適用する場合ゼロオーバーヘッド、その他の場合はstrlen相当のコストがかかる。
P0124R0: Linux-Kernel Memory Model
Linuxのメモリーモデルを解説した文書
P0125R0: std::bitset inclusion test methods
std::bitsetは、数学的な集合を表現するのに使うことができる。
bitsetのn個目のbitが1かどうかで、n個目のオブジェクトが存在するかどうかを表現できる。
この提案は、bitsetにinclusionを判定する機能を付け加えるものである。
- 集合Aに含まれるすべてのオブジェクトが集合Bに含まれる場合、AはBのサブセットである
- AがBのサブセットであるか、AとBが同一である場合、BはAのスーパーセットである
- AがBのサブセットで、AとBが同一ではない場合、AはBの真の(あるいは厳格な)サブセットである
- AがBのサブセットで、AとBが同一ではない場合、BはAの真の(あるいは厳格な)スーパーセットである
この提案は、厳格なサブセットとスーパーセットを判定する以下のようなメンバー関数をbitsetに追加する。
bitset::is_subset_of(const bitset&) ;
bitset::is_superset_of(const bitset&) ;
追加する理由は、明示的に名前が付いていることによる読みやすさのためと最適化のためである。
bitsetのサイズがマルチワードの場合、aがbのサブセットであるかどうかを判定するのに、以下のような実装では、効率が悪い。
std::bitset<256> a, b;
auto a_includes_b(a & b == b);
なぜならば、これはaとbの全ビットを比較してしまうためである。メンバー関数は、結果がfalseであると判明次第処理を戻すことができるし、規格ではそのような最適化をするように規定されている。
auto a_includes_b( a.is_subset_of(b) );
[PDF] P0126R0: std::synchronic<T>
よいスピンロックライブラリ、synchronic<T>の提案
よいスピンロックを書くのは難しい。unmitigated spinning (TTAS), hardware thread yielding, randomized timed exponential back-off, and invocations of platform API’s like SYS_futexを組み合わせなければならない。std::mutexはそのような実装になっていることが多いが、ユーザーが実装するのは難しい。condition variableはその使われ方と歴史的経緯から、直接プラットフォームAPIを呼ぶ実装になっていることが多い。
synchronicは以下のような宣言になっている。
template <class T>
struct synchronic {
...
void notify(std::atomic<T>& atom, T val) noexcept;
void expect(std::atomic<T> const& atom, T val) const;
};
expectでatomの値がvalになるまでブロックする。notifyでatomの値をvalに変える。
P0127R0: Declaring non-type template arguments with auto
型をテンプレート実引数で取る非型テンプレート実引数の宣言のための文法の提案。
テンプレートは非型をテンプレート実引数に取れる。
template < int N >
struct S { int value = N ; } ;
S<123> s;
非型テンプレート仮引数の型をテンプレート化したいことはよくある。
template < typename T, T v >
struct S { T value = v ; } ;
S< int, 123 > s ;
この時、わざわざ型を指定するのが面倒だ。コンパイラーは式を評価した結果の型がわかるのだから、実引数から型推定してほしい。そこで、autoキーワードを使った文法で、この型推定を行わせる機能を提案している。
template < auto v >
struct S { decltype(v) value = v ; } ;
S<123> s ;
P0128R0: constexpr_if
static_ifに変わるコンパイル時条件分岐、constexpr_ifの提案。
constexpr_if( condition )
{
// conditionがtrueの時に評価されるブロック
}
constexpr_if( condition )
{
// conditionがtrueの時に評価されるブロック
}
constexpr_else
{
// conditionがfalseの時に評価されるブロック
}
static_ifは反対多数により否決されたが、やはりほしいとのことで復活した。static_ifに対して上げられた反対意見を解決するべく、制限が多い。
- ブロックスコープの中でのみ使える
- 新しいスコープを導入する
- 条件式には、どちらのブランチもwell-formedになる値が存在する
利用例には様々なものが考えられるが、簡単なコンパイル時条件分岐のために、関数テンプレートのオーバーロードを書くのがダルい場合に使える。
template < typename T >
void f( T && t )
{
// tを処理
}
template < typename T, typename ... Rest >
void f( T && t, Rest ... rest )
{
// tを処理
// 残りを処理
f( std::forward<Rest>(rest)... ) ;
}
constexpr_ifを使えば、以下のように書ける。
template < typename T, typename ... Rest >
void f( T && t, Rest ... rest )
{
// tを処理
// パラメーターパックRestにまだ引数があるのならば残りを処理
constexpr_if( sizeof...(Rest) != 0 )
{
f( std::forward<Rest>( rest ) ... ) ;
}
}
この提案の実験的な実装例は以下にある。
P0129R0: We cannot (realistically) get rid of throwing moves
variantに渡す型のムーブに無例外保証がないと、variantはempty/invalidな状態になりうるので、ムーブは例外を投げない制約を設けようという意見に対し、ムーブに無例外制約を化すのは現実的に無理だとする反論。
そもそも、「例外を投げるムーブ」とはなにか。
コンストラクターの場合、単にムーブコンストラクターのみではなく、以下の式に当てはまるものすべてになる。
X(move(rhs))
もし、オーバーロード解決がコピーコンストラクターを選択したら、当然コピーコンストラクターが対象になる。
同様に、代入演算子の場合、以下の式に当てはまるものすべてになる。
lhs = move(rhs)
オーバーロード解決がコピー代入演算子を選択したら、コピー代入演算子が対象になる。
では、そのようなコードはどのくらい一般的なのか。
struct X
{
X(const X&);
};
このようなクラスは、デフォルトのムーブコンストラクターの生成が抑制されるので、ムーブは実際にはコピーになる。
では例外を投げるムーブは禁止できるのか?
- コア言語で? 超互換性がないC++のforkをつくりたいのであれば、できるんじゃね?
- 標準ライブラリで? かなりの互換性がないC++のforkを作りたいのであれば、できるんじゃね?
- std::variantで? まあできるだろうけど、現実にかかれている結構なコードの型がvariantで使えなくなるぞ。
ドワンゴ広告
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0
標準化委員会の議論はちゃんと進んでいますでしょうか。
ReplyDelete最近思いますに、妙な立案が多くて悪い停滞を起こしてないでしょうか。
杞憂だとうれしいです。
バリアントの議論も良いですが、多倍長精度演算の議論がもっと活発になってほしいです。
意外と見なくなりましたねぇ。なんででしょ。