aboutsummaryrefslogtreecommitdiff
path: root/src/libutil/box_ptr.hh
diff options
context:
space:
mode:
authorJade Lovelace <lix@jade.fyi>2024-03-09 22:05:50 -0800
committerJade Lovelace <lix@jade.fyi>2024-03-11 01:04:52 -0700
commitaf515baf6e4f1c5aa284e464766438b4c8fadd14 (patch)
tree9055fcfae8dcef1027f4a8ccd9aa5192ae2e5fbc /src/libutil/box_ptr.hh
parent8be7030299699edd3732411d8d97f237a67fbc08 (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.hh121
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)...));
+}
+};