From a58662dd46a6903e20ee0fc9e2cab33b1ada8f19 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 17 Mar 2019 12:47:24 -0700 Subject: [PATCH] Make maybe_t conditionally copyable This allows it to be used with both e.g. unique_ptr and std::vector. --- src/fish_tests.cpp | 16 ++++ src/maybe.h | 184 +++++++++++++++++++++++++++++++-------------- 2 files changed, 145 insertions(+), 55 deletions(-) diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 52692f0bc..364701fe0 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -5015,6 +5015,22 @@ void test_maybe() { std::string res = acquire_test.acquire(); do_test(!acquire_test); do_test(res == "def"); + + // maybe_t should be copyable iff T is copyable. + using copyable = std::shared_ptr; + using noncopyable = std::unique_ptr; + do_test(std::is_copy_assignable>::value == true); + do_test(std::is_copy_constructible>::value == true); + do_test(std::is_copy_assignable>::value == false); + do_test(std::is_copy_constructible>::value == false); + + maybe_t c1{"abc"}; + maybe_t 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() { diff --git a/src/maybe.h b/src/maybe.h index 7f3f96cde..a3e5cebda 100644 --- a/src/maybe.h +++ b/src/maybe.h @@ -2,6 +2,116 @@ #define FISH_MAYBE_H #include +#include + +namespace maybe_detail { +// Template magic to make maybe_t copyable iff T is copyable. +// maybe_impl_t is the "too aggressive" implementation: it is always copyable. +template +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(storage); + } + + const T &value() const { + assert(filled && "maybe_t does not have a value"); + return *reinterpret_cast(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(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 +using conditionally_copyable_t = typename std::conditional::value, + copyable_t, noncopyable_t>::type; + +}; // namespace maybe_detail // A none_t is a helper type used to implicitly initialize maybe_t. // Example usage: @@ -15,16 +125,15 @@ inline constexpr none_t none() { return none_t::none; } // Support for a maybe, also known as Optional. // This is a value-type class that stores a value of T in aligned storage. template -class maybe_t { - alignas(T) char storage[sizeof(T)]; - bool filled = false; +class maybe_t : private maybe_detail::conditionally_copyable_t { + maybe_detail::maybe_impl_t impl_; public: // 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. - explicit operator bool() const { return filled; } + explicit operator bool() const { return impl_.filled; } // The default constructor constructs a maybe with no value. maybe_t() = default; @@ -33,79 +142,46 @@ class maybe_t { /* implicit */ maybe_t(none_t n) { (void)n; } // Construct a maybe_t from a value T. - /* implicit */ maybe_t(T &&v) : filled(true) { new (storage) T(std::forward(v)); } + /* implicit */ maybe_t(T &&v) : impl_(std::move(v)) {} // 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. - maybe_t(const maybe_t &v) : filled(v.filled) { - if (v.filled) { - new (storage) T(v.value()); - } - } + maybe_t(const maybe_t &v) = default; // Move constructor. - /* implicit */ maybe_t(maybe_t &&v) : filled(v.filled) { - if (v.filled) { - new (storage) T(std::move(v.value())); - } - } + /* implicit */ maybe_t(maybe_t &&v) = default; // Construct a value in-place. template void emplace(Args &&... args) { reset(); - filled = true; - new (storage) T(std::forward(args)...); + impl_.filled = true; + new (impl_.storage) T(std::forward(args)...); } // Access the value. - T &value() { - assert(filled && "maybe_t does not have a value"); - return *reinterpret_cast(storage); - } + T &value() { return impl_.value(); } - const T &value() const { - assert(filled && "maybe_t does not have a value"); - return *reinterpret_cast(storage); - } + const T &value() const { return impl_.value(); } // Transfer the value to the caller. - T acquire() { - assert(filled && "maybe_t does not have a value"); - T res = std::move(value()); - reset(); - return res; - } + T acquire() { return impl_.acquire(); } // Clear the value. - void reset() { - if (filled) { - value().~T(); - filled = false; - } - } + void reset() { impl_.reset(); } // Assign a new value. maybe_t &operator=(T &&v) { - if (filled) { - value() = std::move(v); - } else { - new (storage) T(std::move(v)); - filled = true; - } + impl_ = std::move(v); return *this; } - maybe_t &operator=(maybe_t &&v) { - if (!v) { - reset(); - } else { - *this = std::move(*v); - } - return *this; - } + // Note that defaulting these allows these to be conditionally deleted via + // conditionally_copyable_t(). + maybe_t &operator=(const maybe_t &v) = default; + maybe_t &operator=(maybe_t &&v) = default; // Dereference support. const T *operator->() const { return &value(); } @@ -127,8 +203,6 @@ class maybe_t { } bool operator!=(const maybe_t &rhs) const { return !(*this == rhs); } - - ~maybe_t() { reset(); } }; #endif