diff --git a/optional.hpp b/optional.hpp index 023d09b..3830f9a 100644 --- a/optional.hpp +++ b/optional.hpp @@ -32,7 +32,12 @@ #define TL_OPTIONAL_NO_CONSTRR #endif -#if __cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || defined(TL_OPTIONAL_GCC49) +#if __cplusplus > 201103L +#define TL_OPTIONAL_CXX14 +#endif + +#if __cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || \ + defined(TL_OPTIONAL_GCC49) /// \exclude #define TL_OPTIONAL_11_CONSTEXPR #else @@ -40,7 +45,6 @@ #define TL_OPTIONAL_11_CONSTEXPR constexpr #endif - namespace tl { /// \brief Used to represent an optional with no data; essentially a bool class monostate {}; @@ -118,36 +122,20 @@ using invoke_result = invoke_result_impl; template using invoke_result_t = typename invoke_result::type; - // Change void to tl::monostate template using fixup_void = conditional_t::value, monostate, U>; -template struct get_invoke_optional_ret { - using type = invoke_result_t< - conditional_t::value, - typename remove_reference_t::value_type &, - typename remove_reference_t::value_type &&>, - U...>; -}; - -template -using get_invoke_ret = typename conditional_t::value, - get_invoke_optional_ret, - invoke_result>::type; - -template -using get_map_return = optional>>; +template > +using get_map_return = optional>>; // Check if invoking F for some Us returns void +template struct returns_void_impl; template -using returns_void = std::is_void>; - -template -using disable_if_optional = enable_if_t::value>; - -template -using enable_if_optional = enable_if_t::value>; +struct returns_void_impl>, U...> + : std::is_void> {}; +template +using returns_void = returns_void_impl; template using enable_if_ret_void = enable_if_t::value>; @@ -270,10 +258,10 @@ struct is_nothrow_swappable }; #endif - template ::value> struct optional_storage_base { - TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept : m_dummy(), m_has_value(false) {} + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} template TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) noexcept @@ -296,12 +284,11 @@ struct optional_storage_base { }; template struct optional_storage_base { - TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept : m_dummy(), m_has_value(false) {} template - TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, - U &&... u) noexcept + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) noexcept : m_value(std::forward(u)...), m_has_value(true) {} ~optional_storage_base() = default; @@ -314,6 +301,7 @@ template struct optional_storage_base { bool m_has_value = false; }; + } // namespace detail /// \brief A tag type to represent an empty optional @@ -339,18 +327,25 @@ public: const char *what() const noexcept { return "Optional has no value"; } }; -/// An optional object is an object that contains the storage for another object and manages the lifetime of this contained object, if any. The contained object may be initialized after the optional object has been initialized, and may be destroyed before the optional object has been destroyed. The initialization state of the contained object is tracked by the optional object. +/// An optional object is an object that contains the storage for another object +/// and manages the lifetime of this contained object, if any. The contained +/// object may be initialized after the optional object has been initialized, +/// and may be destroyed before the optional object has been destroyed. The +/// initialization state of the contained object is tracked by the optional +/// object. template class optional : private detail::optional_storage_base { using base = detail::optional_storage_base; public: - /// \group and_then - /// Carries out some operation which returns an optional on the stored object if there is one. - /// \requires `std::invoke(std::forward(f), value())` returns a `std::optional` for some `U`. - /// \returns Let `U` be the result of `std::invoke(std::forward(f), value())`. Returns a `std::optional`. The return value is empty if `*this` is empty, otherwise the return value of `std::invoke(std::forward(f), value())` is returned. - /// \group and_then - /// \synopsis template \nconstexpr auto and_then(F &&f) &; + /// Carries out some operation which returns an optional on the stored object + /// if there is one. \requires `std::invoke(std::forward(f), value())` + /// returns a `std::optional` for some `U`. \returns Let `U` be the result + /// of `std::invoke(std::forward(f), value())`. Returns a + /// `std::optional`. The return value is empty if `*this` is empty, + /// otherwise the return value of `std::invoke(std::forward(f), value())` + /// is returned. \group and_then \synopsis template \nconstexpr auto + /// and_then(F &&f) &; template TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { using result = detail::invoke_result_t; @@ -385,7 +380,7 @@ public: : result(nullopt); } - #ifndef TL_OPTIONAL_NO_CONSTRR +#ifndef TL_OPTIONAL_NO_CONSTRR /// \group and_then /// \synopsis template \nconstexpr auto and_then(F &&f) const &&; template @@ -397,187 +392,71 @@ public: return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : result(nullopt); } - #endif +#endif +#ifdef TL_OPTIONAL_CXX14 /// \brief Carries out some operation on the stored object if there is one. - /// \returns Let `U` be the result of `std::invoke(std::forward(f), 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) &; - template * = nullptr, - detail::disable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) & - noexcept(noexcept(detail::invoke(std::forward(f), - std::declval()))) { - using result = detail::get_map_return; - return has_value() ? detail::invoke(std::forward(f), **this) - : result(nullopt); + /// \returns Let `U` be the result of `std::invoke(std::forward(f), + /// 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) &; + template auto map(F &&f) & { + return map_impl(*this, std::forward(f)); } - /// \exclude - template * = nullptr, - detail::enable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) & { - if (!has_value()) - return nullopt; - - detail::invoke(std::forward(f), **this); - return monostate{}; + template auto map(F &&f) && { + return map_impl(std::move(*this), std::forward(f)); } - /// \exclude - template * = nullptr, - detail::disable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) & { - using result = detail::get_map_return; - return (f.has_value() && has_value()) - ? detail::invoke(*std::forward(f), **this) - : result(nullopt); + template auto map(F &&f) const & { + return map_impl(*this, std::forward(f)); } - /// \exclude - template * = nullptr, - detail::enable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) & { - if (!f.has_value() || !has_value()) - return nullopt; - - detail::invoke(*std::forward(f), **this); - return monostate{}; +#ifndef TL_OPTIONAL_NO_CONSTRR + template auto map(F &&f) const && { + return map_impl(std::move(*this), std::forward(f)); + } +#endif +#else + /// \brief Carries out some operation on the stored object if there is one. + /// \returns Let `U` be the result of `std::invoke(std::forward(f), + /// 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) &; + template + decltype(map_impl(std::declval(), std::declval())) + map(F &&f) & { + return map_impl(*this, std::forward(f)); } - /// \group map - /// \synopsis template auto map(F &&f) &&; - template * = nullptr, - detail::disable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) && { - using result = detail::get_map_return; - return has_value() ? detail::invoke(std::forward(f), std::move(**this)) - : result(nullopt); + template + decltype(map_impl(std::declval(), std::declval())) + map(F &&f) && { + return map_impl(std::move(*this), std::forward(f)); } - /// \exclude - template * = nullptr, - detail::enable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) && { - if (!has_value()) - return nullopt; - - detail::invoke(std::forward(f), std::move(**this)); - return monostate{}; + template + decltype(map_impl(std::declval(), std::declval())) + map(F &&f) const & { + return map_impl(*this, std::forward(f)); } - /// \exclude - template * = nullptr, - detail::disable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) && { - using result = detail::get_map_return; - return (f.has_value() && has_value()) - ? detail::invoke(*std::forward(f), std::move(**this)) - : result(nullopt); +#ifndef TL_OPTIONAL_NO_CONSTRR + template + decltype(map_impl(std::declval(), std::declval())) + map(F &&f) const && { + return map_impl(std::move(*this), std::forward(f)); } - - /// \exclude - template * = nullptr, - detail::enable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) && { - if (!f.has_value() || !has_value()) - return nullopt; - - detail::invoke(*std::forward(f), std::move(**this)); - return monostate{}; - } - - /// \group map - /// \synopsis template auto map(F &&f) const &; - template * = nullptr, - detail::disable_if_ret_void * = nullptr> - constexpr detail::get_map_return map(F &&f) const & { - using result = detail::get_map_return; - return this->has_value() - ? result(detail::invoke(std::forward(f), **this)) - : result(nullopt); - } - - /// \exclude - template * = nullptr, - detail::enable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) const & { - if (!has_value()) - return nullopt; - - detail::invoke(std::forward(f), **this); - return monostate{}; - } - - /// \exclude - template * = nullptr, - detail::disable_if_ret_void * = nullptr> - constexpr detail::get_map_return map(F &&f) const & { - using result = detail::get_map_return; - return (f.has_value() && has_value()) - ? detail::invoke(*std::forward(f), **this) - : result(nullopt); - } - - /// \exclude - template * = nullptr, - detail::enable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) const & { - if (!f.has_value() || !has_value()) - return nullopt; - - detail::invoke(*std::forward(f), **this); - return monostate{}; - } - - #ifndef TL_OPTIONAL_NO_CONSTRR - /// \group map - /// \synopsis template auto map(F &&f) const &&; - template * = nullptr, - detail::disable_if_ret_void * = nullptr> - constexpr detail::get_map_return map(F &&f) const && { - using result = detail::get_map_return; - return has_value() ? detail::invoke(std::forward(f), std::move(**this)) - : result(nullopt); - } - - /// \exclude - template * = nullptr, - detail::enable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) const && { - if (!has_value()) - return nullopt; - - detail::invoke(std::forward(f), std::move(**this)); - return monostate{}; - } - - /// \exclude - template * = nullptr, - detail::disable_if_ret_void * = nullptr> - constexpr detail::get_map_return map(F &&f) const && { - using result = detail::get_map_return; - return (f.has_value() && has_value()) - ? detail::invoke(*std::forward(f), std::move(**this)) - : result(nullopt); - } - - /// \exclude - template * = nullptr, - detail::enable_if_ret_void * = nullptr> - detail::get_map_return map(F &&f) const && { - if (!f.has_value() || !has_value()) - return nullopt; - - detail::invoke(*std::forward(f), std::move(**this)); - return monostate{}; - } - #endif +#endif +#endif /// \brief Calls `f` if the optional is empty - /// \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 + /// \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 /// \synopsis template optional or_else (F &&f) &; template * = nullptr> optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { @@ -628,7 +507,7 @@ public: return has_value() ? *this : std::forward(f)(); } - #ifndef TL_OPTIONAL_NO_CONSTRR +#ifndef TL_OPTIONAL_NO_CONSTRR /// \exclude template * = nullptr> optional or_else(F &&f) const && { @@ -644,12 +523,11 @@ public: optional or_else(F &&f) const && { return has_value() ? std::move(*this) : std::forward(f)(); } - #endif +#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 + /// \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 template U map_or(F &&f, U &&u) & { return has_value() ? detail::invoke(std::forward(f), **this) : std::forward(u); @@ -667,19 +545,19 @@ public: : std::forward(u); } - #ifndef TL_OPTIONAL_NO_CONSTRR +#ifndef TL_OPTIONAL_NO_CONSTRR /// \group map_or template U map_or(F &&f, U &&u) const && { return has_value() ? detail::invoke(std::forward(f), std::move(**this)) : std::forward(u); } - #endif +#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 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) &; + /// \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 + /// 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) &; template detail::invoke_result_t map_or_else(F &&f, U &&u) & { return has_value() ? detail::invoke(std::forward(f), **this) @@ -695,74 +573,76 @@ public: } /// \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), **this) : std::forward(u)(); } - #ifndef TL_OPTIONAL_NO_CONSTRR +#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)) : std::forward(u)(); } - #endif +#endif /// \returns `u` if `*this` has a value, otherwise an empty optional. template - constexpr optional::type> conjunction (U &&u) const { + constexpr optional::type> conjunction(U &&u) const { using result = optional>; return has_value() ? result{u} : result{nullopt}; } /// \returns `rhs` if `*this` is empty, otherwise the current value. /// \group disjunction - TL_OPTIONAL_11_CONSTEXPR optional disjunction (const optional &rhs) & { + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { return has_value() ? *this : rhs; } /// \group disjunction - constexpr optional disjunction (const optional &rhs) const & { + constexpr optional disjunction(const optional &rhs) const & { return has_value() ? *this : rhs; } /// \group disjunction - TL_OPTIONAL_11_CONSTEXPR optional disjunction (const optional &rhs) && { + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { return has_value() ? std::move(*this) : rhs; } - #ifndef TL_OPTIONAL_NO_CONSTRR +#ifndef TL_OPTIONAL_NO_CONSTRR /// \group disjunction - constexpr optional disjunction (const optional &rhs) const && { + constexpr optional disjunction(const optional &rhs) const && { return has_value() ? std::move(*this) : rhs; } - #endif +#endif /// \group disjunction - TL_OPTIONAL_11_CONSTEXPR optional disjunction (optional &&rhs) & { + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { return has_value() ? *this : std::move(rhs); } /// \group disjunction - constexpr optional disjunction (optional &&rhs) const & { + constexpr optional disjunction(optional &&rhs) const & { return has_value() ? *this : std::move(rhs); } /// \group disjunction - TL_OPTIONAL_11_CONSTEXPR optional disjunction (optional &&rhs) && { + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { return has_value() ? std::move(*this) : std::move(rhs); } - #ifndef TL_OPTIONAL_NO_CONSTRR +#ifndef TL_OPTIONAL_NO_CONSTRR /// \group disjunction - constexpr optional disjunction (optional &&rhs) const && { + constexpr optional disjunction(optional &&rhs) const && { return has_value() ? std::move(*this) : std::move(rhs); } - #endif +#endif /// Takes the value out of the optional, leaving it empty /// \group take @@ -786,14 +666,14 @@ public: return ret; } - #ifndef TL_OPTIONAL_NO_CONSTRR +#ifndef TL_OPTIONAL_NO_CONSTRR /// \group take optional take() const && { optional ret = std::move(*this); reset(); return ret; } - #endif +#endif using value_type = T; @@ -828,10 +708,10 @@ public: } } - /// Constructs the stored value in-place using the given arguments. /// \group in_place - /// \synopsis template constexpr explicit optional(in_place_t, Args&&... args); + /// \synopsis template constexpr explicit optional(in_place_t, + /// Args&&... args); template constexpr explicit optional( detail::enable_if_t::value, in_place_t>, @@ -839,7 +719,8 @@ public: : base(in_place, std::forward(args)...) {} /// \group in_place - /// \synopsis template \nconstexpr explicit optional(in_place_t, std::initializer_list&, Args&&... args); + /// \synopsis template \nconstexpr explicit + /// optional(in_place_t, std::initializer_list&, Args&&... args); template TL_OPTIONAL_11_CONSTEXPR explicit optional( detail::enable_if_t &, @@ -921,7 +802,8 @@ public: // TODO conditionally delete, check exception guarantee /// Copy assignment. /// - /// Copies the value from `rhs` if there is one. Otherwise resets the stored value in `*this`. + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. optional &operator=(const optional &rhs) { if (has_value()) { if (rhs.has_value()) { @@ -943,7 +825,8 @@ public: // TODO conditionally delete, check exception guarantee /// Move assignment. /// - /// Moves the value from `rhs` if there is one. Otherwise resets the stored value in `*this`. + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. optional &operator=(optional &&rhs) noexcept( std::is_nothrow_move_assignable::value &&std::is_nothrow_move_constructible::value) { @@ -965,8 +848,8 @@ public: } // TODO conditionally delete, check exception guarantee - /// Assigns the stored value from `u`, destroying the old value if there was one. - /// \synopsis optional &operator=(U &&u); + /// Assigns the stored value from `u`, destroying the old value if there was + /// one. \synopsis optional &operator=(U &&u); template * = nullptr> optional &operator=(U &&u) { if (has_value()) { @@ -982,8 +865,8 @@ public: // TODO check exception guarantee /// Converting copy assignment operator. /// - /// Copies the value from `rhs` if there is one. Otherwise resets the stored value in `*this`. - /// \synopsis optional &operator=(const optional & rhs); + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. \synopsis optional &operator=(const optional & rhs); template * = nullptr> optional &operator=(const optional &rhs) { @@ -1007,8 +890,8 @@ public: // TODO check exception guarantee /// Converting move assignment operator. /// - /// Moves the value from `rhs` if there is one. Otherwise resets the stored value in `*this`. - /// \synopsis optional &operator=(optional && rhs); + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. \synopsis optional &operator=(optional && rhs); template * = nullptr> optional &operator=(optional &&rhs) { if (has_value()) { @@ -1039,7 +922,8 @@ public: } /// \group emplace - /// \synopsis template \nT& emplace(std::initializer_list il, Args &&... args); + /// \synopsis template \nT& + /// emplace(std::initializer_list il, Args &&... args); template detail::enable_if_t< std::is_constructible &, Args &&...>::value, @@ -1053,7 +937,8 @@ public: /// /// If neither optionals have a value, nothing happens. /// If both have a value, the values are swapped. - /// If one has a value, it is moved to the other and the movee is left valueless. + /// If one has a value, it is moved to the other and the movee is left + /// valueless. void swap(optional &rhs) noexcept(std::is_nothrow_move_constructible::value &&detail::is_nothrow_swappable::value) { @@ -1100,10 +985,10 @@ public: return std::move(this->m_value); } - #ifndef TL_OPTIONAL_NO_CONSTRR +#ifndef TL_OPTIONAL_NO_CONSTRR /// \exclude constexpr const T &&operator*() const && { return std::move(this->m_value); } - #endif +#endif /// \returns whether or not the optional has a value /// \group has_value @@ -1114,9 +999,8 @@ public: return this->m_has_value; } - /// \returns the contained value if there is one, otherwise throws [bad_optional_access] - /// \group value - /// \synopsis constexpr T &value(); + /// \returns the contained value if there is one, otherwise throws + /// [bad_optional_access] \group value \synopsis constexpr T &value(); TL_OPTIONAL_11_CONSTEXPR T &value() & { if (has_value()) return this->m_value; @@ -1136,14 +1020,14 @@ public: throw bad_optional_access(); } - #ifndef TL_OPTIONAL_NO_CONSTRR +#ifndef TL_OPTIONAL_NO_CONSTRR /// \exclude constexpr const T &&value() const && { if (has_value()) return std::move(this->m_value); throw bad_optional_access(); } - #endif +#endif /// \returns the stored value if there is one, otherwise returns `u` /// \group value_or @@ -1169,15 +1053,14 @@ public: this->m_has_value = false; } } - - }; /// \group relop /// \brief Compares two optional objects -/// \details If both optionals contain a value, they are compared with `T`s relational operators. -/// Otherwise `lhs` and `rhs` are equal only if they are both empty, and `lhs` is less than `rhs` -/// only if `rhs` is empty and `lhs` is not. +/// \details If both optionals contain a value, they are compared with `T`s +/// relational operators. Otherwise `lhs` and `rhs` are equal only if they are +/// both empty, and `lhs` is less than `rhs` only if `rhs` is empty and `lhs` is +/// not. template inline constexpr bool operator==(const optional &lhs, const optional &rhs) { @@ -1279,11 +1162,11 @@ inline constexpr bool operator>=(nullopt_t, const optional &rhs) noexcept { return !rhs.has_value(); } - /// \group relop_t /// \brief Compares the optional with a value. -/// \details If the optional has a value, it is compared with the other value using `T`s relational operators. -/// Otherwise, the optional is considered less than the value. +/// \details If the optional has a value, it is compared with the other value +/// using `T`s relational operators. Otherwise, the optional is considered less +/// than the value. template inline constexpr bool operator==(const optional &lhs, const U &rhs) { return lhs.has_value() ? *lhs == rhs : false; @@ -1371,6 +1254,59 @@ inline constexpr optional make_optional(std::initializer_list il, template optional(T)->optional; #endif +/// \exclude +namespace detail { +#ifdef TL_OPTIONAL_CX14 +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto map_impl(Opt &&opt, F &&f) { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto map_impl(Opt &&opt, F &&f) { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return monostate{}; + } + + return optional(nullopt); +} +#else +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto map_impl(Opt &&opt, F &&f) -> optional { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto map_impl(Opt &&opt, F &&f) -> optional { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return monostate{}; + } + + return nullopt; +} +#endif +} // namespace detail + } // namespace tl namespace std { diff --git a/tests/extensions.cpp b/tests/extensions.cpp index 53172d0..2ef9bce 100644 --- a/tests/extensions.cpp +++ b/tests/extensions.cpp @@ -12,342 +12,272 @@ constexpr int get_int(int) { return 42; } TL_OPTIONAL_11_CONSTEXPR tl::optional get_opt_int(int) { return 42; } // What is Clang Format up to?! -TEST_CASE("Monadic operations", - "[monadic]"){SECTION("map"){// lhs is empty - tl::optional o1; -auto o1r = o1.map([](int i) { return i + 2; }); -STATIC_REQUIRE((std::is_same>::value)); -REQUIRE(!o1r); +TEST_CASE("Monadic operations", "[monadic]") { + SECTION("map") { // lhs is empty + tl::optional o1; + auto o1r = o1.map([](int i) { return i + 2; }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(!o1r); -// lhs has value -tl::optional o2 = 40; -auto o2r = o2.map([](int i) { return i + 2; }); -STATIC_REQUIRE((std::is_same>::value)); -REQUIRE(o2r.value() == 42); + // lhs has value + tl::optional o2 = 40; + auto o2r = o2.map([](int i) { return i + 2; }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(o2r.value() == 42); -struct rval_call_map { - double operator()(int) && { return 42.0; }; -}; - -// ensure that function object is forwarded -tl::optional o3 = 42; -auto o3r = o3.map(rval_call_map{}); -STATIC_REQUIRE((std::is_same>::value)); -REQUIRE(o3r.value() == 42); - -// ensure that lhs is forwarded -tl::optional o4 = 40; -auto o4r = std::move(o4).map([](int &&i) { return i + 2; }); -STATIC_REQUIRE((std::is_same>::value)); -REQUIRE(o4r.value() == 42); - -// ensure that lhs is const-propagated -const tl::optional o5 = 40; -auto o5r = o5.map([](const int &i) { return i + 2; }); -STATIC_REQUIRE((std::is_same>::value)); -REQUIRE(o5r.value() == 42); - -// test applicative functor -tl::optional o6 = 40; -auto f6 = tl::make_optional([](const int &i) { return i + 2; }); -auto o6r = o6.map(f6); -STATIC_REQUIRE((std::is_same>::value)); -REQUIRE(o6r.value() == 42); - -// test void return -tl::optional o7 = 40; -auto f7 = tl::make_optional([](const int &i) { return; }); -auto o7r = o7.map(f7); -STATIC_REQUIRE( - (std::is_same>::value)); -REQUIRE(o6r.has_value()); - -// test each overload in turn -tl::optional o8 = 42; -auto o8r = o8.map([](int) { return 42; }); -REQUIRE(*o8r == 42); - -tl::optional o9 = 42; -auto o9r = o9.map([](int) { return; }); -REQUIRE(o9r); - -tl::optional o10 = 42; -auto o10r = o10.map(tl::make_optional([](int) { return 42; })); -REQUIRE(*o10r == 42); - -tl::optional o11 = 42; -auto o11r = o11.map(tl::make_optional([](int) { return; })); -REQUIRE(o11r); - -tl::optional o12 = 42; -auto o12r = std::move(o12).map([](int) { return 42; }); -REQUIRE(*o12r == 42); - -tl::optional o13 = 42; -auto o13r = std::move(o13).map([](int) { return; }); -REQUIRE(o13r); - -tl::optional o14 = 42; -auto o14r = std::move(o14).map(tl::make_optional([](int) { return 42; })); -REQUIRE(*o14r == 42); - -tl::optional o15 = 42; -auto o15r = std::move(o15).map(tl::make_optional([](int) { return; })); -REQUIRE(o15r); - -const tl::optional o16 = 42; -auto o16r = o16.map([](int) { return 42; }); -REQUIRE(*o16r == 42); - -const tl::optional o17 = 42; -auto o17r = o17.map([](int) { return; }); -REQUIRE(o17r); - -const tl::optional o18 = 42; -auto o18r = o18.map(tl::make_optional([](int) { return 42; })); -REQUIRE(*o18r == 42); - -const tl::optional o19 = 42; -auto o19r = o19.map(tl::make_optional([](int) { return; })); -REQUIRE(o19r); - -const tl::optional o20 = 42; -auto o20r = std::move(o20).map([](int) { return 42; }); -REQUIRE(*o20r == 42); - -const tl::optional o21 = 42; -auto o21r = std::move(o21).map([](int) { return; }); -REQUIRE(o21r); - -const tl::optional o22 = 42; -auto o22r = std::move(o22).map(tl::make_optional([](int) { return 42; })); -REQUIRE(*o22r == 42); - -const tl::optional o23 = 42; -auto o23r = std::move(o23).map(tl::make_optional([](int) { return; })); -REQUIRE(o23r); - -tl::optional o24 = tl::nullopt; -auto o24r = o24.map([](int) { return 42; }); -REQUIRE(!o24r); - -tl::optional o25 = tl::nullopt; -auto o25r = o25.map([](int) { return; }); -REQUIRE(!o25r); - -tl::optional o26 = tl::nullopt; -auto o26r = o26.map(tl::make_optional([](int) { return 42; })); -REQUIRE(!o26r); - -tl::optional o27 = tl::nullopt; -auto o27r = o27.map(tl::make_optional([](int) { return; })); -REQUIRE(!o27r); - -tl::optional o28 = tl::nullopt; -auto o28r = std::move(o28).map([](int) { return 42; }); -REQUIRE(!o28r); - -tl::optional o29 = tl::nullopt; -auto o29r = std::move(o29).map([](int) { return; }); -REQUIRE(!o29r); - -tl::optional o30 = tl::nullopt; -auto o30r = std::move(o30).map(tl::make_optional([](int) { return 42; })); -REQUIRE(!o30r); - -tl::optional o31 = tl::nullopt; -auto o31r = std::move(o31).map(tl::make_optional([](int) { return; })); -REQUIRE(!o31r); - -const tl::optional o32 = tl::nullopt; -auto o32r = o32.map([](int) { return 42; }); -REQUIRE(!o32r); - -const tl::optional o33 = tl::nullopt; -auto o33r = o33.map([](int) { return; }); -REQUIRE(!o33r); - -const tl::optional o34 = tl::nullopt; -auto o34r = o34.map(tl::make_optional([](int) { return 42; })); -REQUIRE(!o34r); - -const tl::optional o35 = tl::nullopt; -auto o35r = o35.map(tl::make_optional([](int) { return; })); -REQUIRE(!o35r); - -const tl::optional o36 = tl::nullopt; -auto o36r = std::move(o36).map([](int) { return 42; }); -REQUIRE(!o36r); - -const tl::optional o37 = tl::nullopt; -auto o37r = std::move(o37).map([](int) { return; }); -REQUIRE(!o37r); - -const tl::optional o38 = tl::nullopt; -auto o38r = std::move(o38).map(tl::make_optional([](int) { return 42; })); -REQUIRE(!o38r); - -const tl::optional o39 = tl::nullopt; -auto o39r = std::move(o39).map(tl::make_optional([](int) { return; })); -REQUIRE(!o39r); -} - -SECTION("map constexpr") { -#if !defined(_MSC_VER) && !defined(TL_OPTIONAL_GCC49) - // test each overload in turn - constexpr tl::optional o16 = 42; - constexpr auto o16r = o16.map(get_int); - STATIC_REQUIRE(*o16r == 42); - - constexpr tl::optional o18 = 42; - constexpr auto opt_int = tl::make_optional(get_int); - constexpr auto o18r = o18.map(opt_int); - STATIC_REQUIRE(*o18r == 42); - - constexpr tl::optional o20 = 42; - constexpr auto o20r = std::move(o20).map(get_int); - STATIC_REQUIRE(*o20r == 42); - - constexpr tl::optional o22 = 42; - constexpr auto o22r = std::move(o22).map(opt_int); - STATIC_REQUIRE(*o22r == 42); - - constexpr tl::optional o32 = tl::nullopt; - constexpr auto o32r = o32.map(get_int); - STATIC_REQUIRE(!o32r); - - constexpr tl::optional o34 = tl::nullopt; - constexpr auto o34r = o34.map(opt_int); - STATIC_REQUIRE(!o34r); - - constexpr tl::optional o36 = tl::nullopt; - constexpr auto o36r = std::move(o36).map(get_int); - STATIC_REQUIRE(!o36r); - - constexpr tl::optional o38 = tl::nullopt; - constexpr auto o38r = std::move(o38).map(opt_int); - STATIC_REQUIRE(!o38r); -#endif -} - -SECTION("and_then") { - - // lhs is empty - tl::optional o1; - auto o1r = o1.and_then([](int i) { return tl::optional{42}; }); - STATIC_REQUIRE((std::is_same>::value)); - REQUIRE(!o1r); - - // lhs has value - tl::optional o2 = 12; - auto o2r = o2.and_then([](int i) { return tl::optional{42}; }); - STATIC_REQUIRE((std::is_same>::value)); - REQUIRE(o2r.value() == 42.f); - - // lhs is empty, rhs returns empty - tl::optional o3; - auto o3r = o3.and_then([](int i) { return tl::optional{}; }); - STATIC_REQUIRE((std::is_same>::value)); - REQUIRE(!o3r); - - // rhs returns empty - tl::optional o4 = 12; - auto o4r = o4.and_then([](int i) { return tl::optional{}; }); - STATIC_REQUIRE((std::is_same>::value)); - REQUIRE(!o4r); - - struct rval_call_and_then { - tl::optional operator()(int) && { - return tl::optional(42.0); + struct rval_call_map { + double operator()(int) && { return 42.0; }; }; - }; - // ensure that function object is forwarded - tl::optional o5 = 42; - auto o5r = o5.and_then(rval_call_and_then{}); - STATIC_REQUIRE((std::is_same>::value)); - REQUIRE(o5r.value() == 42); + // ensure that function object is forwarded + tl::optional o3 = 42; + auto o3r = o3.map(rval_call_map{}); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(o3r.value() == 42); - // ensure that lhs is forwarded - tl::optional o6 = 42; - auto o6r = - std::move(o6).and_then([](int &&i) { return tl::optional(i); }); - STATIC_REQUIRE((std::is_same>::value)); - REQUIRE(o6r.value() == 42); + // ensure that lhs is forwarded + tl::optional o4 = 40; + auto o4r = std::move(o4).map([](int &&i) { return i + 2; }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(o4r.value() == 42); - // ensure that function object is const-propagated - const tl::optional o7 = 42; - auto o7r = o7.and_then([](const int &i) { return tl::optional(i); }); - STATIC_REQUIRE((std::is_same>::value)); - REQUIRE(o7r.value() == 42); + // ensure that lhs is const-propagated + const tl::optional o5 = 40; + auto o5r = o5.map([](const int &i) { return i + 2; }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(o5r.value() == 42); - // test each overload in turn - tl::optional o8 = 42; - auto o8r = o8.and_then([](int i) { return tl::make_optional(42); }); - REQUIRE(*o8r == 42); + // test void return + tl::optional o7 = 40; + auto f7 = [](const int &i) { return; }; + auto o7r = o7.map(f7); + STATIC_REQUIRE( + (std::is_same>::value)); + REQUIRE(o7r.has_value()); - tl::optional o9 = 42; - auto o9r = - std::move(o9).and_then([](int i) { return tl::make_optional(42); }); - REQUIRE(*o9r == 42); + // test each overload in turn + tl::optional o8 = 42; + auto o8r = o8.map([](int) { return 42; }); + REQUIRE(*o8r == 42); - const tl::optional o10 = 42; - auto o10r = o10.and_then([](int i) { return tl::make_optional(42); }); - REQUIRE(*o10r == 42); + tl::optional o9 = 42; + auto o9r = o9.map([](int) { return; }); + REQUIRE(o9r); - const tl::optional o11 = 42; - auto o11r = - std::move(o11).and_then([](int i) { return tl::make_optional(42); }); - REQUIRE(*o11r == 42); + tl::optional o12 = 42; + auto o12r = std::move(o12).map([](int) { return 42; }); + REQUIRE(*o12r == 42); - tl::optional o16 = tl::nullopt; - auto o16r = o16.and_then([](int i) { return tl::make_optional(42); }); - REQUIRE(!o16r); + tl::optional o13 = 42; + auto o13r = std::move(o13).map([](int) { return; }); + REQUIRE(o13r); - tl::optional o17 = tl::nullopt; - auto o17r = - std::move(o17).and_then([](int i) { return tl::make_optional(42); }); - REQUIRE(!o17r); + const tl::optional o16 = 42; + auto o16r = o16.map([](int) { return 42; }); + REQUIRE(*o16r == 42); - const tl::optional o18 = tl::nullopt; - auto o18r = o18.and_then([](int i) { return tl::make_optional(42); }); - REQUIRE(!o18r); + const tl::optional o17 = 42; + auto o17r = o17.map([](int) { return; }); + REQUIRE(o17r); - const tl::optional o19 = tl::nullopt; - auto o19r = - std::move(o19).and_then([](int i) { return tl::make_optional(42); }); - REQUIRE(!o19r); -} + const tl::optional o20 = 42; + auto o20r = std::move(o20).map([](int) { return 42; }); + REQUIRE(*o20r == 42); -SECTION("constexpr and_then") { + const tl::optional o21 = 42; + auto o21r = std::move(o21).map([](int) { return; }); + REQUIRE(o21r); + + tl::optional o24 = tl::nullopt; + auto o24r = o24.map([](int) { return 42; }); + REQUIRE(!o24r); + + tl::optional o25 = tl::nullopt; + auto o25r = o25.map([](int) { return; }); + REQUIRE(!o25r); + + tl::optional o28 = tl::nullopt; + auto o28r = std::move(o28).map([](int) { return 42; }); + REQUIRE(!o28r); + + tl::optional o29 = tl::nullopt; + auto o29r = std::move(o29).map([](int) { return; }); + REQUIRE(!o29r); + + const tl::optional o32 = tl::nullopt; + auto o32r = o32.map([](int) { return 42; }); + REQUIRE(!o32r); + + const tl::optional o33 = tl::nullopt; + auto o33r = o33.map([](int) { return; }); + REQUIRE(!o33r); + + const tl::optional o36 = tl::nullopt; + auto o36r = std::move(o36).map([](int) { return 42; }); + REQUIRE(!o36r); + + const tl::optional o37 = tl::nullopt; + auto o37r = std::move(o37).map([](int) { return; }); + REQUIRE(!o37r); + } + + SECTION("map constexpr") { #if !defined(_MSC_VER) && !defined(TL_OPTIONAL_GCC49) - constexpr tl::optional o10 = 42; - constexpr auto o10r = o10.and_then(get_opt_int); - REQUIRE(*o10r == 42); + // test each overload in turn + constexpr tl::optional o16 = 42; + constexpr auto o16r = o16.map(get_int); + STATIC_REQUIRE(*o16r == 42); - constexpr tl::optional o11 = 42; - constexpr auto o11r = std::move(o11).and_then(get_opt_int); - REQUIRE(*o11r == 42); + constexpr tl::optional o18 = 42; + constexpr auto opt_int = tl::make_optional(get_int); + constexpr auto o18r = o18.map(opt_int); + STATIC_REQUIRE(*o18r == 42); - constexpr tl::optional o18 = tl::nullopt; - constexpr auto o18r = o18.and_then(get_opt_int); - REQUIRE(!o18r); + constexpr tl::optional o20 = 42; + constexpr auto o20r = std::move(o20).map(get_int); + STATIC_REQUIRE(*o20r == 42); - constexpr tl::optional o19 = tl::nullopt; - constexpr auto o19r = std::move(o19).and_then(get_opt_int); - REQUIRE(!o19r); + constexpr tl::optional o22 = 42; + constexpr auto o22r = std::move(o22).map(opt_int); + STATIC_REQUIRE(*o22r == 42); + + constexpr tl::optional o32 = tl::nullopt; + constexpr auto o32r = o32.map(get_int); + STATIC_REQUIRE(!o32r); + + constexpr tl::optional o34 = tl::nullopt; + constexpr auto o34r = o34.map(opt_int); + STATIC_REQUIRE(!o34r); + + constexpr tl::optional o36 = tl::nullopt; + constexpr auto o36r = std::move(o36).map(get_int); + STATIC_REQUIRE(!o36r); + + constexpr tl::optional o38 = tl::nullopt; + constexpr auto o38r = std::move(o38).map(opt_int); + STATIC_REQUIRE(!o38r); #endif -} + } -SECTION("or else") { + SECTION("and_then") { + + // lhs is empty + tl::optional o1; + auto o1r = o1.and_then([](int i) { return tl::optional{42}; }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(!o1r); + + // lhs has value + tl::optional o2 = 12; + auto o2r = o2.and_then([](int i) { return tl::optional{42}; }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(o2r.value() == 42.f); + + // lhs is empty, rhs returns empty + tl::optional o3; + auto o3r = o3.and_then([](int i) { return tl::optional{}; }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(!o3r); + + // rhs returns empty + tl::optional o4 = 12; + auto o4r = o4.and_then([](int i) { return tl::optional{}; }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(!o4r); + + struct rval_call_and_then { + tl::optional operator()(int) && { + return tl::optional(42.0); + }; + }; + + // ensure that function object is forwarded + tl::optional o5 = 42; + auto o5r = o5.and_then(rval_call_and_then{}); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(o5r.value() == 42); + + // ensure that lhs is forwarded + tl::optional o6 = 42; + auto o6r = + std::move(o6).and_then([](int &&i) { return tl::optional(i); }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(o6r.value() == 42); + + // ensure that function object is const-propagated + const tl::optional o7 = 42; + auto o7r = + o7.and_then([](const int &i) { return tl::optional(i); }); + STATIC_REQUIRE((std::is_same>::value)); + REQUIRE(o7r.value() == 42); + + // test each overload in turn + tl::optional o8 = 42; + auto o8r = o8.and_then([](int i) { return tl::make_optional(42); }); + REQUIRE(*o8r == 42); + + tl::optional o9 = 42; + auto o9r = + std::move(o9).and_then([](int i) { return tl::make_optional(42); }); + REQUIRE(*o9r == 42); + + const tl::optional o10 = 42; + auto o10r = o10.and_then([](int i) { return tl::make_optional(42); }); + REQUIRE(*o10r == 42); + + const tl::optional o11 = 42; + auto o11r = + std::move(o11).and_then([](int i) { return tl::make_optional(42); }); + REQUIRE(*o11r == 42); + + tl::optional o16 = tl::nullopt; + auto o16r = o16.and_then([](int i) { return tl::make_optional(42); }); + REQUIRE(!o16r); + + tl::optional o17 = tl::nullopt; + auto o17r = + std::move(o17).and_then([](int i) { return tl::make_optional(42); }); + REQUIRE(!o17r); + + const tl::optional o18 = tl::nullopt; + auto o18r = o18.and_then([](int i) { return tl::make_optional(42); }); + REQUIRE(!o18r); + + const tl::optional o19 = tl::nullopt; + auto o19r = + std::move(o19).and_then([](int i) { return tl::make_optional(42); }); + REQUIRE(!o19r); + } + + SECTION("constexpr and_then") { +#if !defined(_MSC_VER) && !defined(TL_OPTIONAL_GCC49) + constexpr tl::optional o10 = 42; + constexpr auto o10r = o10.and_then(get_opt_int); + REQUIRE(*o10r == 42); + + constexpr tl::optional o11 = 42; + constexpr auto o11r = std::move(o11).and_then(get_opt_int); + REQUIRE(*o11r == 42); + + constexpr tl::optional o18 = tl::nullopt; + constexpr auto o18r = o18.and_then(get_opt_int); + REQUIRE(!o18r); + + constexpr tl::optional o19 = tl::nullopt; + constexpr auto o19r = std::move(o19).and_then(get_opt_int); + REQUIRE(!o19r); +#endif + } + + SECTION("or else") { tl::optional o1 = 42; REQUIRE(*(o1.or_else([] { return tl::make_optional(13); })) == 42); tl::optional o2; REQUIRE(*(o2.or_else([] { return tl::make_optional(13); })) == 13); -} + } -SECTION("disjunction") { + SECTION("disjunction") { tl::optional o1 = 42; tl::optional o2 = 12; tl::optional o3; @@ -358,9 +288,9 @@ SECTION("disjunction") { REQUIRE(*o2.disjunction(o3) == 12); REQUIRE(*o3.disjunction(o1) == 42); REQUIRE(*o3.disjunction(o2) == 12); -} + } -SECTION("conjunction") { + SECTION("conjunction") { tl::optional o1 = 42; REQUIRE(*o1.conjunction(42.0) == 42.0); REQUIRE(*o1.conjunction(std::string{"hello"}) == std::string{"hello"}); @@ -370,25 +300,27 @@ SECTION("conjunction") { REQUIRE(!o2.conjunction(42.0)); REQUIRE(!o2.conjunction(std::string{"hello"})); REQUIRE(!o2.conjunction(tl::nullopt)); -} + } -SECTION("map_or") { + SECTION("map_or") { tl::optional o1 = 21; REQUIRE((o1.map_or([](int x) { return x * 2; }, 13)) == 42); tl::optional o2; REQUIRE((o2.map_or([](int x) { return x * 2; }, 13)) == 13); -} + } -SECTION("map_or_else") { + SECTION("map_or_else") { tl::optional o1 = 21; - REQUIRE((o1.map_or_else([](int x) { return x * 2; }, []{return 13;})) == 42); + REQUIRE((o1.map_or_else([](int x) { return x * 2; }, [] { return 13; })) == + 42); tl::optional o2; - REQUIRE((o2.map_or_else([](int x) { return x * 2; }, []{return 13;})) == 13); -} + REQUIRE((o2.map_or_else([](int x) { return x * 2; }, [] { return 13; })) == + 13); + } -SECTION("take") { + SECTION("take") { tl::optional o1 = 42; REQUIRE(*o1.take() == 42); REQUIRE(!o1); @@ -396,6 +328,18 @@ SECTION("take") { tl::optional o2; REQUIRE(!o2.take()); REQUIRE(!o2); -} -} -; + } + + struct foo { + void non_const() {} + }; + +#ifdef TL_OPTIONAL_CXX14 + // Reported by Mark Papadakis + SECTION("map const correctness") { + tl::optional f = foo{}; + auto l = [](auto &&x) { x.non_const(); }; + f.map(l); + } +#endif +};