(root)/
gcc-13.2.0/
libstdc++-v3/
testsuite/
20_util/
expected/
assign.cc
// { dg-options "-std=gnu++23" }
// { dg-do run { target c++23 } }

#include <expected>
#include <type_traits>
#include <testsuite_hooks.h>

int dtor_count;
constexpr void reset_dtor_count()
{
  if (!std::is_constant_evaluated())
    dtor_count = 0;
}
constexpr void inc_dtor_count()
{
  if (!std::is_constant_evaluated())
    ++dtor_count;
}
constexpr bool check_dtor_count(int c)
{
  if (std::is_constant_evaluated())
    return true;
  return dtor_count == c;
}

struct X
{
  constexpr X(int i, int j = 0) noexcept : n(i+j) { }
  constexpr X(std::initializer_list<int> l, void*) noexcept : n(l.size()) { }

  constexpr X(const X&) = default;
  constexpr X(X&& x) noexcept : n(x.n) { x.n = -1; }

  constexpr X& operator=(const X&) = default;
  constexpr X& operator=(X&& x) noexcept { n = x.n; x.n = -1; return *this; }

  constexpr ~X()
  {
    inc_dtor_count();
  }

  constexpr bool operator==(const X&) const = default;
  constexpr bool operator==(int i) const { return n == i; }

  int n;
};

constexpr bool
test_copy(bool = true)
{
  reset_dtor_count();

  std::expected<int, int> e1(1), e2(2), e3(std::unexpect, 3);

  e1 = e1;
  e1 = e2; // T = T
  VERIFY( e1.value() == e2.value() );
  e1 = e3; // T = E
  VERIFY( ! e1.has_value() );
  VERIFY( e1.error() == e3.error() );
  e1 = e3; // E = E
  VERIFY( ! e1.has_value() );
  VERIFY( e1.error() == e3.error() );
  e1 = e2; // E = T
  VERIFY( e1.value() == e2.value() );

  e1 = std::move(e1);
  e1 = std::move(e2); // T = T
  VERIFY( e1.value() == e2.value() );
  e1 = std::move(e3); // T = E
  VERIFY( ! e1.has_value() );
  VERIFY( e1.error() == e3.error() );
  e1 = std::move(e3); // E = E
  VERIFY( ! e1.has_value() );
  VERIFY( e1.error() == e3.error() );
  e1 = std::move(e2); // E = T
  VERIFY( e1.value() == e2.value() );

  std::expected<X, X> x1(1), x2(2), x3(std::unexpect, 3);

  x1 = x1;

  x1 = x2; // T = T
  VERIFY( check_dtor_count(0) );
  VERIFY( x1.value() == x2.value() );
  x1 = x3; // T = E
  VERIFY( check_dtor_count(1) );
  VERIFY( ! x1.has_value() );
  x1 = x3; // E = E
  VERIFY( check_dtor_count(1) );
  VERIFY( ! x1.has_value() );
  x1 = x2; // E = T
  VERIFY( check_dtor_count(2) );
  VERIFY( x1.value() == x2.value() );

  reset_dtor_count();

  x1 = std::move(x1);
  VERIFY( x1.value() == -1 );

  x1 = std::move(x2); // T = T
  VERIFY( check_dtor_count(0) );
  VERIFY( x1.value() == 2 );
  VERIFY( x2.value() == -1 );
  x1 = std::move(x3); // T = E
  VERIFY( check_dtor_count(1) );
  VERIFY( ! x1.has_value() );
  VERIFY( x1.error() == 3 );
  VERIFY( x3.error() == -1 );
  x3.error().n = 33;
  x1 = std::move(x3); // E = E
  VERIFY( check_dtor_count(1) );
  VERIFY( ! x1.has_value() );
  VERIFY( x1.error() == 33 );
  VERIFY( x3.error() == -1 );
  x2.value().n = 22;
  x1 = std::move(x2); // E = T
  VERIFY( check_dtor_count(2) );
  VERIFY( x1.value() == 22 );
  VERIFY( x2.value() == -1 );

  std::expected<void, int> ev1, ev2, ev3(std::unexpect, 3);

  ev1 = ev2; // T = T
  VERIFY( ev1.has_value() );
  ev1 = ev3; // T = E
  VERIFY( ! ev1.has_value() );
  VERIFY( ev1.error() == ev3.error() );
  ev1 = ev3; // E = E
  VERIFY( ! ev1.has_value() );
  VERIFY( ev1.error() == ev3.error() );
  ev1 = ev2; // E = T
  VERIFY( ev1.has_value() );

  reset_dtor_count();
  std::expected<void, X> xv1, xv2, xv3(std::unexpect, 3);

  xv1 = std::move(xv2); // T = T
  VERIFY( check_dtor_count(0) );
  VERIFY( xv1.has_value() );
  xv1 = std::move(xv3); // T = E
  VERIFY( check_dtor_count(0) );
  VERIFY( ! xv1.has_value() );
  VERIFY( xv1.error() == 3 );
  VERIFY( xv3.error() == -1 );
  xv3.error().n = 33;
  xv1 = std::move(xv3); // E = E
  VERIFY( check_dtor_count(0) );
  VERIFY( xv1.error() == 33 );
  VERIFY( xv3.error() == -1 );
  xv1 = std::move(xv2); // E = T
  VERIFY( check_dtor_count(1) );
  VERIFY( xv1.has_value() );

  return true;
}

