mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-14 14:03:58 +00:00
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:
parent
0bde698f81
commit
a58662dd46
2 changed files with 145 additions and 55 deletions
|
@ -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() {
|
||||||
|
|
184
src/maybe.h
184
src/maybe.h
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue