Share
Embed
C++17の新機能 optional編 / new features of C++17 - optional
C++17 で追加された optional の紹介です。
突貫で作ったので冗長な上にいろいろ間違ってるかもです。
あと使用例が無いのは単なる力不足です…
Miutsuru kariya
November 22, 2018
Transcript
101 slides
-
optional objects 4 N1878 A proposal to add an utility
class to represent optional objects (Revision 1) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1878.htm N3406 A proposal to add a utility class to represent optional objects (Revision 2) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3406.html N3527 A proposal to add a utility class to represent optional objects (Revision 3) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3527.html N3672 A proposal to add a utility class to represent optional objects (Revision 4) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.html N3765 On Optional http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3765.pdf N3793 A proposal to add a utility class to represent optional objects (Revision 5) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3793.html N3966 Fixes for optional objects http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3966.html N3982 Rvalue reference overloads for optional http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3982.html -
optional objects 5 N4078 Fixes for optional objects http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4078.html P0032R0
Homogeneous interface for variant, any and optional http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0032r0.pdf P0032R1 Homogeneous interface for variant, any and optional (Revision 1) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0032r1.pdf P0032R2 Homogeneous interface for variant , any and optional (Revision 2) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0032r2.pdf P0307R0 Making Optional Greater Equal Again http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0307r0.pdf P0032R3 Homogeneous interface for variant, any and optional (Revision 3) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0032r3.pdf P0307R2 Making Optional Greater Equal Again http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0307r2.pdf P0504R0 Revisiting in-place tag types for any/optional/variant http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0504r0.html -
optional objects 導入の背景 値が無い状態を使いたい時 10 1. 関数から戻す値が無い時がある 2. 関数にオプション引数がある 3.
null 状態を表したい 4. 寿命を手動で制御したい 5. 高コストな初期化を避けたい ※ 提案ペーパーから適当訳で引用 -
optional objects 導入の背景 値が無い状態を使いたい時 11 1. 関数から戻す値が無い時がある C 言語の getchar()
みたいな感じ。 普通は返す char あるんだけど、たまに無い、 みたいな。 getchar() はそのために戻り値型が char じゃなくて int。 getchar() の戻り値を char で受けて EOF との比較がうまくいかなかったり、逆に int で受けたものを char と比較する際に char 側は負数になってるのに int 側は 正数になっててハマった初心者は少なく見積もっても5000万人はいるはずだ! -
optional objects 導入の背景 値が無い状態を使いたい時 12 2. 関数にオプション引数がある 引数で渡すべき値が無い、みたいな。 デフォルト引数はあくまでもその引数型の値 しか渡せないので。
オーバーロードで解決可能な場合もあるけど、 例えばオプション引数が 3 つあったらオー バーロード 8 個必要になる… -
optional objects 導入の背景 値が無い状態を使いたい時 13 2. 関数にオプション引数がある 提案ペーパーにあった例 void execute(
function<void(int)> fa, function<void(int)> fb ) { int i = computeI(); fa(i); // implicit assumption that fa != nullptr if (fb) { fb(i); } } まあ見ての通り std::function は null にできるんだけどね、もっと汎用性あるもの が欲しいよね… -
optional objects 導入の背景 値が無い状態を使いたい時 14 3. null 状態を表したい 変数で「値を保持していない」と言う状態を表 したい、みたいな。
メンバ変数で構築直後は null 状態にしてお きたい、とか… -
optional objects 導入の背景 値が無い状態を使いたい時 16 4. 寿命を手動で制御したい 提案ペーパーにあった例 Result run3Actions(
Parameter param ) { Resource1 res1; // まだ使う前 Resource2 res2; // まだ使う前 runAction1( res1 ); // ↑ : // res1 はこの範囲だけ使いたい runAction2( res1, res2 ); // ↓ // ↑ : // res2 はこの範囲だけ使いたい runAction3( res2 ); // ↓ } でも例外が発生した場合には res1 と res2 は使ってる最中に限り、お片付けして欲しい… -
optional objects 導入の背景 値が無い状態を使いたい時 17 5. 高コストな初期化を避けたい メンバ変数だと、構築時に初期化は避けられ ないけど、初期化が高コストな型の場合で、 かつ、結局使うかどうかわからないような物
の場合には、実際に必要に迫られるまで初 期化コストを払いたくない、みたいな… -
optional objects 対応策 20 optional の特徴 • 値の有無を表すことができる。 • 値がある場合には、その値を適切に管理してくれる。(コンストラクタ、
デストラクタをちゃんと呼んでくれる) • optional 自身は動的メモリ等を使わない。(new とか呼ばない) • 破棄可能な型であれば std::in_place_t と std::nullopt_t(後述)を除い てなんでも格納できる。(std::optional 自身でさえ!) • 格納する値のアラインメントをちゃんと考慮してくれる。(あたりまえ) • コピー・ムーブできる。(ただし、格納する型が対応していれば) • コピーは値のセマンティクスで行われる。(値の共有とか起きない) • 値の有無を表す分、元の型よりはちょっとメモリ余分に食うかな… -
optional objects 構築 22 構築は、処理内容によって以下のような種類に分けられる。 1. 値が無い状態で構築 2. 格納する値から構築 3.
コピー・ムーブ構築 4. 型の違う optional からコピー・ムーブ構築 5. 値のコンストラクタ引数から直接構築 また、上記のうち2と5は、コンストラクタだけでなく、ファクトリ 関数でも構築できる。(特に5はファクトリ関数の方がオヌヌメ) -
optional objects 構築 24 コンストラクタ constexpr optional() noexcept; constexpr optional(nullopt_t)
noexcept; constexpr optional(const optional&); constexpr optional(optional&&) noexcept(see below); template <class... Args> constexpr explicit optional(in_place_t, Args&&...); template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U>, Args&&...); template <class U = T> EXPLICIT constexpr optional(U&&); template <class U> EXPLICIT optional(const optional<U>&); template <class U> EXPLICIT optional(optional<U>&&); -
optional objects 構築 25 ファクトリ関数 template <class T> constexpr optional<decay_t<T>>
make_optional(T&&); template <class T, class... Args> constexpr optional<T> make_optional(Args&&... args); template <class T, class U, class... Args> constexpr optional<T> make_optional(initializer_list<U> il, Args&&... args); -
optional objects 構築 26 値が無い状態で構築 constexpr optional() noexcept; constexpr optional(nullopt_t)
noexcept; 引数無し(デフォルトコンストラクタ)、あるいは、引数に std::nullopt_t 型 の値 std::nullopt を指定して構築すると、値が無い状態となる。 std::optional<int> v1; // v1は値を持たない std::optional<int> v2{std::nullopt}; // v2も値を持たない なお、テンプレート型引数Tがデフォルト構築できなくても OK。 なにしろ値がないんだからね、当然だね。 -
optional objects 構築 27 何で書き方が2通りあるの? 1. 値が無いことを明示したい時に便利。特に optional の引数や戻り値の場合 に
std::nullopt って書けばわかりやすいよね。デフォルトコンストラクタの場合 は {} って書かなきゃならなくて何のこっちゃってなる… func( {} ); // これってどうなのよ… 2. そもそも optional<T> っていうのは「T 型」と「値が無いという状態を表す型」 の直和型なんだから、後者を表す「型」としての std::nullopt_t と、その「値」と しての std::nullopt があった方が自然だよね、とのこと… optional<T> ≡ T ⨆ std::nullopt_t 3. あと、デフォルト構築だと格納する値がデフォルト構築されてると勘違いしそ うな気もするから、個人的には std::nullopt 書かれてた方がいいかなぁ… std::optional<MyStruct> v; // MyStruct 型の値がデフォルト構築 // されるようにも見える?(見えない? -
optional objects 構築 28 格納する値から構築 // コンストラクタ template <class U
= T> EXPLICIT constexpr optional(U&&); // ファクトリ関数(非メンバ関数) template <class T> constexpr optional<decay_t<T>> make_optional(T&&); 引数が1つの場合に、その引数から格納するオブジェクトを直接構築す る。 実は、見ての通り何か型がちょっと違うし、挙動も微妙に(?)違う。 -
optional objects 構築 29 格納する値から構築・コンストラクタ template <class U = T>
EXPLICIT constexpr optional(U&&); 使い方は至って普通。 // 普通の値から構築 std::optional<int> v1 = 42; 引数型が T&& じゃなくて U&& なので、実は型が違っても基本的には 一時オブジェクトが作られない。 // 文字列リテラルから string を optional 内に直接構築 std::optional<std::string> v2{"ゴーン has gone"}; -
optional objects 構築 30 ちなみに、ナゾの EXPLICIT は is_convertible_v<U&&, T> が
false の 場合のみ explicit。 // double から int への変換はコピー初期化でも大丈夫! std::optional<int> v = 3.14; // double から S への変換は explicit struct S { explicit S(double) {} }; // 直接初期化はできるけど、コピー初期化(v = 3.14)じゃダメだぞ! std::optional<S> v{3.14}; -
optional objects 構築 31 実は普通の値から構築はもうちょっと簡単に書ける。 // 普通の値から構築。何とテンプレート引数書かなくても値から int に推論! std::optional
v1 = 42; このために、optional には以下のような推論補助が提供されている。 template <class T> optional(T) -> optional<T>; 普通は推論補助なんてなくてもいいはずなんだけど、この場合は引数 からテンプレート型引数が推論できないので… (引数型が T とは無関係な U&& だからね、仕方ないね…) -
optional objects 構築 32 ちなみに、さっきの string の例は省略しちゃダメだよ。違うものになっ ちゃうから… // optional<string>
のつもりが optional<const char*> に… std::optional v2{"ゴーン has gone"}; だからと言って、引数側を string にしちゃうと、せっかく U&& で受けられ るようにしてるのに一時オブジェクト作っちゃうので MOTTAINAI… // 確かに optional<string> にはなるけど… std::optional v2{"ゴーン has gone"s}; -
optional objects 構築 33 あと、値をデフォルト構築したい時にもこれ使っちゃいそうだけど、これも 一時オブジェクト作っちゃうので、後述の直接構築使った方が良いよ。 struct S { S()
{ /* デフォルトコンストラクタだぞ! */ } }; // 確かに optional<S> で S をデフォルト構築できるけど… std::optional v3{ S{} }; // こっちの方が効率的だよ(詳しくは後述) std::optional<S> v4{std::in_place}; // これでも同じく効率的(これも詳しくは後述) auto v5 = std::make_optional<S>(); -
optional objects 構築 34 ところで、テンプレート引数 U の謎のデフォルト引数 T は何? template
<class U = T> EXPLICIT constexpr optional(U&&); 引数が 1 つの初期化リストの場合にテンプレート型推論がうまくいかないのを防 ぐため。…だと思う…(多分。自信なし) // こんなこともできるけど… std::optional<std::array<double, 3>> v({ 1, 2, 3 }); でもこれ実はワナ。例えば上記の場合、引数の型は initializer_list<int> じゃなく て array<double, 3> になっちゃっているので、一旦引数として構築してから optional 内にムーブされる。 (しかも std::array にはムーブは無いのでコピーになる) 初期化リスト渡すならこれじゃなくて後述の直接構築をオヌヌメします… -
optional objects 構築 35 格納する値から構築・ファクトリ関数 template<class T> constexpr optional<decay_t<T>> make_optional(T&&);
こちらも使い方は至って普通。 // 普通の値から構築 auto v1 = std::make_optional(42); これだけ見ると、前述のコンストラクタでテンプレート引数省略した方が楽そう… でも、他のオーバーロード(後述)と共に使うことで、構築を統一的に書けるとい うメリットはあるかも… -
optional objects 構築 36 あと、見ての通り生成される型には std::decay_t が付いているので、参照が削除 されたうえで、配列はポインタに、関数は関数ポインタに、cv 修飾された型は cv
就職が削除された型になる。 template <class T> constexpr optional<decay_t<T>> make_optional(T&&); ちなみに、呼び出す際にテンプレート型引数を指定することもできるけど、そうす ると実引数の型と仮引数の型が一致しなくなるので、ほとんどの場合後述する 別のオーバーロードが呼ばれちゃうと思う…(多分。自信なし) // これだと違うオーバーロードが呼ばれる… auto v = std::make_optional<std::string>("ゴーン has gone"); あ、それから C++17 で戻り値のコピー省略(RVO)が必須になったので、make_optional 使ってもムダな戻り値のコピーとかは発生しないぞ!すごいぞ C++17! -
optional objects 構築 37 コピー・ムーブ構築 constexpr optional(const optional&); constexpr optional(optional&&)
noexcept(see below); コピー・ムーブコンストラクタ。値の有無を含めてコピー・ムーブする。 みんなが思った通りに普通に動く。 std::optional<int> v1 = 42; std::optional<int> v2 = v1; なお、テンプレート型引数Tがコピー・ムーブできなければ使えない。 そりゃあそうだよね… あと T のムーブコンストラクタが noexcept ならムーブコンストラクタが noexcept になる。 -
optional objects 構築 38 もちろんコピー・ムーブでもテンプレート型引数の推論は効くよ! // std::optional<int> だぞ! std::optional v1
= 42; // これも std::optional<int> だぞ! std::optional v2 = v1; いやでもこの場合は普通に auto 使うかな… // これも std::optional<int> になるぞ!(当たり前) auto v3 = v1; -
optional objects 構築 39 型の違う optional からコピー・ムーブ構築 template <class U>
EXPLICIT optional(const optional<U>&); template <class U> EXPLICIT optional(optional<U>&&); 型の違う optional からもコピー・ムーブ構築できる。 ただし、型 U から型 T への変換が出来なきゃダメ(当たり前) ちなみに、ナゾの EXPLICIT は「格納する値から構築」と一緒で、is_convertible_v<U&&, T> が false の場合のみ explicit。 -
optional objects 構築 40 // optional<double> になるぞ! std::optional v1 =
3.14; // optional<double> から optional<int> にコピー構築できるぞ! std::optional<int> v2 = v1; // double から S への変換は explicit struct S { explicit S(double) {} }; // optional<double> から optional<S> にコピー構築できるけど、 // コピー初期化(v3 = v1)じゃダメだぞ!(ちょっと意味不明) std::optional<S> v3{v1}; -
optional objects 構築 41 値のコンストラクタ引数から直接構築 // 可変引数版コンストラクタ template <class... Args>
constexpr explicit optional(in_place_t, Args&&...); // 可変引数版ファクトリ関数(非メンバ関数) template <class T, class... Args> constexpr optional<T> make_optional(Args&&... args); // 初期化リスト版コンストラクタ template <class U, class... Args> constexpr explicit optional(in_place_t, initializer_list<U>, Args&&...); // 初期化リスト版ファクトリ関数(非メンバ関数) template <class T, class U, class... Args> constexpr optional<T> make_optional(initializer_list<U> il, Args&&... args); -
optional objects 構築 42 値のコンストラクタ引数から直接構築・可変引数版 // コンストラクタ template <class... Args>
constexpr explicit optional(in_place_t, Args&&...); // ファクトリ関数(非メンバ関数) template <class T, class... Args> constexpr optional<T> make_optional(Args&&... args); テンプレート型引数Tのオブジェクトを optional 内に直接構築する。 コンストラクタの場合、引数の先頭に std::in_place_t 型の引数 std::in_place を指定すると使える。 どちらも一時オブジェクトが作られないので効率がいいけど、ナゾの std::in_place を指定する必要が無いのでファクトリ関数の方がオヌヌメ。 -
optional objects 構築 43 // 1024 個の 'w' からなる string
を optional 内に直接構築 // コンストラクタを使用(std::in_placeがウザい。てかそんなん覚えてられん) std::optional<std::string> v1{std::in_place, 1024, 'w'}; // 1024 個の 'w' からなる string を optional 内に直接構築 // ファクトリ関数を使用 auto v2 = std::make_optional<std::string>(1024, 'w'); 個人的にはどう見ても make_optional の方がいいんだけど、みなさんは どう思います? -
optional objects 構築 44 値のコンストラクタ引数から直接構築・初期化リスト版 // コンストラクタ template <class U,
class... Args> constexpr explicit optional(in_place_t, initializer_list<U>, Args&&...); // ファクトリ関数(非メンバ関数) template <class T, class U, class... Args> constexpr optional<T> make_optional(initializer_list<U> il, Args&&... args); やりたいことは可変引数版と一緒だが、T 型のコンストラクタ引数に初 期化リストがあると可変引数版ではテンプレート型推論がうまく働かな いので、こちらのバージョンが必要。 ちなみに、後ろにナゾの args があるのはご愛敬(標準コンテナの初期化リストコンストラク タにも追加の引数(アロケータ)があるのでしょうがない…)。 -
optional objects 構築 45 // 3 つの要素を持った vector を optional
内に直接構築 // コンストラクタを使用(std::in_placeが(ry) std::optional<std::vector<int>> v1{std::in_place, { 1, 2, 3 }}; // 3 つの要素を持った vector を optional 内に直接構築 // ファクトリ関数を使用 auto v2 = std::make_optional<std::vector<int>>({ 1, 2, 3 }); 個人的にはどう見て(ry -
optional objects 構築 46 あと、格納する値から構築にも書いたけど、値をデフォルト構築したい場 合も、この直接構築使うのがいいよ! struct S { S()
{ /* デフォルトコンストラクタだぞ! */ } }; // 値をデフォルト構築するぞ! std::optional<S> v1{std::in_place}; // コンストラクタ使用 auto v2 = std::make_optional<S>(); // ファクトリ関数使用(オヌヌメ // ちなみに、これじゃ値無しになっちゃうのでダメ! std::optional<S> v3 = {}; // これなら値のデフォルト構築にはなるけど、一時オブジェクト作っちゃう… std::optional v4{ S{} }; -
optional objects 破棄 48 デストラクタ ~optional(); コンストラクタと違って、1 個しかない!(当たり前) ちゃんと動く。値を保持している時に限り、その値のデストラクタを呼び 出す。
なお、テンプレート型引数 T のデストラクタが自明(trivial)な場合、この デストラクタも自明となる。 ※ 自明なデストラクタ:ざっくり言うと、オブジェクト破棄時に呼ばなくてもいいデストラクタ。 ~optional() が自明な場合、値のデストラクタも呼ばれないかも。(呼ばなくていいので) -
optional objects 代入演算子 50 代入演算子も、基本的にコンストラクタと似ているが、代入 先(*this:代入される側)と代入元(引数:代入する側)の値 の有無によって以下のような動きになる。 なお、std::nullopt を代入する場合は、引数側に値無しってこ とで。
あと、たとえムーブ代入でも代入元の「値の有無」は変わら ないので注意!(値がどうなるかは値の型による) 代入元の値に対してデストラクタが呼ばれるわけじゃないからね… *this に値あり *this に値無し 引数側に値あり 代入される 値が構築される 引数側に値無し 値が破棄される 何も起きない -
optional objects 代入演算子 51 optional& operator=(nullopt_t) noexcept; optional& operator=(const optional&);
optional& operator=(optional&&) noexcept(see below); template <class U = T> optional& operator=(U&&); template <class U> optional& operator=(const optional<U>&); template <class U> optional& operator=(optional<U>&&); コンストラクタほどじゃないけど、わりと多い… 何と basic_string と一緒(6個) けど、挙動は見ればわかると思う… -
optional objects 代入演算子 52 std::optional v1 = 42; // int
std::optional v2 = 114514; // int std::optional v3 = 3.14; // double v2 = v1; // 普通にコピー代入 v3 = v1; // int から double に変換しながら代入 v1 = std::nullopt; // 値ありから値無しに v2 = 1.41421356; // 普通の値を double から int に変換しつつ代入 v3 = v1; // v3 も値無しに v1 = 42; // 値無しから再び値ありに 見たまんまで、わりと分かりやすいと思う… -
optional objects 再構築 54 直接構築コントラクタっぽいやつ。 (でも規格書上では assignment にグルーピングされてる…) 挙動としては直接構築コンストラクタとほぼ同様だが、以下 の点が異なる。
1. 構築前に値があった場合、まず破棄する。 さすがに破棄しないで構築したらマズいので、当たり前っちゃあ当 たり前だけど… 2. 構築された値への左辺値参照(T&)を返す。 まぁコンストラクタは値返せないもんね… -
optional objects 再構築 55 template <class... Args> T& emplace(Args&&...); template
<class U, class... Args> T& emplace(initializer_list<U>, Args&&...); 名前(emplace)もやっぱり直接構築っぽい。 2 個あるのは直接構築コンストラクタと同様の理由。 コンストラクタと違って名前で区別できてるので、引数に std::in_place は要らないよ… -
optional objects 再構築 56 // 値無しで構築 std::optional<std::string> v1; // 1024
個の 'w' からなる string を構築 v1.emplace(1024, 'w'); // 値ありで構築 auto v2 = std::make_optional<std::vector<int>>({ 1, 2, 3 }); // いったん破棄してから、再度 vector を構築 v2.emplace({ 4, 5, 6 }); 普通の代入と違って、値があった場合でもいったん破棄されるのがポイ ント。 それはそれでいいんだけど、string とかにある assign もあった方が良かったんじゃないか なぁ…(そういや最近 assign は見かけないなぁ… -
optional objects 交換 58 普通に交換される。 挙動は2つのオブジェクトの値の有無に応じて以下のようになる。 表から分かる通り、値のムーブと交換ができなければならない。 値あり 値無し 値あり
値同士に対して swap が 呼び出される 値あり側の値から値無し 側にムーブ構築した後、 値あり側の値が破棄され る 値無し 値あり側の値から値無し 側にムーブ構築した後、 値あり側の値が破棄され る 何も起きない -
optional objects 交換 59 // メンバ関数 void swap(optional&) noexcept(see below);
// 非メンバ関数 template <class T> void swap(optional<T>&, optional<T>&) noexcept(see below); 非メンバ関数版の swap は、メンバ関数の swap を呼び出すだけ。 なお、値のムーブ構築と交換が noexcept なら、これらの関数も noxcept となる。 -
optional objects 交換 60 auto v1 = std::make_optional<std::vector<int>>({ 1, 1,
4 }); auto v2 = std::make_optional<std::vector<int>>({ 5, 1, 4 }); // なんとなく非メンバ関数版で交換!(値が入れ替わる) std::swap(v1, v2); // v1 だけ値無しにしてみる… v1 = std::nullopt; //なんとなくメンバ関数版で交換!(v1 は元に戻る、v2 は値無しになる) v1.swap(v2); 極めて普通… -
optional objects 値の存在確認 62 constexpr explicit operator bool() const noexcept;
constexpr bool has_value() const noexcept; 値があれば true、無ければ false を返す。どちらも同じ。 前者はちゃんと explicit なので、if 文の条件式とかじゃなければ暗黙変 換とかされたりはしない。(もちろん直接初期化で使えば変換される) -
optional objects 値の存在確認 63 std::optional v = 42; auto b1
= v.has_value(); // true になる。 // if 文で直接使うと、値の存在確認になる if (v) { std::cout << "値はあるぞ!¥n"; } // bool b2 = v; 暗黙変換はされないので、これはできない。 bool b3{v}; // これは明示的変換になるので OK で、true になる。 auto b4 = static_cast<bool>(v); // もちろんこれでも OK。 auto b5 = !v; // これも OK で、false になる。 -
optional objects 値の参照(普通のやつ) 65 constexpr T& value() &; constexpr T&&
value() &&; constexpr const T& value() const&; constexpr const T&& value() const&&; 値を取得する。見ての通り戻り値型が参照なので、const じゃなければ 変更もできるが、寿命には注意が必要(特に右辺値参照)。 もし値が無い場合は std::bad_optional_access 例外が発生するので安心 (?)。 const&& バージョンなんているの?と思うかもしれないが、これが無いと戻り値型が const T& になっちゃうので(右辺値参照がいつのまにか左辺値参照に!)無いと困るし、 mutable とかがあるともしかしたら結構有効に機能するかも… -
optional objects 値の参照(普通のやつ) 66 std::optional v = 42; auto i
= v.value(); // 値が取得されて i は 42 になる v.value() = 99; // 参照なので更新もできる v = std::nullopt; try { auto j = v.value(); // 値が無い時に参照すると例外が発生する } catch (std::bad_optional_access&) { std::cout << "ひでぶ!¥n"; } -
optional objects 値の参照(普通のやつ) 67 ちなみに、右辺値参照返ってくるからと言ってうかつに参照で受けると、 痛い目を見るから、参照もほどほどにね! // ヤバい!dangling reference!!! auto&&
v1 = std::make_optional<std::vector<int>>({ 1, 1, 4 }).value(); // これもヤバい!dangling reference!!! const auto& v2 = std::make_optional<std::vector<int>>({ 5, 1, 4 }).value(); optional を直接参照で受けてれば、C++ のナゾ機能によって寿命が 延びるんだけど、value の結果だけ受けても寿命延びないからね… 右辺値参照はあくまでもその式内で完結させて、せいぜい最後にムー ブ構築に使う程度で! // 値で受けてもムーブ構築されるから、そこまで高コストじゃないよ! auto v3 = std::make_optional<std::vector<int>>({ 6, 3, 4 }).value(); -
optional objects 値の参照(デフォルト値付き) 69 template <class U> constexpr T value_or(U&&)
const&; template <class U> constexpr T value_or(U&&) &&; 値がある場合はその値を、無い場合は引数で指定した値(からT型の値 を構築した結果)をデフォルト値として返す。見ての通り戻り値型は参照 じゃなく単なる値なので、寿命とか気にする必要なくて安心。 引数の型は T じゃなくてもいいけど、もちろん T 型の値を構築できる必 要がある。 ちなみに、value() と違って参照を返さないので、const& と && だけしか ないよ。(必要ない) ところで、何でこいつにはテンプレート引数 U にデフォルト値が無いんですかね… -
optional objects 値の参照(デフォルト値付き) 70 std::optional v = 42; auto i
= v.value_or(99); // 値があるので i は 42 になる! // v.value_or(0) = 99; // value と違って更新はできないぞ! // 戻り値型が参照じゃないからね、仕方ないね… v = std::nullopt; auto j = v.value_or(99); // 値が無いので j は 99 になるよ! auto k = v.value_or(3.14);// 型が違っても平気だけど、 // 使われる時は当然変換されるぞ。 // この場合 k は 3 になる! -
optional objects 値の参照(ポインタっぽく) 72 constexpr T& operator*() &; constexpr T&&
operator*() &&; constexpr const T& operator*() const&; constexpr const T&& operator*() const&&; 値を取得する。値が無いって言うと nullptr っぽいので、ポインタっぽくア クセス! ぱっと見 value() と全く同じように見えるけど、こちらは値が無い場合に はみんな大好き未定義動作になるので安心!(安心とは 逆に言えば、こちらの実装は値の有無をチェックしなくてもいいので、 value() より速い、…かもしれない… vector の at と operator[] みたいな感じ… -
optional objects 値の参照(ポインタっぽく) 73 std::optional v = 42; auto i
= *v; // 値が取得されて i は 42 になる *v = 99; // もちろん参照なので更新もできるぞ! v = std::nullopt; auto j = *v; // ダメ。ゼッタイ。(UB) ちなみに、参照の危険性は value と一緒! // ヒャッハー!!!(dangling reference) auto&& k = *make_optional<std::vector<int>>({ 1, 1, 4 }); -
optional objects 値の参照(メンバアクセスっぽく) 75 constexpr T* operator->(); constexpr const T*
operator->() const; operator* と同様、スマートポインタでのメンバアクセスっぽく。 値が無い場合にみんな大好き未定義動作になるのも operator* と同じ。 ただし、こちらはポインタが返るので、戻り値型で右辺値左辺値の区別ができな い。 このため、たとえ右辺値に対して使ってもメンバアクセスの結果は左辺値になっ てしまうので、激しく注意が必要! -
optional objects 値の参照(メンバアクセスっぽく) 76 struct S { int m =
42; }; auto v = std::make_optional<S>(); auto i = v->m; // メンバの値が取得されて i は 42 になる v->m = 99; // ポインタ経由なので更新もできる(こわい v = std::nullopt; auto j = v->m; // ダメ。ゼッタイ。(UB) -
optional objects 値の参照(メンバアクセスっぽく) 77 右辺値参照にはくれぐれもご注意を! struct S { int m
= 42; void f() & { /* 左辺値版 */ } void f() && { /* 右辺値版 */ } }; // コイツ、右辺値のくせに左辺値版が動くぞ… std::make_optional<S>()->f(); // 一時オブジェクトに代入できてどうすんねん… std::make_optional<S>()->m = 99; -
optional objects クリア 79 void reset() noexcept; 値を破棄する。もともと値が無ければ何もしない。 とっても思った通りに動く。 std::optional<std::string>
v = "値はあるぞ!"; // 値ありで構築 v.reset(); // ここで値を破棄 v.reset(); // もう一度 reset を呼んでも何もしない v = "復活するぞ!"; // もちろん、再度割り当てることもできる -
optional objects 比較演算子 82 optional 同士の比較 template <class T, class
U> constexpr bool operator@@(const optional<T>&, const optional<U>&); @@ は全部で6種類(==、!=、<、<=、>、>=)。 T と U は異なる型で OK だけど、対応する演算子で比較可能じゃないと ダメ。(当たり前) 比較のされ方を簡単に言うと、「値無し」は「どんな optional にも共通の 値」で、かつ、「他のどんな普通の値よりも小さい、別の値」とみなされる、 と言うこと。 -
optional objects 比較演算子 83 等値比較(== 、!=)の場合 1. 両方とも値が無ければ等しい 2. 片方しか値が無ければ等しくない
3. 両方とも値があれば、値を対応する演算子で比較した結果が、最 終的な結果になる 大小比較(<、<=、>、>=)の場合 1. 両方とも値が無ければ等しい 2. 値が無い方は値がある方より常に小さい 3. 両方とも値があれば、値を対応する演算子で比較した結果が、最 終的な結果になる -
optional objects 比較演算子 84 optional と nullopt_t との比較 template <class
T> constexpr bool operator@@(const optional<T>&, nullopt_t ) noexcept; template <class T> constexpr bool operator@@(nullopt_t, const optional<T>&) noexcept; 全6種類(==、!=、<、<=、>、>=)×2種類(nullopt_t が左辺か右辺か)。 結果は「nullopt_t」を「値の無い optional」だと思えばいいだけ。 -
optional objects 比較演算子 85 optional と普通の値との比較 template <class T, class
U> constexpr bool operator@@(const optional<T>&, const U&); template <class T, class U> constexpr bool operator@@(const T&, const optional<U>&); 全6種類(==、!=、<、<=、>、>=)×2種類(普通の値が左辺か右辺か)。 結果は「普通の値」を「値のある optional」だと思えばいいだけ。 -
optional objects 比較演算子 86 std::optional v = 42; // int
std::optional w = 3.14; // double auto b1 = v < w; // 普通に比較できて false になる auto b2 = v == 42.0; // 普通に比較できて true になる v = std::nullopt; auto b3 = v <= w; // 値無しは値有より小さいので // true になる auto b4 = v == std::nullopt; // 値無し同士で true になる -
optional objects ハッシュサポート 88 template <class T> struct hash<optional<T>>; std::optional<T>
に対する std::hash の特殊化。 std::hash<std::remove_const_t<T>> がハッシュをサポートしている場合に限って この特殊化が提供される。 これによって、std::optional<T> が、みんな大好き「非順序連想コンテナ」のキー として使用できるようになるぞ! なお、値がある場合のハッシュ値は std::remove_const_t<T> のハッシュ値と同 一と決まっているけど、値が無い場合のハッシュ値は未規定だよ(未定義動作 なわけじゃない)。 ちなみに、ユニークキー系の場合は、「値無し」キーは1つしか存在できないよ! (当たり前) -
optional objects ハッシュサポート 89 // みんな大好き unordered_set std::unordered_set<std::optional<std::string>> us; //
普通に要素追加できるぞ! us.emplace("ゴーン has gone"); us.emplace("ゴーン、お前だったのか…"); // もちろん値無しも追加できるぞ!(std::nullopt でもいいよ) us.emplace(); // ちょっと変わったコンストラクタだと、 // 効率考えると std::in_place 使わなきゃならないの悲しい… us.emplace(std::in_place, 1024, 'w'); // 空文字列追加とか… (値のデフォルト構築) us.emplace(std::in_place); -
optional objects ハッシュサポート 90 // みんな大好き unordered_map std::unordered_map<std::optional<std::string>, std::string> um;
// 普通に要素追加できるぞ! um.emplace("ゴーン", "has gone"); // 同じキーは当然存在できないぞ! um.emplace("ゴーン", "お前だったのか…"); // 値無しキーの追加で std::nullopt 大活躍! um.emplace(std::nullopt, "値ねぇし!"); // 値のデフォルト構築で、std::in_place が避けられない… um.emplace(std::in_place, "空文字だぞ!"); // ちょっと変わったコンストラクタだと、もはや何が何だか… um.emplace(std::piecewise_construct, std::forward_as_tuple(std::in_place, 1024, 'w'), std::forward_as_tuple("テラワロス!")); -
optional objects メンバ型 92 using value_type = T; テンプレート型引数 T
に渡した型を、ネストしたメンバ型 value_type で参 照できる。 ちょっとコンテナっぽい… std::optional v1 = 42; // 型推論で T は int になる decltype(v1)::value_type i = 114514; // これは int になる std::optional v2 = 3.14; // 型推論で T は double になる decltype(v2)::value_type d = 1.41421356; // これは double になる でも、これ使うかな…(メタメタしい人は使うかな? -
optional objects その他補足 94 std::optional<bool> まあ、ぶっちゃけやめといた方が無難。 std::optional v = false;
if (!v) { // 値が無い時なのか、false の時なのか func(); // どっちだよ!!!1! } そもそも、ほとんどのケースで単なる enum でもよさそう… ホントに3値論理欲しいなら、boost::tribool 使うとか… -
optional objects その他補足 95 std::optional<T*> ポインタ自体にも nullptr あるんだから、optional にする必要なくない? int
i = 42; std::optional v = &i; func(*v); // お前が欲しかったのはホントにポインタなのか? // 実は int が欲しかったんじゃないのか? -
optional objects その他補足 96 std::optional<std::optional<T>> いやもはや std::variant<int, T> とか std::variant<enum,
T> とかの方が 良くない? std::optional<std::optional<int>> v = std::nullopt; func(v.value_or(114).value_or(514)); // 何て? -
optional objects その他補足 97 メモリ使用量 optional は値の有無を覚えておく必要があるから、普通に考えて 1 バイ トは余分に容量食うけど、アラインメントの問題があるので思ったよりも
大きくなっちゃうかも… std::cout << sizeof(double) << ',' << alignof(double) << ',' << sizeof(std::optional<double>) << ',' << alignof(std::optional<double>) << '¥n'; 64bit Linux 環境では GCC も Clang も "8,8,16,8" だった。 まぁそりゃそうだよね… -
optional objects その他補足 98 こんなのがあると、ちょっとメモリをムダにしてる感が… struct S { std::optional<double> b;
std::optional<double> e; }; こんな感じの方がメモリ少なくて済むかな… struct S { double b; double e; bool bExists; bool eExists; }; -
optional objects その他補足 99 テンプレート引数の制限 テンプレート引数にはほとんどなんでも指定できるけど、以下のものは 指定できない。 • std::nullopt_t コンストラクタが曖昧になるし、何より意味が分からないので、できて
もやらないと思う… • std::in_place_t これもコンストラクタが曖昧になるし、何より意味が分からないので、 できてもやらないと思う… • 参照 これはやりたい人いるのかな?(boost::optional は出来る) まぁどうしてもやりたい場合は reference_wrapper 使いましょう… -
optional objects その他補足 100 std::nullopt_t のデフォルト構築 std::nullopt_t はデフォルト構築できない。 できちゃうと、以下の代入が曖昧になるかららしい… std::optional<int>
v = 42; v = {}; // v は値を持たない C++難しいな…