C++2b(C++23)標準ライブラリに追加されるstd::expected<T, E>の例外安全性についてメモ。
正常型T/エラー型Eを値(value)として取り扱いつつ「強い例外安全性(Strong exception safety)」を満たすために、テンプレートパラメータに一定の制約が課される。
- ムーブ/コピー代入:
TもしくはEのいずれかが “nothrowムーブ構築可能” であること - 正常型
U代入:Tが “nothrow構築可能” または、TもしくはEのいずれかが “nothrowムーブ構築可能” であること - エラー型
G代入:Eが “nothrow構築可能” または、TもしくはEのいずれかが “nothrowムーブ構築可能” であること swap:TもしくはEのいずれかが “nothrowムーブ構築可能” であること
T型/E型の少なくとも一方でnothrow保証のある構築手段を確保し、他方の型において例外送出された場合は操作前状態までロールバック処理*1を行う(後述引用のcatch節を参照)。
C++2b DIS N4950 22.8.6.4/p1-2, 22.8.6.5/p2より引用。各種代入演算子(operator=)の効果は説明用reinit-expected関数を用いて規定される。
1 This subclause makes use of the following exposition-only function:
template<class T, class U, class... Args> constexpr void reinit-expected(T& newval, U& oldval, Args&&... args) { // exposition only if constexpr (is_nothrow_constructible_v<T, Args...>) { destroy_at(addressof(oldval)); construct_at(addressof(newval), std::forward<Args>(args)...); } else if constexpr (is_nothrow_move_constructible_v<T>) { T tmp(std::forward<Args>(args)...); destroy_at(addressof(oldval)); construct_at(addressof(newval), std::move(tmp)); } else { U tmp(std::move(oldval)); destroy_at(addressof(oldval)); try { construct_at(addressof(newval), std::forward<Args>(args)...); } catch (...) { construct_at(addressof(oldval), std::move(tmp)); throw; } } }constexpr expected& operator=(const expected& rhs);2 Effects:
- If
this->has_value() && rhs.has_value()istrue, equivalent toval = *rhs.- Otherwise, if
this->has_value()istrue, equivalent to:reinit-expected(unex, val, rhs.error())- Otherwise, if
rhs.has_value()istrue, equivalent to:reinit-expected(val, unex, *rhs)- Otherwise, equivalent to
unex = rhs.error().Then, if no exception was thrown, equivalent to:
has_val = rhs.has_value(); return *this;
constexpr void swap(expected& rhs) noexcept(see below);2 Effects: See Table 63.
Table 63:
swap(expected&)effects
this->has_value()!this->has_value()rhs.has_value()equivalent to: using std::swap; swap(val, rhs.val);calls rhs.swap(*this)!rhs.has_value()see below equivalent to: using std::swap; swap(unex, rhs.unex);For the case where
rhs.value()isfalseandthis->has_value()istrue, equivalent to:if constexpr (is_nothrow_move_constructible_v<E>) { E tmp(std::move(rhs.unex)); destroy_at(addressof(rhs.unex)); try { construct_at(addressof(rhs.val), std::move(val)); destroy_at(addressof(val)); construct_at(addressof(unex), std::move(tmp)); } catch(...) { construct_at(addressof(rhs.unex), std::move(tmp)); throw; } } else { T tmp(std::move(val)); destroy_at(addressof(val)); try { construct_at(addressof(unex), std::move(rhs.unex)); destroy_at(addressof(rhs.unex)); construct_at(addressof(rhs.val), std::move(tmp)); } catch (...) { construct_at(addressof(val), std::move(tmp)); throw; } } has_val = false; rhs.has_val = true;
関連URL
- std::expected<T, E> - yohhoyの日記
- cppreference std::expected
- cpprefjp std::expected