Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions include/beman/optional/optional.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -403,6 +409,13 @@ class optional {
std::destroy_at(std::addressof(value_));
engaged_ = false;
}

Copy link
Member

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.

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 {
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand All @@ -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>
Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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>{};
}
Expand All @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The 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)();
}
Expand Down
45 changes: 45 additions & 0 deletions tests/beman/optional/optional_monadic.t.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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]).
— end note]" in https://eel.is/c++draft/optional#monadic-note-1

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) {
Expand Down Expand Up @@ -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());
}
21 changes: 21 additions & 0 deletions tests/beman/optional/optional_ref_monadic.t.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include <beman/optional/optional.hpp>

#include <beman/optional/test_types.hpp>

#include <gtest/gtest.h>

namespace {
Expand Down Expand Up @@ -89,6 +91,25 @@ TEST(OptionalRefMonadicTest, Transform) {
auto o38r = o38.transform([](int& i) -> const int& { return i; });
EXPECT_TRUE(o38r);
EXPECT_TRUE(*o38r == 42);

// transform and return a non-movable class
using immovable = beman::optional::tests::immovable;

beman::optional::optional<int&> o39 = fortytwo;
auto o39r = o39.transform([](int&) { return immovable(); });
EXPECT_TRUE(o39r);

beman::optional::optional<int&> o40 = fortytwo;
auto o40r = std::move(o40).transform([](int&) { return immovable(); });
EXPECT_TRUE(o40r);

const beman::optional::optional<int&> o41 = fortytwo;
auto o41r = o41.transform([](int&) { return immovable(); });
EXPECT_TRUE(o41r);

const beman::optional::optional<int&> o42 = fortytwo;
auto o42r = std::move(o42).transform([](int&) { return immovable(); });
EXPECT_TRUE(o42r);
}

TEST(OptionalRefMonadicTest, TransformConstexpr) {
Expand Down