-
Notifications
You must be signed in to change notification settings - Fork 17
Miscellanea fixes for optional #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,12 @@ template <typename T, typename U> | |
| concept optional_ge_rel = requires(const T& t, const U& u) { | ||
| { t >= u } -> std::convertible_to<bool>; | ||
| }; | ||
|
|
||
| struct from_function_t { | ||
| explicit from_function_t() = default; | ||
| }; | ||
|
|
||
| inline constexpr from_function_t from_function{}; | ||
| } // namespace detail | ||
|
|
||
| struct in_place_t { | ||
|
|
@@ -403,6 +409,13 @@ class optional { | |
| std::destroy_at(std::addressof(value_)); | ||
| engaged_ = false; | ||
| } | ||
|
|
||
| template <class U> | ||
| friend class optional; | ||
|
|
||
| template <class F, class Arg> | ||
| constexpr optional(detail::from_function_t, F&& f, Arg&& arg) | ||
| : value_(std::invoke(std::forward<F>(f), std::forward<Arg>(arg))), engaged_(true) {} | ||
| }; | ||
|
|
||
| class bad_optional_access : public std::exception { | ||
|
|
@@ -785,7 +798,7 @@ constexpr auto optional<T>::transform(F&& f) & { | |
| static_assert(!std::is_same_v<U, in_place_t>); | ||
| static_assert(!std::is_same_v<U, nullopt_t>); | ||
| static_assert(std::is_object_v<U> || std::is_reference_v<U>); /// References now allowed | ||
| return (has_value()) ? optional<U>{std::invoke(std::forward<F>(f), value_)} : optional<U>{}; | ||
| return (has_value()) ? optional<U>{detail::from_function, std::forward<F>(f), value_} : optional<U>{}; | ||
| } | ||
|
|
||
| template <class T> | ||
|
|
@@ -796,7 +809,7 @@ constexpr auto optional<T>::transform(F&& f) && { | |
| static_assert(!std::is_same_v<U, in_place_t>); | ||
| static_assert(!std::is_same_v<U, nullopt_t>); | ||
| static_assert(std::is_object_v<U> || std::is_reference_v<U>); /// References now allowed | ||
| return (has_value()) ? optional<U>{std::invoke(std::forward<F>(f), std::move(value_))} : optional<U>{}; | ||
| return (has_value()) ? optional<U>{detail::from_function, std::forward<F>(f), std::move(value_)} : optional<U>{}; | ||
| } | ||
|
|
||
| template <class T> | ||
|
|
@@ -807,7 +820,7 @@ constexpr auto optional<T>::transform(F&& f) const& { | |
| static_assert(!std::is_same_v<U, in_place_t>); | ||
| static_assert(!std::is_same_v<U, nullopt_t>); | ||
| static_assert(std::is_object_v<U> || std::is_reference_v<U>); /// References now allowed | ||
| return (has_value()) ? optional<U>{std::invoke(std::forward<F>(f), value_)} : optional<U>{}; | ||
| return (has_value()) ? optional<U>{detail::from_function, std::forward<F>(f), value_} : optional<U>{}; | ||
| } | ||
|
|
||
| template <class T> | ||
|
|
@@ -818,7 +831,7 @@ constexpr auto optional<T>::transform(F&& f) const&& { | |
| static_assert(!std::is_same_v<U, in_place_t>); | ||
| static_assert(!std::is_same_v<U, nullopt_t>); | ||
| static_assert(std::is_object_v<U> || std::is_reference_v<U>); /// References now allowed | ||
| return (has_value()) ? optional<U>{std::invoke(std::forward<F>(f), value_)} : optional<U>{}; | ||
| return (has_value()) ? optional<U>{detail::from_function, std::forward<F>(f), std::move(value_)} : optional<U>{}; | ||
| } | ||
|
|
||
| /// Calls `f` if the optional is empty | ||
|
|
@@ -1207,6 +1220,14 @@ class optional<T&> { | |
| T& r(std::forward<U>(u)); | ||
| value_ = std::addressof(r); | ||
| } | ||
|
|
||
| template <class U> | ||
| friend class optional; | ||
|
|
||
| template <class F, class Arg> | ||
| constexpr optional(detail::from_function_t, F&& f, Arg&& arg) { | ||
| convert_ref_init_val(std::invoke(std::forward<F>(f), std::forward<Arg>(arg))); | ||
| } | ||
| }; | ||
|
|
||
| // \rSec3[optionalref.ctor]{Constructors} | ||
|
|
@@ -1356,7 +1377,7 @@ constexpr optional<std::invoke_result_t<F, T&>> optional<T&>::transform(F&& f) c | |
| static_assert((std::is_object_v<U> && !std::is_array_v<U>) || std::is_lvalue_reference_v<U>, | ||
| "Result must be an non-array object or an lvalue reference"); | ||
| if (has_value()) { | ||
| return optional<U>{std::invoke(std::forward<F>(f), *value_)}; | ||
| return optional<U>{detail::from_function, std::forward<F>(f), *value_}; | ||
| } else { | ||
| return optional<U>{}; | ||
| } | ||
|
|
@@ -1368,7 +1389,7 @@ constexpr optional<T&> optional<T&>::or_else(F&& f) const { | |
| using U = std::invoke_result_t<F>; | ||
| static_assert(std::is_same_v<std::remove_cvref_t<U>, optional>, "Result must be an optional"); | ||
| if (has_value()) { | ||
| return *value_; | ||
| return *this; | ||
steve-downey marked this conversation as resolved.
Show resolved
Hide resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the LWG issue fodder. We wish to change from constructing a new optional over the deferenced val, to using the converting constructor for optional from optional, in order to avoid the deref of a possibly dangling pointer and instead copy the value of the pointer. This may not be safe on all platforms, but is safer and defined on many. |
||
| } else { | ||
| return std::forward<F>(f)(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |
|
|
||
| #include <beman/optional/optional.hpp> | ||
|
|
||
| #include <beman/optional/test_types.hpp> | ||
|
|
||
| #include <gtest/gtest.h> | ||
|
|
||
| constexpr int get_int(int) { return 42; } | ||
|
|
@@ -81,6 +83,24 @@ TEST(OptionalMonadicTest, Transform) { | |
| beman::optional::optional<const int&> o38r = o38.transform([](int& i) -> const int& { return i; }); | ||
| EXPECT_TRUE(o38r); | ||
| EXPECT_TRUE(*o38r == 42); | ||
|
|
||
| // transform and return a non-movable class | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are the tests I missed for transform and the reason for the from_function tag? Do existing implementations get this right? Do we have a spec bug, or is silence enough to require it to work?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, the spec says "[Note 1: There is no requirement that U is movable ([dcl.init.general]). Implementations support such non-movable payloads: https://gcc.godbolt.org/z/Wh6q7EGsj |
||
| using immovable = beman::optional::tests::immovable; | ||
| beman::optional::optional<int> o39 = 42; | ||
| auto o39r = o39.transform([](int) { return immovable(); }); | ||
| EXPECT_TRUE(o39r); | ||
|
|
||
| beman::optional::optional<int> o40 = 42; | ||
| auto o40r = std::move(o40).transform([](int) { return immovable(); }); | ||
| EXPECT_TRUE(o40r); | ||
|
|
||
| const beman::optional::optional<int> o41 = 42; | ||
| auto o41r = o41.transform([](int) { return immovable(); }); | ||
| EXPECT_TRUE(o41r); | ||
|
|
||
| const beman::optional::optional<int> o42 = 42; | ||
| auto o42r = std::move(o42).transform([](int) { return immovable(); }); | ||
| EXPECT_TRUE(o42r); | ||
| } | ||
|
|
||
| TEST(OptionalMonadicTest, TransformConstexpr) { | ||
|
|
@@ -323,3 +343,28 @@ TEST(OptionalMonadicTest, Constexpr_or_else) { | |
| constexpr auto test4 = *(std::move(o2).or_else([] { return beman::optional::make_optional(13); })) == 13; | ||
| EXPECT_TRUE(test4); | ||
| } | ||
|
|
||
| constexpr bool optional_or_else_do_not_dereference_impl() { | ||
| // create a dangling optional<T&> | ||
| int* ptr = new int(42); | ||
| beman::optional::optional<int&> iref = *ptr; | ||
| delete ptr; | ||
|
|
||
| if (!iref) | ||
| return false; | ||
|
|
||
| const auto or_else_fun = []() { return beman::optional::optional<int&>(); }; | ||
|
|
||
| // This must not dereference the pointer inside `iref` | ||
| beman::optional::optional<int&> iref2 = iref.or_else(or_else_fun); | ||
| if (!iref2) | ||
| return false; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| static_assert(optional_or_else_do_not_dereference_impl()); | ||
|
|
||
| TEST(OptionalMonadicTest, optional_or_else_do_not_derefence) { | ||
| EXPECT_TRUE(optional_or_else_do_not_dereference_impl()); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewing for creating LWG issues:
I believe this, which is necessary for https://eel.is/c++draft/optional.monadic#note-1, is already covered by the current wording.