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
This commit is contained in:
jade 2024-03-09 22:05:50 -08:00
parent 8be7030299
commit af515baf6e

121
src/libutil/box_ptr.hh Normal file
View file

@ -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)...));
}
};