From 4f46abec9de91b521e9a7d8e16c59683ce46fee3 Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Sat, 29 Oct 2022 11:26:34 -0500 Subject: [PATCH] Reintroduce trivially copyable maybe_t impl This reverts commit 1c92d4c5dbc0b5e3a3a074199a876602129de517 and reintroduces support for trivially copyable `maybe_t` impls but with a GCC version check to disable the optimization for GNU GCC compiler versions 9 and below. GCC 8.3.0 armhf builds seem to have a problem with the trivially copyable `maybe_t` impl that introduces odd heisenbugs that cause the tests to fail. GDB reveals that `maybe_t` function parameters received in the callee differ from what was passed-in by the caller. This behavior appears to be (but has not been confirmed as) a platform-specific compiler bug. Under the same system (32-bit Debian 10 armhf), compiling with clang 7.0.1 does not result in any bugs and causes all the tests to pass while compiling with GCC 10.2 under 32-bit Debian 11 armhf also doesn't run into any problems, so just expand the existing GCC version check that gates support for trivially copyable `maybe_t` impls to encompass both the troublesome GCC 8 version and the untested GCC 9 version. --- src/maybe.h | 57 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/maybe.h b/src/maybe.h index 2edb8a9e5..236c4bf04 100644 --- a/src/maybe.h +++ b/src/maybe.h @@ -7,10 +7,11 @@ #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 magic to make maybe_t (trivially) copyable iff T is. + +// This is an unsafe implementation: it is always trivially copyable. template -struct maybe_impl_t { +struct maybe_impl_trivially_copyable_t { alignas(T) char storage[sizeof(T)]; bool filled = false; @@ -38,11 +39,13 @@ struct maybe_impl_t { return res; } - maybe_impl_t() = default; + maybe_impl_trivially_copyable_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) { + explicit maybe_impl_trivially_copyable_t(T &&v) : filled(true) { + new (storage) T(std::forward(v)); + } + maybe_impl_trivially_copyable_t &operator=(T &&v) { if (filled) { value() = std::move(v); } else { @@ -53,8 +56,8 @@ struct maybe_impl_t { } // 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) { + explicit maybe_impl_trivially_copyable_t(const T &v) : filled(true) { new (storage) T(v); } + maybe_impl_trivially_copyable_t &operator=(const T &v) { if (filled) { value() = v; } else { @@ -63,15 +66,27 @@ struct maybe_impl_t { } return *this; } +}; - // Move construction/assignment from a maybe_impl. - maybe_impl_t(maybe_impl_t &&v) : filled(v.filled) { +// This is an unsafe implementation: it is always copyable. +template +struct maybe_impl_not_trivially_copyable_t : public maybe_impl_trivially_copyable_t { + using base_t = maybe_impl_trivially_copyable_t; + using base_t::maybe_impl_trivially_copyable_t; + using base_t::operator=; + using base_t::filled; + using base_t::reset; + using base_t::storage; + + // Move construction/assignment from another instance. + maybe_impl_not_trivially_copyable_t(maybe_impl_not_trivially_copyable_t &&v) { + filled = v.filled; if (filled) { new (storage) T(std::move(v.value())); } } - maybe_impl_t &operator=(maybe_impl_t &&v) { + maybe_impl_not_trivially_copyable_t &operator=(maybe_impl_not_trivially_copyable_t &&v) { if (!v.filled) { reset(); } else { @@ -80,14 +95,15 @@ struct maybe_impl_t { return *this; } - // Copy construction/assignment from a maybe_impl. - maybe_impl_t(const maybe_impl_t &v) : filled(v.filled) { + // Copy construction/assignment from another instance. + maybe_impl_not_trivially_copyable_t(const maybe_impl_not_trivially_copyable_t &v) : base_t() { + filled = v.filled; if (v.filled) { new (storage) T(v.value()); } } - maybe_impl_t &operator=(const maybe_impl_t &v) { + maybe_impl_not_trivially_copyable_t &operator=(const maybe_impl_not_trivially_copyable_t &v) { if (&v == this) return *this; if (!v.filled) { reset(); @@ -97,7 +113,8 @@ struct maybe_impl_t { return *this; } - ~maybe_impl_t() { reset(); } + maybe_impl_not_trivially_copyable_t() = default; + ~maybe_impl_not_trivially_copyable_t() { reset(); } }; struct copyable_t {}; @@ -130,7 +147,15 @@ inline constexpr none_t none() { return none_t::none; } // This is a value-type class that stores a value of T in aligned storage. template class maybe_t : private maybe_detail::conditionally_copyable_t { - maybe_detail::maybe_impl_t impl_; +#if __GNUG__ && __GNUC__ < 5 + using maybe_impl_t = maybe_detail::maybe_impl_not_trivially_copyable_t; +#else + using maybe_impl_t = + typename std::conditional::value, + maybe_detail::maybe_impl_trivially_copyable_t, + maybe_detail::maybe_impl_not_trivially_copyable_t >::type; +#endif + maybe_impl_t impl_; public: // return whether the receiver contains a value.