Make maybe_t conditionally copyable

This allows it to be used with both e.g. unique_ptr and std::vector.
This commit is contained in:
ridiculousfish 2019-03-17 12:47:24 -07:00
parent 0bde698f81
commit a58662dd46
2 changed files with 145 additions and 55 deletions

View file

@ -5015,6 +5015,22 @@ void test_maybe() {
std::string res = acquire_test.acquire(); std::string res = acquire_test.acquire();
do_test(!acquire_test); do_test(!acquire_test);
do_test(res == "def"); do_test(res == "def");
// maybe_t<T> should be copyable iff T is copyable.
using copyable = std::shared_ptr<int>;
using noncopyable = std::unique_ptr<int>;
do_test(std::is_copy_assignable<maybe_t<copyable>>::value == true);
do_test(std::is_copy_constructible<maybe_t<copyable>>::value == true);
do_test(std::is_copy_assignable<maybe_t<noncopyable>>::value == false);
do_test(std::is_copy_constructible<maybe_t<noncopyable>>::value == false);
maybe_t<std::string> c1{"abc"};
maybe_t<std::string> c2 = c1;
do_test(c1.value() == "abc");
do_test(c2.value() == "abc");
c2 = c1;
do_test(c1.value() == "abc");
do_test(c2.value() == "abc");
} }
void test_layout_cache() { void test_layout_cache() {

View file

@ -2,6 +2,116 @@
#define FISH_MAYBE_H #define FISH_MAYBE_H
#include <cassert> #include <cassert>
#include <type_traits>
namespace maybe_detail {
// Template magic to make maybe_t<T> copyable iff T is copyable.
// maybe_impl_t is the "too aggressive" implementation: it is always copyable.
template <typename T>
struct maybe_impl_t {
alignas(T) char storage[sizeof(T)];
bool filled = false;
T &value() {
assert(filled && "maybe_t does not have a value");
return *reinterpret_cast<T *>(storage);
}
const T &value() const {
assert(filled && "maybe_t does not have a value");
return *reinterpret_cast<const T *>(storage);
}
void reset() {
if (this->filled) {
value().~T();
this->filled = false;
}
}
T acquire() {
assert(filled && "maybe_t does not have a value");
T res = std::move(value());
reset();
return res;
}
maybe_impl_t() = default;
// Move construction/assignment from a T.
explicit maybe_impl_t(T &&v) : filled(true) { new (storage) T(std::forward<T>(v)); }
maybe_impl_t &operator=(T &&v) {
if (filled) {
value() = std::move(v);
} else {
new (storage) T(std::move(v));
filled = true;
}
return *this;
}
// Copy construction/assignment from a T.
explicit maybe_impl_t(const T &v) : filled(true) { new (storage) T(v); }
maybe_impl_t &operator=(const T &v) {
if (filled) {
value() = v;
} else {
new (storage) T(v);
filled = true;
}
return *this;
}
// Move construction/assignment from a maybe_impl.
maybe_impl_t(maybe_impl_t &&v) : filled(v.filled) {
if (filled) {
new (storage) T(std::move(v.value()));
}
}
maybe_impl_t &operator=(maybe_impl_t &&v) {
if (!v.filled) {
reset();
} else {
*this = std::move(v.value());
}
return *this;
}
// Copy construction/assignment from a maybe_impl.
maybe_impl_t(const maybe_impl_t &v) : filled(v.filled) {
if (v.filled) {
new (storage) T(v.value());
}
}
maybe_impl_t &operator=(const maybe_impl_t &v) {
if (&v == this) return *this;
if (!v.filled) {
reset();
} else {
*this = v.value();
}
return *this;
}
~maybe_impl_t() { reset(); }
};
struct copyable_t {};
struct noncopyable_t {
noncopyable_t() = default;
noncopyable_t(noncopyable_t &&) = default;
noncopyable_t &operator=(noncopyable_t &&) = default;
noncopyable_t(const noncopyable_t &) = delete;
noncopyable_t &operator=(const noncopyable_t &) = delete;
};
// conditionally_copyable_t is copyable iff T is copyable.
// This enables conditionally copyable wrapper types by inheriting from it.
template <typename T>
using conditionally_copyable_t = typename std::conditional<std::is_copy_constructible<T>::value,
copyable_t, noncopyable_t>::type;
}; // namespace maybe_detail
// A none_t is a helper type used to implicitly initialize maybe_t. // A none_t is a helper type used to implicitly initialize maybe_t.
// Example usage: // Example usage:
@ -15,16 +125,15 @@ inline constexpr none_t none() { return none_t::none; }
// Support for a maybe, also known as Optional. // Support for a maybe, also known as Optional.
// This is a value-type class that stores a value of T in aligned storage. // This is a value-type class that stores a value of T in aligned storage.
template <typename T> template <typename T>
class maybe_t { class maybe_t : private maybe_detail::conditionally_copyable_t<T> {
alignas(T) char storage[sizeof(T)]; maybe_detail::maybe_impl_t<T> impl_;
bool filled = false;
public: public:
// return whether the receiver contains a value. // return whether the receiver contains a value.
bool has_value() const { return filled; } bool has_value() const { return impl_.filled; }
// bool conversion indicates whether the receiver contains a value. // bool conversion indicates whether the receiver contains a value.
explicit operator bool() const { return filled; } explicit operator bool() const { return impl_.filled; }
// The default constructor constructs a maybe with no value. // The default constructor constructs a maybe with no value.
maybe_t() = default; maybe_t() = default;
@ -33,79 +142,46 @@ class maybe_t {
/* implicit */ maybe_t(none_t n) { (void)n; } /* implicit */ maybe_t(none_t n) { (void)n; }
// Construct a maybe_t from a value T. // Construct a maybe_t from a value T.
/* implicit */ maybe_t(T &&v) : filled(true) { new (storage) T(std::forward<T>(v)); } /* implicit */ maybe_t(T &&v) : impl_(std::move(v)) {}
// Construct a maybe_t from a value T. // Construct a maybe_t from a value T.
/* implicit */ maybe_t(const T &v) : filled(true) { new (storage) T(v); } /* implicit */ maybe_t(const T &v) : impl_(v) {}
// Copy constructor. // Copy constructor.
maybe_t(const maybe_t &v) : filled(v.filled) { maybe_t(const maybe_t &v) = default;
if (v.filled) {
new (storage) T(v.value());
}
}
// Move constructor. // Move constructor.
/* implicit */ maybe_t(maybe_t &&v) : filled(v.filled) { /* implicit */ maybe_t(maybe_t &&v) = default;
if (v.filled) {
new (storage) T(std::move(v.value()));
}
}
// Construct a value in-place. // Construct a value in-place.
template <class... Args> template <class... Args>
void emplace(Args &&... args) { void emplace(Args &&... args) {
reset(); reset();
filled = true; impl_.filled = true;
new (storage) T(std::forward<Args>(args)...); new (impl_.storage) T(std::forward<Args>(args)...);
} }
// Access the value. // Access the value.
T &value() { T &value() { return impl_.value(); }
assert(filled && "maybe_t does not have a value");
return *reinterpret_cast<T *>(storage);
}
const T &value() const { const T &value() const { return impl_.value(); }
assert(filled && "maybe_t does not have a value");
return *reinterpret_cast<const T *>(storage);
}
// Transfer the value to the caller. // Transfer the value to the caller.
T acquire() { T acquire() { return impl_.acquire(); }
assert(filled && "maybe_t does not have a value");
T res = std::move(value());
reset();
return res;
}
// Clear the value. // Clear the value.
void reset() { void reset() { impl_.reset(); }
if (filled) {
value().~T();
filled = false;
}
}
// Assign a new value. // Assign a new value.
maybe_t &operator=(T &&v) { maybe_t &operator=(T &&v) {
if (filled) { impl_ = std::move(v);
value() = std::move(v);
} else {
new (storage) T(std::move(v));
filled = true;
}
return *this; return *this;
} }
maybe_t &operator=(maybe_t &&v) { // Note that defaulting these allows these to be conditionally deleted via
if (!v) { // conditionally_copyable_t().
reset(); maybe_t &operator=(const maybe_t &v) = default;
} else { maybe_t &operator=(maybe_t &&v) = default;
*this = std::move(*v);
}
return *this;
}
// Dereference support. // Dereference support.
const T *operator->() const { return &value(); } const T *operator->() const { return &value(); }
@ -127,8 +203,6 @@ class maybe_t {
} }
bool operator!=(const maybe_t &rhs) const { return !(*this == rhs); } bool operator!=(const maybe_t &rhs) const { return !(*this == rhs); }
~maybe_t() { reset(); }
}; };
#endif #endif