constexpr bool
test_converting(bool = true)
{
  std::expected<int, int> e1(1);
  std::expected<unsigned, long> e2(2U), e3(std::unexpect, 3L);
  e1 = e2;
  VERIFY( e1.value() == e2.value() );
  e1 = e3;
  VERIFY( ! e1.has_value() );
  VERIFY( e1.error() == e3.error() );
  e1 = e2;
  VERIFY( e1.value() == e2.value() );

  e1 = std::move(e3);
  VERIFY( ! e1.has_value() );
  VERIFY( e1.error() == e3.error() );
  e1 = std::move(e2);
  VERIFY( e1.value() == e2.value() );

  std::expected<void, int> ev4;
  std::expected<const void, long> ev5(std::unexpect, 5);
  ev4 = ev5;
  VERIFY( ! ev4.has_value() );
  VERIFY( ev4.error() == 5 );
  ev4 = std::expected<volatile void, unsigned>();
  VERIFY( ev4.has_value() );
  ev4 = std::move(ev5);
  VERIFY( ! ev4.has_value() );
  VERIFY( ev4.error() == 5 );

  return true;
}

constexpr bool
test_unexpected(bool = true)
{
  reset_dtor_count();

  std::expected<X, int> e1(0);

  e1 = std::unexpected<int>(5);
  VERIFY( ! e1.has_value() );
  VERIFY( e1.error() == 5 );
  VERIFY( check_dtor_count(1) );

  e1 = std::unexpected<int>(6);
  VERIFY( check_dtor_count(1) );

  std::expected<int, X> e2;

  std::unexpected<X> x(std::in_place, 1, 2);
  e2 = x;
  VERIFY( check_dtor_count(1) );

  e2 = 1;
  VERIFY( e2.value() == 1 );
  VERIFY( check_dtor_count(2) );

  return true;
}

constexpr bool
test_emplace(bool = true)
{
  reset_dtor_count();

  std::expected<int, int> e1(1);
  e1.emplace(2);
  VERIFY( e1.value() == 2 );

  std::expected<void, int> ev2;
  ev2.emplace();
  VERIFY( ev2.has_value() );

  std::expected<X, int> e3(std::in_place, 0, 0);

  e3.emplace({1,2,3}, nullptr);
  VERIFY( e3.value() == 3 );
  VERIFY( check_dtor_count(1) );

  e3.emplace(2, 2);
  VERIFY( e3.value() == 4 );
  VERIFY( check_dtor_count(2) );

  std::expected<int, X> ev4(std::unexpect, 4);

  ev4.emplace(5);
  VERIFY( ev4.value() == 5 );
  VERIFY( check_dtor_count(3) );

  ev4.emplace(6);
  VERIFY( ev4.value() == 6 );
  VERIFY( check_dtor_count(3) );

  return true;
}

void
test_exception_safety()
{
  struct CopyThrows
  {
    CopyThrows(int i) noexcept : x(i) { }
    CopyThrows(const CopyThrows&) { throw 1; }
    CopyThrows(CopyThrows&&) = default;
    CopyThrows& operator=(const CopyThrows&) = default;
    CopyThrows& operator=(CopyThrows&&) = default;
    int x;

    bool operator==(int i) const { return x == i; }
  };

  struct MoveThrows
  {
    MoveThrows(int i) noexcept : x(i) { }
    MoveThrows(const MoveThrows&) = default;
    MoveThrows(MoveThrows&&) { throw 1L; }
    MoveThrows& operator=(const MoveThrows&) = default;
    MoveThrows& operator=(MoveThrows&&) = default;
    int x;

    bool operator==(int i) const { return x == i; }
  };

  std::expected<CopyThrows, MoveThrows> c(std::unexpect, 1);

  // operator=(U&&)
  try {
    CopyThrows x(2);
    c = x;
    VERIFY( false );
  } catch (int) {
    VERIFY( ! c.has_value() );
    VERIFY( c.error() == 1 );
  }

  c = CopyThrows(2);

  try {
    c = std::unexpected<MoveThrows>(3);
    VERIFY( false );
  } catch (long) {
    VERIFY( c.value() == 2 );
  }
}

int main(int argc, char**)
{
  bool non_constant = argc == 1; // force non-constant evaluation

  static_assert( test_copy() );
  test_copy(non_constant);
  static_assert( test_converting() );
  test_converting(non_constant);
  static_assert( test_unexpected() );
  test_unexpected(non_constant);
  static_assert( test_emplace() );
  test_emplace(non_constant);

  test_exception_safety();

  // Ensure the non-constexpr tests actually ran:
  VERIFY( dtor_count != 0 );
}