std::expected(続々)
この記事は以前書いていたstd::expected(続)の続きです。
今回はいよいよSy Brandの実装の中を見ていきます。
/// An `expected<T, E>` object is an object that contains the storage for
/// another object and manages the lifetime of this contained object `T`.
/// Alternatively it could contain the storage for another unexpected object
/// `E`. The contained object may not be initialized after the expected object
/// has been initialized, and may not be destroyed before the expected object
/// has been destroyed. The initialization state of the contained object is
/// tracked by the expected object.
template <class T, class E>
class expected : private detail::expected_move_assign_base<T, E>,
private detail::expected_delete_ctor_base<T, E>,
private detail::expected_delete_assign_base<T, E>,
private detail::expected_default_ctor_base<T, E> {
static_assert(!std::is_reference<T>::value, "T must not be a reference");
static_assert(!std::is_same<T, std::remove_cv<in_place_t>::type>::value,
"T must not be in_place_t");
static_assert(!std::is_same<T, std::remove_cv<unexpect_t>::type>::value,
"T must not be unexpect_t");
static_assert(!std::is_same<T, typename std::remove_cv<unexpected<E>>::type>::value,
"T must not be unexpected<E>");
static_assert(!std::is_reference<E>::value, "E must not be a reference");
コメント部分の直訳
expected<T, E>
オブジェクトは、他のオブジェクトのストレージを含み、この含まれるオブジェクト T
のライフタイムを管理するオブジェクトである。
あるいは、別の予期しないオブジェクト E
のためのストレージを含むこともできます。
含まれるオブジェクトは期待されるオブジェクトが初期化された後には初期化されてはならず、期待されるオブジェクトが破壊される前に破壊されてはならない。
内包されたオブジェクトの初期化状態は、期待されるオブジェクトによって追跡されます。
よくわからないのでいくつかに分けて見ていきます。
static_assert
コンパイル時Assertです。条件を満たさない型が渡されるとコンパイルエラーにします。つまり、渡された型はexpected
が扱う型としては契約違反と親切に教えてくれる部分です。
static_assert(!std::is_reference<T>::value, "T must not be a reference");
// ->参照型をTとして渡すことは禁止!!!
static_assert(!std::is_same<T, std::remove_cv<in_place_t>::type>::value,
"T must not be in_place_t");
// ->in_place_t型をTとして渡すことは禁止!!!
static_assert(!std::is_same<T, std::remove_cv<unexpect_t>::type>::value,
"T must not be unexpect_t");
// ->unexpect_t型をTとして渡すことは禁止!!!
static_assert(!std::is_same<T, typename std::remove_cv<unexpected<E>>::type>::value,
"T must not be unexpected<E>");
static_assert(!std::is_reference<E>::value, "E must not be a reference");
// 参照型をEとして渡すことは禁止!!!
- 参照型を渡してはいけないのは?
union
に参照型を渡すことはできないためです。[1] - unexpect_t型をTとして渡すことは禁止なのは?
unexpect_t
はT
=E
の場合にコンストラクタ引数が重複しないためにE
のために作ったクラスです。[2] -
in_place_t
型をTとして渡すことは禁止なのは?
in_place_t
型は実は意味があるクラスではなく、初期化するコンストラクタを選択するためのタグで意味がないクラスのため禁止されています。
int main()
{
// std::in_placeがないと実はコンパイルエラー
std::optional<std::string> p{ 3, 'A' };
// stringクラスのコンストラクタ引数3と'A'をとり、
// optionalクラス内でstring型のオブジェクトを生成する。
std::optional<std::string> p{ std::in_place, 3, 'A' };
/* 次のようなオーバーロードするためのタグとなる引数
template <class... _Types> >
constexpr explicit optional(in_place_t, _Types&&... _Args) : */
}
private継承
private継承はほぼ使うときはないですが外部に公開されない継承ということを利用して、"基底クラスの実装を再利用すること"を目的に使われます。ここでは、T,Eのコンストラクタ、コピーコンストラクタ、代入演算子、move演算子、move代入演算子の実装を切り替えることを実現しています。
テンプレートメタプログラミングではコンパイル時にコードが実体化されるため、Tがデフォルトコンストラクタを持っていないのにデフォルトコンストラクタを呼ぶようなコードが生成されるとコンパイルエラーになってしまいます。こういったことを防ぐためにprivateな基底クラスをテンプレートの特殊化を使って実装を分けるということをしています。
コンストラクタ、コピーコンストラクタ、代入演算子、move演算子、move代入演算子、デストラクタはご存じのとうりデフォルトで勝手に作られるのですが、デフォルトとユーザー定義のものでは扱いが違います。
以下のようにコメントを追加することで意図がわかりやすくなったと思います。
template <class T, class E>
class expected :
private detail::expected_move_assign_base<T, E>,
// ->トリビアルなmove代入演算子を持つか否かで実装を変更する,
private detail::expected_delete_ctor_base<T, E>,
// ->T と E がコピー/ムーブコンストラクタブルかどうかに応じて、
// コピーとムーブコンストラクタを条件付きで削除します,
private detail::expected_delete_assign_base<T, E>,
// ->T と E がコピー/ムーブコンストラクタブル + アサイン可能か
// どうかに応じて、条件付きでコピー/ムーブコンストラクタを削除します。
private detail::expected_default_ctor_base<T, E> {
// ->Tがデフォルトで構成可能でない場合に、期待されるデフォルトの
// コンストラクタが削除されることを保証します。
クラス図
-
前回のstd::expected(続) 共用体の新たな仕様を参照 ↩︎
-
前回のstd::expected(続) TとEが同じ型を参照 ↩︎
Discussion