From af515baf6e4f1c5aa284e464766438b4c8fadd14 Mon Sep 17 00:00:00 2001 From: Jade Lovelace Date: Sat, 9 Mar 2024 22:05:50 -0800 Subject: [PATCH] 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 --- src/libutil/box_ptr.hh | 121 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/libutil/box_ptr.hh 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 +#include +#include +#include +#include + +namespace nix { + +/** A pointer that's like Rust's Box: forwards comparisons to the inner class and is non-null */ +template +// FIXME: add custom deleter support +class box_ptr +{ + std::unique_ptr inner; + + template + friend class box_ptr; + + explicit box_ptr(std::unique_ptr p) + : inner(std::move(p)) + { + assert(inner != nullptr); + } + +public: + using pointer = typename std::unique_ptr::pointer; + + inline typename std::add_lvalue_reference::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 unsafeFromNonnull(std::unique_ptr p) + { + return box_ptr(std::move(p)); + } + + inline box_ptr & operator=(box_ptr && other) noexcept = default; + + // No copy for you. + box_ptr & operator=(const box_ptr &) = 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 && other) = default; + + /** Conversion operator */ + template + // n.b. the requirements here are the same as libstdc++ unique_ptr's checks but with concepts + requires std::convertible_to::pointer, pointer> &&(!std::is_array_v) + box_ptr(box_ptr && other) noexcept + : inner(std::move(other.inner)) + { + other.inner = nullptr; + } + + box_ptr(box_ptr & other) = delete; +}; + +template +requires std::equality_comparable +bool operator==(box_ptr const & x, box_ptr 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 +requires std::equality_comparable +bool operator!=(box_ptr const & x, box_ptr const & y) +{ + return *x != *y; +} + +#define MAKE_COMPARISON(OP) \ + template \ + requires std::totally_ordered \ + bool operator OP(box_ptr const & x, box_ptr const & y) \ + { \ + return *x OP * y; \ + } + +MAKE_COMPARISON(<); +MAKE_COMPARISON(>); +MAKE_COMPARISON(>=); +MAKE_COMPARISON(<=); + +#undef MAKE_COMPARISON + +template +requires std::three_way_comparable std::compare_three_way_result_t +operator<=>(box_ptr const & x, box_ptr const & y) +{ + return *x <=> *y; +} + +template +inline box_ptr make_box_ptr(Args &&... args) +{ + return box_ptr::unsafeFromNonnull(std::make_unique(std::forward(args)...)); +} +};