1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#pragma once
/// @file

#include <concepts>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <assert.h>

namespace nix {

/** A pointer that's like Rust's Box: forwards comparisons to the inner class and is non-null */
template<typename T>
// FIXME: add custom deleter support
class box_ptr
{
    std::unique_ptr<T> inner;

    template<typename T2>
    friend class box_ptr;

    explicit box_ptr(std::unique_ptr<T> p)
        : inner(std::move(p))
    {
        assert(inner != nullptr);
    }

public:
    using pointer = typename std::unique_ptr<T>::pointer;

    inline typename std::add_lvalue_reference<T>::type operator*() const noexcept
    {
        return *inner.get();
    }

    inline pointer operator->() const noexcept
    {
        return inner.get();
    }

    inline pointer get() const noexcept
    {
        return inner.get();
    }

    /**
     * Create a box_ptr from a nonnull unique_ptr.
     */
    static inline box_ptr<T> unsafeFromNonnull(std::unique_ptr<T> p)
    {
        return box_ptr(std::move(p));
    }

    inline box_ptr<T> & operator=(box_ptr<T> && other) noexcept = default;

    // No copy for you.
    box_ptr<T> & operator=(const box_ptr<T> &) = delete;

    // XXX: we have to set the other's insides to nullptr, since we cannot
    // put a garbage object there, and we don't have any compiler
    // enforcement to not touch moved-from values. sighh.
    box_ptr(box_ptr<T> && other) = default;

    /** Conversion operator */
    template<typename Other>
    // n.b. the requirements here are the same as libstdc++ unique_ptr's checks but with concepts
    requires std::convertible_to<typename box_ptr<Other>::pointer, pointer> &&(!std::is_array_v<Other>)
        box_ptr(box_ptr<Other> && other) noexcept
        : inner(std::move(other.inner))
    {
        other.inner = nullptr;
    }

    box_ptr(box_ptr<T> & other) = delete;

    std::unique_ptr<T> take() &&
    {
        return std::move(inner);
    }
};

template<typename T>
requires std::equality_comparable<T>
bool operator==(box_ptr<T> const & x, box_ptr<T> const & y)
{
    // Although there could be an optimization here that compares x == y, this
    // is unsound for floats with NaN, or anything else that violates
    // reflexivity.
    return *x == *y;
}

template<typename T>
requires std::equality_comparable<T>
bool operator!=(box_ptr<T> const & x, box_ptr<T> const & y)
{
    return *x != *y;
}

#define MAKE_COMPARISON(OP) \
    template<typename T> \
    requires std::totally_ordered<T> \
    bool operator OP(box_ptr<T> const & x, box_ptr<T> const & y) \
    { \
        return *x OP * y; \
    }

MAKE_COMPARISON(<);
MAKE_COMPARISON(>);
MAKE_COMPARISON(>=);
MAKE_COMPARISON(<=);

#undef MAKE_COMPARISON

template<typename T>
requires std::three_way_comparable<T> std::compare_three_way_result_t<T, T>
operator<=>(box_ptr<T> const & x, box_ptr<T> const & y)
{
    return *x <=> *y;
}

template<typename T, typename... Args>
inline box_ptr<T> make_box_ptr(Args &&... args)
{
    return box_ptr<T>::unsafeFromNonnull(std::make_unique<T>(std::forward<Args>(args)...));
}
};