// { dg-do run { target c++14 } }

// Copyright (C) 2013-2020 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#include <experimental/optional>
#include <testsuite_hooks.h>

#include <tuple>

using std::experimental::bad_optional_access;
static_assert( std::is_default_constructible<bad_optional_access>::value, "" );

struct trivially_destructible
{
  trivially_destructible() = delete;
  trivially_destructible(trivially_destructible const&) = delete;
  trivially_destructible& operator=(trivially_destructible const&) = delete;
  trivially_destructible(trivially_destructible&&) = delete;
  trivially_destructible& operator=(trivially_destructible&&) = delete;
  ~trivially_destructible() noexcept = default;
};

static_assert( std::is_trivially_destructible<trivially_destructible>(), "" );

struct no_default_constructor
{
  no_default_constructor() = delete;
};

struct no_copy_constructor
{
  no_copy_constructor() = default;
  no_copy_constructor(no_copy_constructor const&) = delete;
  no_copy_constructor& operator=(no_copy_constructor const&) = default;
  no_copy_constructor(no_copy_constructor&&) = default;
  no_copy_constructor& operator=(no_copy_constructor&&) = default;
};

struct no_copy_assignment
{
  no_copy_assignment() = default;
  no_copy_assignment(no_copy_assignment const&) = default;
  no_copy_assignment(no_copy_assignment&&) = default;
  no_copy_assignment& operator=(no_copy_assignment&&) = default;
};

struct no_move_constructor
{
  no_move_constructor() = default;
  no_move_constructor(no_move_constructor const&) = default;
  no_move_constructor& operator=(no_move_constructor const&) = default;
  no_move_constructor(no_move_constructor&&) = delete;
  no_move_constructor& operator=(no_move_constructor&&) = default;
};

struct no_move_assignment
{
  no_move_assignment() = default;
  no_move_assignment(no_move_assignment const&) = default;
  no_move_assignment& operator=(no_move_assignment const&) = default;
  no_move_assignment(no_move_assignment&&) = default;
  no_move_assignment& operator=(no_move_assignment&&) = delete;
};

struct no_copy : no_copy_constructor, no_copy_assignment { };
struct no_move : no_move_constructor, no_move_assignment { };

// Laxest possible model of a value type for optional
struct only_destructible
{
  only_destructible(only_destructible&&) = delete;
};

