// -*- mode: c++ -*-
#ifndef __IMP_OPTIONAL__85951861
#define __IMP_OPTIONAL__85951861

#include <utility>
#include <exception>
#include <type_traits>

/*
 * An implementation of the upcoming std::optional in C++17
 */

namespace imp {
  struct bad_optional_access : std::exception {};

  struct inplace_tag {};
  static constexpr inplace_tag inplace {};

  struct nullopt_tag {};
  static constexpr nullopt_tag nullopt {};

  template <class T>
  class Optional {
      bool mInit {};
      union {
          char mDummy;
          T mValue;
      };

  public:
      constexpr Optional():
          mDummy() {}

      constexpr Optional(const Optional& other):
          Optional()
      {
          *this = other;
      }

      constexpr Optional(Optional&& other):
          Optional()
      {
          *this = std::move(other);
      }

      constexpr Optional(nullopt_tag):
          Optional() {}

      template <class... Args>
      constexpr Optional(inplace_tag, Args&&... args):
          mInit(true),
          mValue(std::forward<Args>(args)...) {}

      template <class U>
      constexpr Optional(U&& other):
          mInit(true),
          mValue(std::forward<U>(other)) {}

      ~Optional()
      {
          if (mInit) {
              mValue.~T();
          }
      }

      constexpr Optional& operator=(const Optional& other)
      {
          if (!other.mInit) {
              *this = nullopt;
          } else if (mInit) {
              mValue = *other;
          } else {
              ::new (static_cast<void*>(&mValue)) T(other.mValue);
              mInit = true;
          }
          return *this;
      }

      constexpr Optional& operator=(Optional&& other)
      {
          if (!other.mInit) {
              *this = nullopt;
          } else if (mInit) {
              mValue = std::move(*other);
          } else {
              ::new (static_cast<void*>(&mValue)) T(std::move(other.mValue));
              mInit = true;
          }
          return *this;
      }

      Optional& operator=(nullopt_tag)
      {
          if (mInit) {
              mValue.~T();
              mInit = false;
          }
          return *this;
      }

      template <class U>
      Optional& operator=(U&& other)
      {
          if (mInit) {
              mValue = std::forward<U>(other);
          } else {
              ::new (static_cast<void*>(&mValue)) T(std::forward<U>(other));
              mInit = true;
          }
          return *this;
      }

      template <class U>
      Optional& operator=(const Optional<U> &other)
      {
          if (!other.mInit) {
              reset();
          } else if (mInit) {
              mValue = other.mValue;
          } else {
              ::new (static_cast<void*>(&mValue)) T(other.mValue);
              mInit = true;
          }
      }

      template <class U>
      Optional& operator=(Optional<U>&& other)
      {
          if (!other.mInit) {
              reset();
          } else if (mInit) {
              mValue = std::move(other.mValue);
              other.mInit = false;
          } else {
			  ::new (static_cast<void*>(&mValue)) T(std::move(other.mValue));
              other.mInit = false;
              mInit = true;
          }
      }

      constexpr T* operator->()
      { return &mValue; }

      constexpr const T* operator->() const
      { return &mValue; }

      constexpr T& operator*() &
      { return mValue; }

      constexpr const T& operator*() const&
      { return mValue; }

      constexpr T&& operator*() &&
      { return std::move(mValue); }

      constexpr const T& operator*() const&&
      { return std::move(mValue); }

      constexpr explicit operator bool() const
      { return mInit; }

      constexpr bool have_value() const
      { return mInit; }

      constexpr T& value() &
      {
          if (!mInit)
              throw bad_optional_access {};
          return mValue;
      }

      constexpr const T& value() const&
      {
          if (!mInit)
              throw bad_optional_access {};
          return mValue;
      }

      constexpr T&& value() &&
      {
          if (!mInit)
              throw bad_optional_access {};
          return std::move(mValue);
      }

      constexpr const T&& value() const&&
      {
          if (!mInit)
              throw bad_optional_access {};
          return std::move(mValue);
      }

      template <class U>
      constexpr T value_or(U&& default_value) const&
      { return mInit ? mValue : static_cast<T>(std::forward<U>(default_value)); }

      template <class U>
      constexpr T value_or(U&& default_value) &&
      { return mInit ? std::move(mValue) : static_cast<T>(std::forward<U>(default_value)); }

