最終更新日時: 2025年07月14日 23時04分00秒 (JST)
Koichi Murase が更新

履歴 編集

function
<expected>

# std::expected::operator=(C++23)

    constexpr expected& operator=(const expected& rhs);    // (1)

    constexpr expected& operator=(expected&& rhs) noexcept(see below); // (2)

    template<class U = T>
    constexpr expected& operator=(U&& v);                  // (3)

    template<class G>
    constexpr expected& operator=(const unexpected<G>& e); // (4)

    template<class G>
    constexpr expected& operator=(unexpected<G>&& e);      // (5)

## 概要

- (1) : コピー代入。
- (2) : ムーブ代入。
- (3) : 要素型`T`に変換可能な値を、正常値としてコピー代入またはムーブ代入。
- (4) : 変換可能な`unexpected`オブジェクトから、エラー値としてコピー代入。
- (5) : 変換可能な`unexpected`オブジェクトから、エラー値としてムーブ代入。

動作説明用の`expected`クラスメンバ変数として、下記を導入する。

- `val` : `T`型の正常値。
- `unex` : `E`型のエラー値。
- `has_val` : `bool`型のフラグ変数。正常値を保持する場合は`true`に、エラー値を保持する場合は`false`となる。

また、説明用のテンプレート関数`reinit-expected`を次の通り定義する。

    template<class T, class U, class... Args>
    constexpr void reinit-expected(T& newval, U& oldval, Args&&... args) {
      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;
        }
      }
    }

## テンプレートパラメータ制約

- (2) : 次の制約を全て満たすこと
  - `is_move_constructible_v<T> == true`
  - `is_move_assignable_v<T> == true`
  - `is_move_constructible_v<E> == true`
  - `is_move_assignable_v<E> == true`
  - `(is_nothrow_move_constructible_v<T> || is_nothrow_move_constructible_v<E>) == true`
- (3) : 次の制約を全て満たすこと
  - `is_same_v<expected, remove_cvref_t<U>> == false`
  - `remove_cvref_t<U>`が`unexpected`の特殊化ではない
  - `is_constructible_v<T, U> == true`
  - `is_assignable_v<T&, U> == true`
  - `(is_nothrow_constructible_v<T, U> || is_nothrow_move_constructible_v<T> || is_nothrow_move_constructible_v<E>) == true`
- (4) : 次の制約を全て満たすこと
  - `is_constructible_v<E, const G&> == true`
  - `is_assignable_v<E&, const G&> == true`
  - `(is_nothrow_constructible_v<E, const G&> || is_nothrow_move_constructible_v<T> ||is_nothrow_move_constructible_v<E>) == true`
- (5) : 次の制約を全て満たすこと
  - `is_constructible_v<E, G> == true`
  - `is_assignable_v<E&, G> == true`
  - `(is_nothrow_constructible_v<E, G> || is_nothrow_move_constructible_v<T> || is_nothrow_move_constructible_v<E>) == true`

## 効果

- (1) : 次の処理を行ったのち、例外が送出されなければ、`has_val = rhs.has_value(); return *this;`
  - `this`と`rhs`が共に正常値を保持していたら、`val = *rhs`
  - `this`が正常値を保持し、`rhs`がエラー値を保持していたら、`reinit-expected(unex, val, rhs.error())`
  - `this`がエラーを保持し、`rhs`が正常値を保持していたら、`reinit-expected(val, unex, *rhs)`
  - `this`と`rhs`が共にエラー値を保持していたら、`unex = rhs.error()`
- (2) : 次の処理を行ったのち、例外が送出されなければ、`has_val = rhs.has_value(); return *this;`
  - `this`と`rhs`が共に正常値を保持していたら、`val = std::move(*rhs)`
  - `this`が正常値を保持し、`rhs`がエラー値を保持していたら、`reinit-expected(unex, val, std::move(rhs.error()))`
  - `this`がエラーを保持し、`rhs`が正常値を保持していたら、`reinit-expected(val, unex, std::move(*rhs))`
  - `this`と`rhs`が共にエラー値を保持していたら、`unex = std::move(rhs.error())`
- (3) : 次の処理と等価
  - `this`が正常値を保持していたら、`val = std::forward<U>(v)`
  - `this`がエラー値を保持していたら、`reinit-expected(val, unex, std::forward<U>(v)); has_val = true;`
