yohhoyの日記

技術的メモをしていきたい日記

std::expectedと強い例外安全性

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ムーブ構築可能” であること
  • swapTもしくは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() is true, equivalent to val = *rhs.
  • Otherwise, if this->has_value() is true, equivalent to: reinit-expected(unex, val, rhs.error())
  • Otherwise, if rhs.has_value() is true, 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() is false and this->has_value() is true, 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