      template <class Functor>
      constexpr auto if_value(Functor functor) -> Optional<std::result_of_t<decltype(functor(value()))>>
      {
          if (have_value()) {
              return { functor(value()) };
          } else {
              return nullopt;
          }
      }

      template <class Functor>
      constexpr auto if_empty(Functor functor) -> Optional<std::result_of_t<decltype(functor(value()))>>
      {
          if (have_value()) {
              return *this;
          } else {
              return functor();
          }
      }

      template <class... Args>
      constexpr void emplace(Args&&... args)
      {
          reset();
          mValue.T(std::forward<Args>(args)...);
          mInit = true;
      }

      constexpr void reset()
      { *this = nullopt; }
  };

  template <class T>
  class Optional<T&> {
      T *mValue {};

  public:
      constexpr Optional() = default;

      constexpr Optional(const Optional& other) = default;

      constexpr Optional(Optional&& other) = default;

      constexpr Optional(nullopt_tag) {}

      constexpr Optional(std::nullptr_t) {}

      constexpr Optional(T* value):
          mValue(value) {}

      template <class U>
      constexpr Optional(inplace_tag, U&& value):
          mValue(std::forward<U*>(&value)) {}

      constexpr Optional& operator=(const Optional& other)
      {
          mValue = other.mValue;
          return *this;
      }

      constexpr Optional& operator=(Optional&& other)
      {
          mValue = std::move(other.mValue);
          return *this;
      }

      Optional& operator=(nullopt_tag)
      {
          mValue = nullptr;
          return *this;
      }

      template <class U>
      Optional& operator=(U&& other)
      {
          mValue = std::forward<U*>(&other);
          return *this;
      }

      template <class U>
      Optional& operator=(const Optional<U> &other)
      {
          mValue = other.mValue;
          return *this;
      }

      template <class U>
      Optional& operator=(Optional<U>&& other)
      {
          mValue = std::move(other.mValue);
          return *this;
      }

      constexpr T* operator->()
      { return mValue; }

      constexpr const T* operator->() const
      { return mValue; }

      constexpr T& operator*() &
      { return *mValue; }

      constexpr const T& operator*() const&
      { return *mValue; }

      constexpr T&& operator*() &&
      { return std::move(*mValue); }

      constexpr const T& operator*() const&&
      { return std::move(*mValue); }

      constexpr bool operator!() const
      { return mValue == nullptr; }

      constexpr explicit operator bool() const
      { return mValue != nullptr; }

      constexpr bool have_value() const
      { return mValue == nullptr; }

      constexpr T& value() &
      {
          if (mValue == nullptr)
              throw bad_optional_access {};
          return *mValue;
      }

      constexpr const T& value() const&
      {
          if (mValue == nullptr)
              throw bad_optional_access {};
          return *mValue;
      }

      constexpr T&& value() &&
      {
          if (mValue == nullptr)
              throw bad_optional_access {};
          return std::move(*mValue);
      }

      constexpr const T&& value() const&&
      {
          if (mValue == nullptr)
              throw bad_optional_access {};
          return std::move(*mValue);
      }

      template <class U>
      constexpr const T& value_or(U&& default_value) const&
      { return mValue != nullptr ? *mValue : static_cast<T>(std::forward<U>(default_value)); }

      template <class U>
      constexpr T& value_or(U&& default_value) &&
      { return mValue != nullptr ? std::move(*mValue) : static_cast<T>(std::forward<U>(default_value)); }

      template <class Functor>
      constexpr auto if_value(Functor functor) -> decltype(functor(**this))
      {
          if (have_value()) {
              return functor(**this);
          } else {
              return nullopt;
          }
      }

      template <class Functor>
      constexpr Optional& if_empty(Functor functor)
      {
          if (have_value()) {
              return *this;
          } else {
              return functor();
          }
      }

      template <class U>
      constexpr void emplace(U&& value)
      {
          mValue = std::forward<U*>(&value);
      }

      constexpr void reset()
      { mValue = nullptr; }
  };

  template <class T, class... Args>
  inline Optional<T> make_optional(Args&&... args)
  {
      return Optional<T>(inplace, std::forward<Args>(args)...);
  };
}

#endif //__IMP_OPTIONAL__85951861