- (4) : 次の処理と等価
  - `this`が正常値を保持していたら、`reinit-expected(unex, val, std::forward<const G&>(e.error())); has_val = false;`
  - `this`がエラー値を保持していたら、`unex = std::forward<const G&>(e.error());`
- (5) : 次の処理と等価
  - `this`が正常値を保持していたら、`reinit-expected(unex, val, std::forward<G>(e.error())); has_val = false;`
  - `this`がエラー値を保持していたら、`unex = std::forward<G>(e.error());`

## 戻り値

`*this`

## 例外

- (2) : ムーブ代入演算子のnoexcept例外指定は、次の式に従う
  - `is_nothrow_move_assignable_v<T> && is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<E> && is_nothrow_move_constructible_v<E>`

## delete定義される条件

- (1) : 下記いずれか1つでも満たされないとき、コピー代入演算子はdelete定義される。
  - `is_copy_assignable_v<T> == true`
  - `is_copy_constructible_v<T> == true`
  - `is_copy_assignable_v<E> == true`
  - `is_copy_constructible_v<E> == true`
  - `(is_nothrow_move_constructible_v<T> || is_nothrow_move_constructible_v<E>) == true`

## 例

    #include <cassert>
    #include <expected>
    #include <memory>
    #include <string>
    #include <tuple>
    #include <utility>

    // std::pair型から2要素std::tuple型へはコピー代入可能
    using IntPair  = std::pair<int, int>;
    using IntTuple = std::tuple<int, int>;

    // std::unique_ptr型からstd::shared_ptr型へはムーブ代入可能
    using UniquePtr = std::unique_ptr<int>;
    using SharedPtr = std::shared_ptr<int>;

    int main()
    {
      // (1) コピー代入
      {
        std::expected<int, std::string> srcV = 42;
        std::expected<int, std::string> dstV;
        dstV = srcV;
        assert(srcV.has_value() && dstV.has_value());
        assert(srcV.value() == 42 && dstV.value() == 42);

        std::expected<int, std::string> srcE = std::unexpected{"Oops"};
        std::expected<int, std::string> dstE;
        dstE = srcE;
        assert(!srcE.has_value() && !dstE.has_value());
        assert(srcE.error() == "Oops" && dstE.error() == "Oops");
      }

      // (2) ムーブ代入
      {
        std::expected<std::string, int> srcV = "ok";
        std::expected<std::string, int> dstV;
        dstV = std::move(srcV);
        assert(srcV.has_value() && dstV.has_value());
        assert(dstV.value() == "ok");
        // srcV.value()はstd::stringムーブ後の未規定の値

        std::expected<int, std::string> srcE = std::unexpected{"ng"};
        std::expected<int, std::string> dstE;
        dstE = std::move(srcE);
        assert(!srcE.has_value() && !dstE.has_value());
        assert(dstE.error() == "ng");
        // srcE.error()はstd::stringムーブ後の未規定の値
      }

      // (3) 正常値の変換コピー代入
      {
        IntPair src = IntPair{1, 2};
        std::expected<IntTuple, int> dst;
        dst = src;
        assert(dst.has_value());
        assert((dst.value() == IntTuple{1, 2}));
      }
      // (3) 正常値の変換ムーブ代入
      {
        UniquePtr src = std::make_unique<int>(42);
        std::expected<SharedPtr, int> dst;
        dst = std::move(src);
        assert(dst.has_value());
        assert(*dst.value() == 42);
      }

      // (4) エラー値の変換コピー代入
      {
        std::unexpected<IntPair> src{IntPair{1, 2}};
        std::expected<int, IntTuple> dst;
        dst = src;
        assert(not dst.has_value());
        assert((dst.error() == IntTuple{1, 2}));
      }

      // (5) エラー値の変換ムーブ代入
      {
        std::unexpected<UniquePtr> src{std::make_unique<int>(42)};
        std::expected<int, SharedPtr> dst;
        dst = std::move(src);
        assert(not dst.has_value());
        assert(*dst.error() == 42);
      }
    }

### 出力

## バージョン

### 言語

- C++23

### 処理系

- Clang: 16.0 ✅
- GCC: 12.1 ✅
- ICC: ??
- Visual C++: ??

## 参照

- P0323R12 std::expected