// { dg-options "-std=gnu++23" }
// { dg-do run { target c++23 } }
#include <optional>
#if __cpp_lib_optional < 202110L
# error "Feature test macro for monadic optional has wrong value in <optional>"
#endif
#include <testsuite_hooks.h>
constexpr bool
test_and_then()
{
  std::optional<int> o;
  auto r = o.and_then([](int) -> std::optional<short> { throw 1; });
  VERIFY( !r.has_value() );
  static_assert( std::is_same_v<decltype(r), std::optional<short>> );
  o = 111;
  r = o.and_then([](int i) -> std::optional<short> { return {i/10}; });
  VERIFY( *r == 11 );
  return true;
}
static_assert( test_and_then() );
enum { CalledLvalue = 1, CalledConst = 2, PassedLvalue = 4, PassedConst = 8 };
struct F
{
  template<typename This, typename Value>
    static constexpr std::optional<int>
    called_as()
    {
      int res = 0;
      if constexpr (std::is_lvalue_reference_v<This>)
	res |= CalledLvalue;
      if constexpr (std::is_const_v<std::remove_reference_t<This>>)
	res |= CalledConst;
      if constexpr (std::is_lvalue_reference_v<Value>)
	res |= PassedLvalue;
      if constexpr (std::is_const_v<std::remove_reference_t<Value>>)
	res |= PassedConst;
      return {res};
    }
  template<typename T>
    constexpr std::optional<int>
    operator()(T&&) &
    { return called_as<F&, T>(); }
  template<typename T>
    constexpr std::optional<int>
    operator()(T&&) const &
    { return called_as<const F&, T>(); }
  template<typename T>
    constexpr std::optional<int>
    operator()(T&&) &&
    { return called_as<F, T>(); }
  template<typename T>
    constexpr std::optional<int>
    operator()(T&&) const &&
    { return called_as<const F, T>(); }
};
constexpr bool
test_forwarding()
{
  std::optional<long> o = 1;
  F f;
  VERIFY( *o.and_then(f) == (PassedLvalue|CalledLvalue) );
  VERIFY( *o.and_then(std::move(f)) == PassedLvalue );
  VERIFY( *std::move(o).and_then(f) == CalledLvalue );
  VERIFY( *std::move(o).and_then(std::move(f)) == 0 );
  const auto& co = o;
  VERIFY( *co.and_then(f) == (PassedLvalue|PassedConst|CalledLvalue) );
  VERIFY( *co.and_then(std::move(f)) == (PassedLvalue|PassedConst) );
  VERIFY( *std::move(co).and_then(f) == (PassedConst|CalledLvalue) );
  VERIFY( *std::move(co).and_then(std::move(f)) == PassedConst );
  const auto& cf = f;
  VERIFY( *o.and_then(cf) == (PassedLvalue|CalledLvalue|CalledConst) );
  VERIFY( *o.and_then(std::move(cf)) == (PassedLvalue|CalledConst) );
  VERIFY( *std::move(o).and_then(cf) == (CalledLvalue|CalledConst) );
  VERIFY( *std::move(o).and_then(std::move(cf)) == CalledConst );
  VERIFY( *co.and_then(cf) == (PassedLvalue|PassedConst|CalledLvalue|CalledConst) );
  VERIFY( *co.and_then(std::move(cf)) == (PassedLvalue|PassedConst|CalledConst) );
  VERIFY( *std::move(co).and_then(cf) == (PassedConst|CalledLvalue|CalledConst) );
  VERIFY( *std::move(co).and_then(std::move(cf)) == (PassedConst|CalledConst) );
  o = std::nullopt;
  VERIFY( ! o.and_then(f).has_value() );
  VERIFY( ! co.and_then(f).has_value() );
  VERIFY( ! std::move(o).and_then(f).has_value() );
  VERIFY( ! std::move(co).and_then(f).has_value() );
  return true;
}
static_assert( test_forwarding() );
void f(int&) { }
void
test_unconstrained()
{
  // PR libstdc++/102863 - Optional monadic ops should not be constrained
  std::optional<int> x;
  auto answer = x.and_then([](auto& y) { f(y); return std::optional<int>{42}; });
  VERIFY( !answer );
}
int main()
{
  test_and_then();
  test_forwarding();
  test_unconstrained();
}