diff --git a/optional.hpp b/optional.hpp index 74b8d5c..6d107bf 100644 --- a/optional.hpp +++ b/optional.hpp @@ -36,10 +36,15 @@ #endif #if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions #define TL_OPTIONAL_NO_CONSTRR + +//GCC < 5 doesn't support some standard C++11 type traits #define IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) std::has_trivial_copy_constructor::value #define IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign::value + +// This one will be different for GCC 5.7 if it's ever supported #define IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value #else #define IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) std::is_trivially_copy_constructible::value @@ -51,6 +56,7 @@ #define TL_OPTIONAL_CXX14 #endif +// constexpr implies const in C++11, not C++14 #if (__cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || \ defined(TL_OPTIONAL_GCC49)) && \ !defined(TL_OPTIONAL_GCC54) @@ -274,6 +280,8 @@ struct is_nothrow_swappable }; #endif +// The storage base manages the actual storage, and correctly propagates trivial destruction from T +// This case is for when T is trivially destructible template ::value> struct optional_storage_base { TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept @@ -299,6 +307,7 @@ struct optional_storage_base { bool m_has_value; }; +// This case is for when T is not trivially destructible template struct optional_storage_base { TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept : m_dummy(), m_has_value(false) {} @@ -307,6 +316,8 @@ template struct optional_storage_base { TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) : m_value(std::forward(u)...), m_has_value(true) {} + // No destructor, so this class is trivially destructible + struct dummy {}; union { dummy m_dummy; @@ -316,6 +327,8 @@ template struct optional_storage_base { bool m_has_value = false; }; + +// This base class provides some handy member functions which can be used in further derived classes template struct optional_operations_base : optional_storage_base { using optional_storage_base::optional_storage_base; @@ -354,11 +367,14 @@ template struct optional_operations_base : optional_storage_base { #endif }; - template +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T is trivially copy constructible +template struct optional_copy_base : optional_operations_base { using optional_operations_base::optional_operations_base; }; +// This specialization is for when T is not trivially copy constructible template struct optional_copy_base : optional_operations_base { using optional_operations_base::optional_operations_base; @@ -377,7 +393,8 @@ struct optional_copy_base : optional_operations_base { optional_copy_base &operator=(optional_copy_base &&rhs) = default; }; - +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it doesn't implement an analogue to std::is_trivially_move_constructible. We have to make do with a non-trivial move constructor even if T is trivially move constructible #ifndef TL_OPTIONAL_GCC49 template ::value> struct optional_move_base : optional_copy_base { @@ -405,6 +422,7 @@ template struct optional_move_base : optional_copy_base { optional_move_base &operator=(optional_move_base &&rhs) = default; }; +// This class manages conditionally having a trivial copy assignment operator template struct optional_copy_assign_base : optional_move_base { using optional_move_base::optional_move_base; @@ -426,6 +444,8 @@ struct optional_copy_assign_base : optional_move_base { }; +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it doesn't implement an analogue to std::is_trivially_move_assignable. We have to make do with a non-trivial move assignment operator even if T is trivially move assignable #ifndef TL_OPTIONAL_GCC49 template ::value && std::is_trivially_move_constructible::value && std::is_trivially_move_assignable::value> struct optional_move_assign_base : optional_copy_assign_base { @@ -455,6 +475,8 @@ struct optional_move_assign_base : optional_copy_assign_base { operator=(optional_move_assign_base &&rhs) = default; }; + +// optional_delete_ctor_base will conditionally delete copy and move constructors depending on whether T is copy/move constructible template ::value, bool EnableMove = std::is_move_constructible::value> struct optional_delete_ctor_base { @@ -497,6 +519,7 @@ template struct optional_delete_ctor_base { operator=(optional_delete_ctor_base &&) noexcept = default; }; +// optional_delete_assign_base will conditionally delete copy and move constructors depending on whether T is copy/move constructible + assignable template ::value && std::is_copy_assignable::value), @@ -585,7 +608,7 @@ class optional : private detail::optional_move_assign_base, public: // The different versions for C++14 and 11 are needed because deduced return -// types are not SFINAE-safe. C.f. +// types are not SFINAE-safe. This provides better support for things like generic lambdas. C.f. // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) /// \group and_then @@ -705,19 +728,28 @@ public: /// value())`. Returns a `std::optional`. The return value is empty if /// `*this` is empty, otherwise an `optional` is constructed from the /// return value of `std::invoke(std::forward(f), value())` and is - /// returned. \group map \synopsis template auto map(F &&f) &; + /// returned. + /// + /// \group map + /// \synopsis template constexpr auto map(F &&f) &; template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { return map_impl(*this, std::forward(f)); } + /// \group map + /// \synopsis template constexpr auto map(F &&f) &&; template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { return map_impl(std::move(*this), std::forward(f)); } + /// \group map + /// \synopsis template constexpr auto map(F &&f) const&; template constexpr auto map(F &&f) const & { return map_impl(*this, std::forward(f)); } + /// \group map + /// \synopsis template constexpr auto map(F &&f) const&&; template constexpr auto map(F &&f) const && { return map_impl(std::move(*this), std::forward(f)); } @@ -727,7 +759,10 @@ public: /// value())`. Returns a `std::optional`. The return value is empty if /// `*this` is empty, otherwise an `optional` is constructed from the /// return value of `std::invoke(std::forward(f), value())` and is - /// returned. \group map \synopsis template auto map(F &&f) &; + /// returned. + /// + /// \group map + /// \synopsis template auto map(F &&f) &; template TL_OPTIONAL_11_CONSTEXPR decltype(map_impl(std::declval(), std::declval())) @@ -735,6 +770,8 @@ public: return map_impl(*this, std::forward(f)); } + /// \group map + /// \synopsis template auto map(F &&f) &&; template TL_OPTIONAL_11_CONSTEXPR decltype(map_impl(std::declval(), std::declval())) @@ -742,6 +779,8 @@ public: return map_impl(std::move(*this), std::forward(f)); } + /// \group map + /// \synopsis template auto map(F &&f) const&; template constexpr decltype(map_impl(std::declval(), std::declval())) @@ -750,6 +789,8 @@ public: } #ifndef TL_OPTIONAL_NO_CONSTRR + /// \group map + /// \synopsis template auto map(F &&f) const&&; template constexpr decltype(map_impl(std::declval(), std::declval())) @@ -763,7 +804,9 @@ public: /// \requires `std::invoke_result_t` must be void or convertible to /// `optional`. \effects If `*this` has a value, returns `*this`. /// Otherwise, if `f` returns `void`, calls `std::forward(f)` and returns - /// `std::nullopt`. Otherwise, returns `std::forward(f)()`. \group or_else + /// `std::nullopt`. Otherwise, returns `std::forward(f)()`. + /// + /// \group or_else /// \synopsis template optional or_else (F &&f) &; template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { @@ -833,8 +876,12 @@ public: #endif /// \brief Maps the stored value with `f` if there is one, otherwise returns - /// `u` \details If there is a value stored, then `f` is called with `**this` - /// and the value is returned. Otherwise `u` is returned. \group map_or + /// `u`. + /// + /// \details If there is a value stored, then `f` is called with `**this` + /// and the value is returned. Otherwise `u` is returned. + /// + /// \group map_or template U map_or(F &&f, U &&u) & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u); @@ -861,10 +908,14 @@ public: #endif /// \brief Maps the stored value with `f` if there is one, otherwise calls `u` - /// and returns the result. \details If there is a value stored, then `f` is + /// and returns the result. + /// + /// \details If there is a value stored, then `f` is /// called with `**this` and the value is returned. Otherwise - /// `std::forward(u)()` is returned. \group map_or_else \synopsis template - /// \nauto map_or_else(F &&f, U &&u) &; + /// `std::forward(u)()` is returned. + /// + /// \group map_or_else + /// \synopsis template \nauto map_or_else(F &&f, U &&u) &; template detail::invoke_result_t map_or_else(F &&f, U &&u) & { return has_value() ? detail::invoke(std::forward(f), **this) @@ -890,8 +941,7 @@ public: #ifndef TL_OPTIONAL_NO_CONSTRR /// \group map_or_else - /// \synopsis template \nauto map_or_else(F &&f, U &&u) - /// const &&; + /// \synopsis template \nauto map_or_else(F &&f, U &&u) const &&; template detail::invoke_result_t map_or_else(F &&f, U &&u) const && { return has_value() ? detail::invoke(std::forward(f), std::move(**this))