// { dg-do compile { target c++23 } }

// LWG 3877. Incorrect constraints on const-qualified monadic overloads

#include <expected>

struct T1
{
};

struct T2
{
  T2(const T2&) = delete;
};

struct T3
{
  T3(const T3&) = delete;
  T3(T3&&) = delete;
  T3(T3&) { }
  T3(const T3&&) { }
};

template<typename Exp, typename F>
concept Has_and_then = requires(Exp&& exp, F f) {
  std::forward<Exp>(exp).and_then(f);
};

using ExpiT1 = std::expected<int, T1>;
static_assert( Has_and_then<ExpiT1&, ExpiT1(int)> );
static_assert( Has_and_then<const ExpiT1&, ExpiT1(int)> );
static_assert( Has_and_then<ExpiT1&&, ExpiT1(int)> );
static_assert( Has_and_then<const ExpiT1&&, ExpiT1(int)> );

using ExpiT2 = std::expected<int, T2>;
static_assert( !Has_and_then<ExpiT2&, ExpiT2(int)> );
static_assert( !Has_and_then<const ExpiT2&, ExpiT2(int)> );
static_assert( !Has_and_then<ExpiT2&&, ExpiT2(int)> );
static_assert( !Has_and_then<const ExpiT2&&, ExpiT2(int)> );

using ExpiT3 = std::expected<int, T3>;
static_assert( Has_and_then<ExpiT3&, ExpiT3(int)> );
static_assert( !Has_and_then<const ExpiT3&, ExpiT3(int)> );
static_assert( Has_and_then<ExpiT3&&, ExpiT3(int)> ); // uses and_then(F) const &&
static_assert( Has_and_then<const ExpiT3&&, ExpiT3(int)> );

template<typename Exp, typename F>
concept Has_or_else = requires(Exp&& exp, F f) {
  std::forward<Exp>(exp).or_else(f);
};

using ExpT1i = std::expected<T1, int>;
static_assert( Has_or_else<ExpT1i&, ExpT1i(int)> );
static_assert( Has_or_else<const ExpT1i&, ExpT1i(int)> );
static_assert( Has_or_else<ExpT1i&&, ExpT1i(int)> );
static_assert( Has_or_else<const ExpT1i&&, ExpT1i(int)> );

using ExpT2i = std::expected<T2, int>;
static_assert( !Has_or_else<ExpT2i&, ExpT2i(int)> );
static_assert( !Has_or_else<const ExpT2i&, ExpT2i(int)> );
static_assert( !Has_or_else<ExpT2i&&, ExpT2i(int)> );
static_assert( !Has_or_else<const ExpT2i&&, ExpT2i(int)> );

using ExpT3i = std::expected<T3, int>;
static_assert( Has_or_else<ExpT3i&, ExpT3i(int)> );
static_assert( !Has_or_else<const ExpT3i&, ExpT3i(int)> );
static_assert( Has_or_else<ExpT3i&&, ExpT3i(int)> ); // uses or_else(F) const &&
static_assert( Has_or_else<const ExpT3i&&, ExpT3i(int)> );

template<typename Exp, typename F>
concept Has_transform = requires(Exp&& exp, F f) {
  std::forward<Exp>(exp).transform(f);
};

static_assert( Has_transform<ExpiT1&, int(int)> );
static_assert( Has_transform<const ExpiT1&, int(int)> );
static_assert( Has_transform<ExpiT1&&, int(int)> );
static_assert( Has_transform<const ExpiT1&&, int(int)> );

static_assert( !Has_transform<ExpiT2&, int(int)> );
static_assert( !Has_transform<const ExpiT2&, int(int)> );
static_assert( !Has_transform<ExpiT2&&, int(int)> );
static_assert( !Has_transform<const ExpiT2&&, int(int)> );

