unique_interface_ptr.h

// unique_interface_ptr.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2021

@import "IObject.h"
#include <memory>

/*
todo:
  -   std::unique_ptr<T,D> seems to allow the pointer type to be provided by the D.
      Does that mean we can actually use std::unique_ptr<> to implement unique_interface_ptr<>?

  -   Do we need reflection of 
          gcroot<T*>
          gcroot<ptr<I>>
          std::optional<T>
          std::variant
          std::array
          std::vector, std::deque, std::list, std::set, std::map
          std::unique_ptr, std::shared_ptr
          unique_interface_ptr<I>
          shared_interface_ptr

  -   Unit test unique_interface_ptr<I>

  -   Illustrate how an application programmer might use a unique_interface_ptr<I>

  -   Does swap exist on ptr<T>?

  -   C++ has a named requirement called NullablePointer.
              - EqualityComparable      (must have == and this is an equivalence relation)
              - DefaultConstructible    (a public default constructor must be defined)
              - CopyConstructible       (T is MoveConstrictible AND  T u=v,  T(v) must be valid and have specified effects)
    
      Is a ptr<T> a NullablePointer?
*/

namespace ceda
{
template <class T>
class unique_interface_ptr 
{
public:
    using pointer_type = ptr<T>;
    using interface_type = T;

    constexpr unique_interface_ptr() noexcept {}
    constexpr unique_interface_ptr(nullptr_t) noexcept {}

    unique_interface_ptr& operator=(nullptr_t) noexcept 
    {
        reset();
        return *this;
    }

    explicit unique_interface_ptr(pointer_type p) noexcept : ptr_(p) {}

    unique_interface_ptr(unique_interface_ptr&& right) noexcept : ptr_(right.release()) {}

    template <class T2, std::enable_if_t<std::is_convertible<typename unique_interface_ptr<T2>::pointer_type,pointer_type>::value, int> = 0>
    unique_interface_ptr(unique_interface_ptr<T2> right) noexcept : ptr_(right.release()) {}

    template <class T2, std::enable_if_t<std::is_convertible<typename unique_interface_ptr<T2>::pointer_type,pointer_type>::value, int> = 0>
    unique_interface_ptr& operator=(unique_interface_ptr<T2>&& right) noexcept 
    {
        reset(right.release());
        return *this;
    }

    unique_interface_ptr& operator=(unique_interface_ptr&& right) noexcept 
    {
        if (this != std::addressof(right)) 
        {
            reset(right.release());
        }
        return *this;
    }

    void swap(unique_interface_ptr& right) noexcept 
    {
        using std::swap;
        swap(ptr_, right.ptr_);
    }

    ~unique_interface_ptr() noexcept 
    {
        if (ptr_) 
        {
            ptr_->Destroy();
        }
    }

    [[nodiscard]] std::add_lvalue_reference_t<T> operator*() const noexcept 
    {
        return *ptr_;
    }

    [[nodiscard]] pointer_type operator->() const noexcept 
    {
        return ptr_;
    }

    [[nodiscard]] pointer_type get() const noexcept 
    {
        return ptr_;
    }

    explicit operator bool() const noexcept 
    {
        return static_cast<bool>(ptr_);
    }

    pointer_type release() noexcept 
    {
        return std::exchange(ptr_, nullptr);
    }

    void reset(pointer_type p = nullptr) noexcept 
    {
        pointer_type prev = std::exchange(ptr_, p);
        if (prev) 
        {
            prev->Destroy();
        }
    }

    unique_interface_ptr(const unique_interface_ptr&) = delete;
    unique_interface_ptr& operator=(const unique_interface_ptr&) = delete;

private:
    template <class>
    friend class unique_interface_ptr;