int main()
{
  {
    static_assert( std::is_trivially_destructible<std::experimental::optional<trivially_destructible>>(), "" );
  }

  {
    using T = no_default_constructor;
    using O = std::experimental::optional<T>;
    static_assert( std::is_same<O::value_type, T>(), "" );
    static_assert( std::is_default_constructible<O>(), "" );
    { O o; }
    static_assert( std::is_copy_constructible<O>(), "" );
    { O o; auto copy = o; }
    static_assert( std::is_copy_assignable<O>(), "" );
    { O o, p; p = o; }
    static_assert( std::is_move_constructible<O>(), "" );
    { O o; auto moved_to = std::move(o); }
    static_assert( std::is_move_assignable<O>(), "" );
    { O o, p; p = std::move(o); }
  }

  {
    using T = no_copy_constructor;
    using O = std::experimental::optional<T>;
    static_assert( std::is_same<O::value_type, T>(), "" );
    static_assert( std::is_default_constructible<O>(), "" );
    { O o; }
    static_assert( !std::is_copy_constructible<O>(), "" );
    static_assert( !std::is_copy_assignable<O>(), "" );
    static_assert( std::is_move_constructible<O>(), "" );
    { O o; auto moved_to = std::move(o); }
    static_assert( std::is_move_assignable<O>(), "" );
    { O o, p; p = std::move(o); }
  }

  {
    using T = no_copy_assignment;
    using O = std::experimental::optional<T>;
    static_assert( std::is_default_constructible<O>(), "" );
    { O o; }
    static_assert( std::is_copy_constructible<O>(), "" );
    { O o; auto copy = o; }
    static_assert( !std::is_copy_assignable<O>(), "" );
    static_assert( std::is_move_constructible<O>(), "" );
    { O o; auto moved_to = std::move(o); }
    static_assert( std::is_move_assignable<O>(), "" );
    { O o, p; p = std::move(o); }
  }

  {
    using T = no_copy;
    using O = std::experimental::optional<T>;
    static_assert( std::is_same<O::value_type, T>(), "" );
    static_assert( std::is_default_constructible<O>(), "" );
    { O o; }
    static_assert( !std::is_copy_constructible<O>(), "" );
    static_assert( !std::is_copy_assignable<O>(), "" );
    static_assert( std::is_move_constructible<O>(), "" );
    { O o; auto moved_to = std::move(o); }
    static_assert( std::is_move_assignable<O>(), "" );
    { O o, p; p = std::move(o); }
  }

  {
    using T = no_move_constructor;
    using O = std::experimental::optional<T>;
    static_assert( std::is_same<O::value_type, T>(), "" );
    static_assert( std::is_default_constructible<O>(), "" );
    { O o; }
    static_assert( std::is_copy_constructible<O>(), "" );
    { O o; auto copy = o; }
    static_assert( std::is_copy_assignable<O>(), "" );
    /*
     * T should be move constructible due to [12.8/11], which is a new rule in C++1y
     * not yet implemented by GCC. Because there is already a special exception in C++11
     * for the generation of the special members that GCC implements (at least some of the
     * time), this does not affect the std::experimental::optional implementation however. So the assertion
     * for T should be changed (or removed altogether) when the time comes, but the rest
     * should however remain correct and unchanged.
     */
    static_assert( !std::is_move_constructible<T>(), "" );
    static_assert( std::is_move_constructible<O>(), "" );
    { O o; auto moved_to = std::move(o); }
    static_assert( std::is_move_assignable<O>(), "" );
    { O o, p; p = std::move(o); }
  }

  {
    using T = no_move_assignment;
    using O = std::experimental::optional<T>;
    static_assert( std::is_same<O::value_type, T>(), "" );
    static_assert( std::is_default_constructible<O>(), "" );
    { O o; }
    static_assert( std::is_copy_constructible<O>(), "" );
    { O o; auto copy = o; }
    static_assert( std::is_copy_assignable<O>(), "" );
    { O o, p; p = o; }
    static_assert( std::is_move_constructible<O>(), "" );
    { O o; auto moved_to = std::move(o); }
    /*
     * Paragraph 23 of same leads to a similar situation but with respect to move
     * assignment.
     */
    static_assert( !std::is_move_assignable<T>(), "" );
    static_assert( std::is_move_assignable<O>(), "" );
    { O o, p; p = std::move(o); }
  }

  {
    using T = no_move;
    using O = std::experimental::optional<T>;
    static_assert( std::is_same<O::value_type, T>(), "" );
    static_assert( std::is_default_constructible<O>(), "" );
    { O o; }
    static_assert( std::is_copy_constructible<O>(), "" );
    { O o; auto copy = o; }
    static_assert( std::is_copy_assignable<O>(), "" );
    { O o, p; p = o; }
    static_assert( std::is_move_constructible<O>(), "" );
    { O o; auto moved_to = std::move(o); }
    static_assert( std::is_move_assignable<O>(), "" );
    { O o, p; p = std::move(o); }
  }

  {
    using T = only_destructible;
    using O = std::experimental::optional<T>;
    static_assert( std::is_same<O::value_type, T>(), "" );
    static_assert( std::is_default_constructible<O>(), "" );
    { O o; }
    static_assert( !std::is_copy_constructible<O>(), "" );
    static_assert( !std::is_copy_assignable<O>(), "" );
    static_assert( !std::is_move_constructible<O>(), "" );
    static_assert( !std::is_move_assignable<O>(), "" );
  }

  {
    /*
     * Should not complain about 'invalid' specializations as long as
     * they're not instantiated.
     */
    using A = std::experimental::optional<int&>;
    using B = std::experimental::optional<int&&>;
    using C1 = std::experimental::optional<std::experimental::in_place_t>;
    using C2 = std::experimental::optional<std::experimental::in_place_t const>;
    using C3 = std::experimental::optional<std::experimental::in_place_t volatile>;
    using C4 = std::experimental::optional<std::experimental::in_place_t const volatile>;
    using D1 = std::experimental::optional<std::experimental::nullopt_t>;
    using D2 = std::experimental::optional<std::experimental::nullopt_t const>;
    using D3 = std::experimental::optional<std::experimental::nullopt_t volatile>;
    using D4 = std::experimental::optional<std::experimental::nullopt_t const volatile>;

    using X = std::tuple<A, B, C1, C2, C3, C4, D1, D2, D3, D4>;
  }

  {
    std::experimental::optional<const int> o { 42 };
    static_assert( std::is_same<decltype(o)::value_type, const int>(), "" );
    VERIFY( o );
    VERIFY( *o == 42 );
  }

  {
    constexpr std::experimental::optional<const int> o { 33 };
    static_assert( std::is_same<decltype(o)::value_type, const int>(), "" );
    static_assert( o, "" );
    static_assert( *o == 33, "" );
  }
}