static_assert( Has_transform<ExpiT3&, int(int)> );
static_assert( !Has_transform<const ExpiT3&, int(int)> );
static_assert( Has_transform<ExpiT3&&, int(int)> ); // uses transform(F) const &&
static_assert( Has_transform<const ExpiT3&&, int(int)> );

template<typename Exp, typename F>
concept Has_transform_error = requires(Exp&& exp, F f) {
  std::forward<Exp>(exp).transform_error(f);
};

static_assert( Has_transform_error<ExpT1i&, int(int)> );
static_assert( Has_transform_error<const ExpT1i&, int(int)> );
static_assert( Has_transform_error<ExpT1i&&, int(int)> );
static_assert( Has_transform_error<const ExpT1i&&, int(int)> );

static_assert( !Has_transform_error<ExpT2i&, int(int)> );
static_assert( !Has_transform_error<const ExpT2i&, int(int)> );
static_assert( !Has_transform_error<ExpT2i&&, int(int)> );
static_assert( !Has_transform_error<const ExpT2i&&, int(int)> );

static_assert( Has_transform_error<ExpT3i&, int(int)> );
static_assert( !Has_transform_error<const ExpT3i&, int(int)> );
static_assert( Has_transform_error<ExpT3i&&, int(int)> ); // uses transform_error(F) const &&
static_assert( Has_transform_error<const ExpT3i&&, int(int)> );

// std::expected<cv void, E>

using ExpvT1 = std::expected<void, T1>;
static_assert( Has_and_then<ExpvT1&, ExpvT1()> );
static_assert( Has_and_then<const ExpvT1&, ExpvT1()> );
static_assert( Has_and_then<ExpvT1&&, ExpvT1()> );
static_assert( Has_and_then<const ExpvT1&&, ExpvT1()> );

using ExpvT2 = std::expected<void, T2>;
static_assert( !Has_and_then<ExpvT2&, ExpvT2()> );
static_assert( !Has_and_then<const ExpvT2&, ExpvT2()> );
static_assert( !Has_and_then<ExpvT2&&, ExpvT2()> );
static_assert( !Has_and_then<const ExpvT2&&, ExpvT2()> );

using ExpvT3 = std::expected<void, T3>;
static_assert( Has_and_then<ExpvT3&, ExpvT3()> );
static_assert( !Has_and_then<const ExpvT3&, ExpvT3()> );
static_assert( Has_and_then<ExpvT3&&, ExpvT3()> ); // uses and_then(F) const &&
static_assert( Has_and_then<const ExpvT3&&, ExpvT3()> );

using Expvi = std::expected<void, int>;
static_assert( Has_or_else<Expvi&, Expvi(int)> );
static_assert( Has_or_else<const Expvi&, Expvi(int)> );
static_assert( Has_or_else<Expvi&&, Expvi(int)> );
static_assert( Has_or_else<const Expvi&&, Expvi(int)> );

static_assert( Has_transform<ExpvT1&, int()> );
static_assert( Has_transform<const ExpvT1&, int()> );
static_assert( Has_transform<ExpvT1&&, int()> );
static_assert( Has_transform<const ExpvT1&&, int()> );

static_assert( !Has_transform<ExpvT2&, int()> );
static_assert( !Has_transform<const ExpvT2&, int()> );
static_assert( !Has_transform<ExpvT2&&, int()> );
static_assert( !Has_transform<const ExpvT2&&, int()> );

static_assert( Has_transform<ExpvT3&, int()> );
static_assert( !Has_transform<const ExpvT3&, int()> );
static_assert( Has_transform<ExpvT3&&, int()> ); // uses transform(F) const &&
static_assert( Has_transform<const ExpvT3&&, int()> );

static_assert( Has_transform_error<Expvi&, int(int)> );
static_assert( Has_transform_error<const Expvi&, int(int)> );
static_assert( Has_transform_error<Expvi&&, int(int)> );
static_assert( Has_transform_error<const Expvi&&, int(int)> );