    pointer_type ptr_;
};


template <class T, class X, class... _Types>
[[nodiscard]] unique_interface_ptr<T> make_unique_interface_ptr(_Types&&... _Args) 
{ 
    return unique_interface_ptr<T>(new X(std::forward<_Types>(_Args)...));
}

template <class T>
void swap(unique_interface_ptr<T>& left, unique_interface_ptr<T>& right) noexcept 
{
    left.swap(right);
}

template <class _Ty1, class _Ty2>
[[nodiscard]] bool operator==(const unique_interface_ptr<_Ty1>& left, const unique_interface_ptr<_Ty2>& right) 
{
    return left.get() == right.get();
}

template <class _Ty1, class _Ty2>
[[nodiscard]] bool operator!=(const unique_interface_ptr<_Ty1>& left, const unique_interface_ptr<_Ty2>& right) 
{
    return !(left == right);
}

template <class _Ty1, class _Ty2>
[[nodiscard]] bool operator<(const unique_interface_ptr<_Ty1>& left, const unique_interface_ptr<_Ty2>& right) 
{
    using _Ptr1   = typename unique_interface_ptr<_Ty1>::pointer_type;
    using _Ptr2   = typename unique_interface_ptr<_Ty2>::pointer_type;
    using _Common = std::common_type_t<_Ptr1, _Ptr2>;
    return std::less<_Common>{}(left.get(), right.get());
}

template <class _Ty1, class _Ty2>
[[nodiscard]] bool operator>=(const unique_interface_ptr<_Ty1>& left, const unique_interface_ptr<_Ty2>& right) 
{
    return !(left < right);
}

template <class _Ty1, class _Ty2>
[[nodiscard]] bool operator>(const unique_interface_ptr<_Ty1>& left, const unique_interface_ptr<_Ty2>& right) 
{
    return right < left;
}

template <class _Ty1, class _Ty2>
[[nodiscard]] bool operator<=(const unique_interface_ptr<_Ty1>& left, const unique_interface_ptr<_Ty2>& right) 
{
    return !(right < left);
}

template <class T>
[[nodiscard]] bool operator==(const unique_interface_ptr<T>& left, nullptr_t) noexcept 
{
    return !left;
}

template <class T>
[[nodiscard]] bool operator==(nullptr_t, const unique_interface_ptr<T>& right) noexcept 
{
    return !right;
}

template <class T>
[[nodiscard]] bool operator!=(const unique_interface_ptr<T>& left, nullptr_t right) noexcept 
{
    return !(left == right);
}

template <class T>
[[nodiscard]] bool operator!=(nullptr_t left, const unique_interface_ptr<T>& right) noexcept 
{
    return !(left == right);
}

template <class T>
[[nodiscard]] bool operator<(const unique_interface_ptr<T>& left, nullptr_t right) 
{
    using _Ptr = typename unique_interface_ptr<T>::pointer_type;
    return std::less<_Ptr>{}(left.get(), right);
}

template <class T>
[[nodiscard]] bool operator<(nullptr_t left, const unique_interface_ptr<T>& right) 
{
    using _Ptr = typename unique_interface_ptr<T>::pointer_type;
    return std::less<_Ptr>{}(left, right.get());
}

template <class T>
[[nodiscard]] bool operator>=(const unique_interface_ptr<T>& left, nullptr_t right) 
{
    return !(left < right);
}

template <class T>
[[nodiscard]] bool operator>=(nullptr_t left, const unique_interface_ptr<T>& right) 
{
    return !(left < right);
}

template <class T>
[[nodiscard]] bool operator>(const unique_interface_ptr<T>& left, nullptr_t right) 
{
    return right < left;
}

template <class T>
[[nodiscard]] bool operator>(nullptr_t left, const unique_interface_ptr<T>& right) 
{
    return right < left;
}

template <class T>
[[nodiscard]] bool operator<=(const unique_interface_ptr<T>& left, nullptr_t right) 
{
    return !(right < left);
}

template <class T>
[[nodiscard]] bool operator<=(nullptr_t left, const unique_interface_ptr<T>& right) 
{
    return !(right < left);
}

template <class Elem, class Traits, class T>
std::basic_ostream<Elem, Traits>& operator<<(std::basic_ostream<Elem, Traits>& os, const unique_interface_ptr<T>& p) 
{
    os << p.get();
    return os;
}

} // namespace ceda