diff options
author | Jade Lovelace <lix@jade.fyi> | 2024-03-09 22:05:50 -0800 |
---|---|---|
committer | Jade Lovelace <lix@jade.fyi> | 2024-03-11 01:04:52 -0700 |
commit | af515baf6e4f1c5aa284e464766438b4c8fadd14 (patch) | |
tree | 9055fcfae8dcef1027f4a8ccd9aa5192ae2e5fbc /src/libutil/box_ptr.hh | |
parent | 8be7030299699edd3732411d8d97f237a67fbc08 (diff) |
Add box_ptr: nonnull unique_ptr with value semantics
This solves the problem of collections of boxed subclasses with virtual
dispatch, which should still be treated as values, since the
indirection is only there due to the virtual dispatch.
Change-Id: I368daedd3f31298e99c6e56a15606337a55494c6
Diffstat (limited to 'src/libutil/box_ptr.hh')
-rw-r--r-- | src/libutil/box_ptr.hh | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/src/libutil/box_ptr.hh b/src/libutil/box_ptr.hh new file mode 100644 index 000000000..f7e4fd509 --- /dev/null +++ b/src/libutil/box_ptr.hh @@ -0,0 +1,121 @@ +#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; +}; + +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)...)); +} +}; |