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
23 changes: 23 additions & 0 deletions docs/optional.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ tombstone value. At first thought,
https://en.cppreference.com/w/cpp/numeric/math/isnan[NaN] is the obvious
tombstone, but NaNs never compare equal to anything, not even themselves.

For tuple-like types, if all of their component parts provide tombstone values,
a tombstone value for them is synthesized:

[source,cpp]
----
// S has a tombstone value, therefore so does std::tuple<S, S>
auto o = stdx::optional{std::tuple{S{42}, S{17}}};
----

=== Multi-argument `transform`

`stdx::optional` provides one extra feature over `std::optional`: the ability to
Expand All @@ -112,3 +121,17 @@ auto opt_sum = transform(
This flavor of `transform` returns the result of the function only if all of its
`stdx::optional` arguments are engaged. If any one is not, a disengaged
`stdx::optional` is returned.

=== Unpacking `transform` and `and_then`

When the contained `value_type` supports the tuple protocol with `apply`,
`transform` (or `and_then`) can unpack it to pass arguments to a function:

[source,cpp]
----
auto opt1 = stdx::optional{std::tuple{S{42}, S{17}}};
auto opt_sum = transform(
[](S const &x, S const &y) { return S{x.value + y.value}; },
opt1);
// result is stdx::optional{S{59}}
----
118 changes: 86 additions & 32 deletions include/stdx/optional.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,37 @@ template <auto V> struct tombstone_value {
}
};

namespace optional_detail {
template <typename Func, typename Arg, typename = void> struct unwrap_invoker {
template <typename F, typename A>
constexpr static auto invoke(F &&f, A &&a) {
return [&] { return std::forward<F>(f)(std::forward<A>(a)); };
}
};

template <typename Func, template <typename...> typename L, typename... Ts>
struct unwrap_invoker<Func, L<Ts...>,
std::void_t<decltype(apply(std::declval<Func>(),
std::declval<L<Ts...>>()))>> {
template <typename F, typename A>
constexpr static auto invoke(F &&f, A &&a) {
return [&] { return apply(std::forward<F>(f), std::forward<A>(a)); };
}
};

template <typename F, typename Arg>
constexpr auto unwrap_invoke(F &&f, Arg &&arg) {
return unwrap_invoker<stdx::remove_cvref_t<F>,
stdx::remove_cvref_t<Arg>>::invoke(std::forward<F>(f),
std::forward<Arg>(
arg));
}

template <typename F, typename Arg>
using unwrap_invoke_result_t =
decltype(unwrap_invoke(std::declval<F>(), std::declval<Arg>())());
} // namespace optional_detail

template <typename T, typename TS = tombstone_traits<T>> class optional {
static_assert(not std::is_integral_v<T> or
not stdx::is_specialization_of_v<TS, tombstone_traits>,
Expand Down Expand Up @@ -169,32 +200,34 @@ template <typename T, typename TS = tombstone_traits<T>> class optional {
}

template <typename F> constexpr auto transform(F &&f) & {
using func_t = stdx::remove_cvref_t<F>;
using U = std::invoke_result_t<func_t, value_type &>;
return *this ? optional<U>{with_result_of{
[&] { return std::forward<F>(f)(val); }}}
: optional<U>{};
using U = optional_detail::unwrap_invoke_result_t<F, value_type &>;
return *this
? optional<U>{with_result_of{optional_detail::unwrap_invoke(
std::forward<F>(f), val)}}
: optional<U>{};
}
template <typename F> constexpr auto transform(F &&f) const & {
using func_t = stdx::remove_cvref_t<F>;
using U = std::invoke_result_t<func_t, value_type const &>;
return *this ? optional<U>{with_result_of{
[&] { return std::forward<F>(f)(val); }}}
: optional<U>{};
using U =
optional_detail::unwrap_invoke_result_t<F, value_type const &>;
return *this
? optional<U>{with_result_of{optional_detail::unwrap_invoke(
std::forward<F>(f), val)}}
: optional<U>{};
}
template <typename F> constexpr auto transform(F &&f) && {
using func_t = stdx::remove_cvref_t<F>;
using U = std::invoke_result_t<func_t, value_type &&>;
return *this ? optional<U>{with_result_of{
[&] { return std::forward<F>(f)(std::move(val)); }}}
: optional<U>{};
using U = optional_detail::unwrap_invoke_result_t<F, value_type &&>;
return *this
? optional<U>{with_result_of{optional_detail::unwrap_invoke(
std::forward<F>(f), std::move(val))}}
: optional<U>{};
}
template <typename F> constexpr auto transform(F &&f) const && {
using func_t = stdx::remove_cvref_t<F>;
using U = std::invoke_result_t<func_t, value_type const &&>;
return *this ? optional<U>{with_result_of{
[&] { return std::forward<F>(f)(std::move(val)); }}}
: optional<U>{};
using U =
optional_detail::unwrap_invoke_result_t<F, value_type const &&>;
return *this
? optional<U>{with_result_of{optional_detail::unwrap_invoke(
std::forward<F>(f), std::move(val))}}
: optional<U>{};
}

template <typename F> constexpr auto or_else(F &&f) const & -> optional {
Expand All @@ -205,24 +238,28 @@ template <typename T, typename TS = tombstone_traits<T>> class optional {
}

template <typename F> constexpr auto and_then(F &&f) & {
using func_t = stdx::remove_cvref_t<F>;
using U = std::invoke_result_t<func_t, value_type &>;
return *this ? std::forward<F>(f)(val) : U{};
using U = optional_detail::unwrap_invoke_result_t<F, value_type &>;
return *this ? optional_detail::unwrap_invoke(std::forward<F>(f), val)()
: U{};
}
template <typename F> constexpr auto and_then(F &&f) const & {
using func_t = stdx::remove_cvref_t<F>;
using U = std::invoke_result_t<func_t, value_type const &>;
return *this ? std::forward<F>(f)(val) : U{};
using U =
optional_detail::unwrap_invoke_result_t<F, value_type const &>;
return *this ? optional_detail::unwrap_invoke(std::forward<F>(f), val)()
: U{};
}
template <typename F> constexpr auto and_then(F &&f) && {
using func_t = stdx::remove_cvref_t<F>;
using U = std::invoke_result_t<func_t, value_type &&>;
return *this ? std::forward<F>(f)(std::move(val)) : U{};
using U = optional_detail::unwrap_invoke_result_t<F, value_type &&>;
return *this ? optional_detail::unwrap_invoke(std::forward<F>(f),
std::move(val))()
: U{};
}
template <typename F> constexpr auto and_then(F &&f) const && {
using func_t = stdx::remove_cvref_t<F>;
using U = std::invoke_result_t<func_t, value_type &&>;
return *this ? std::forward<F>(f)(std::move(val)) : U{};
using U =
optional_detail::unwrap_invoke_result_t<F, value_type const &&>;
return *this ? optional_detail::unwrap_invoke(std::forward<F>(f),
std::move(val))()
: U{};
}

private:
Expand Down Expand Up @@ -260,6 +297,23 @@ template <typename T, typename TS = tombstone_traits<T>> class optional {
-> bool {
return not(lhs < rhs);
}

template <typename F>
[[nodiscard]] friend constexpr auto operator|(optional const &lhs, F &&f) {
return lhs.and_then(std::forward<F>(f));
}
template <typename F>
[[nodiscard]] friend constexpr auto operator|(optional &lhs, F &&f) {
return lhs.and_then(std::forward<F>(f));
}
template <typename F>
[[nodiscard]] friend constexpr auto operator|(optional &&lhs, F &&f) {
return std::move(lhs).and_then(std::forward<F>(f));
}
template <typename F>
[[nodiscard]] friend constexpr auto operator|(optional const &&lhs, F &&f) {
return std::move(lhs).and_then(std::forward<F>(f));
}
};

template <typename T> optional(T) -> optional<T>;
Expand Down
39 changes: 39 additions & 0 deletions test/optional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <cstdint>
#include <optional>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>

Expand Down Expand Up @@ -464,3 +465,41 @@ TEST_CASE("tombstone traits for product types come from components",
STATIC_REQUIRE(*o == std::tuple{E{0xffu}, S{-1},
std::numeric_limits<float>::infinity()});
}

TEST_CASE("transform unpacks tuples if necessary", "[optional]") {
constexpr auto o1 = stdx::optional{std::tuple{S{42}, S{17}}};
constexpr auto o2 =
o1.transform([](auto s1, auto s2) { return S{s1.value + s2.value}; });
STATIC_REQUIRE(o2->value == 59);
}

TEST_CASE("transform unpacks tuple-protocol types if necessary", "[optional]") {
constexpr auto o1 = stdx::optional{std::pair{S{42}, S{17}}};
constexpr auto o2 =
o1.transform([](auto s1, auto s2) { return S{s1.value + s2.value}; });
STATIC_REQUIRE(o2->value == 59);
}

TEST_CASE("and_then unpacks tuples if necessary", "[optional]") {
constexpr auto o1 = stdx::optional{std::tuple{S{42}, S{17}}};
constexpr auto o2 = o1.and_then([](auto s1, auto s2) {
return stdx::optional{S{s1.value + s2.value}};
});
STATIC_REQUIRE(o2->value == 59);
}

TEST_CASE("and_then unpacks tuple-protocol types if necessary", "[optional]") {
constexpr auto o1 = stdx::optional{std::pair{S{42}, S{17}}};
constexpr auto o2 = o1.and_then([](auto s1, auto s2) {
return stdx::optional{S{s1.value + s2.value}};
});
STATIC_REQUIRE(o2->value == 59);
}

TEST_CASE("and_then is pipeable", "[optional]") {
constexpr auto const o1 = stdx::optional{std::tuple{S{42}, S{17}}};
constexpr auto const o2 = o1 | [](auto s1, auto s2) {
return stdx::optional{S{s1.value + s2.value}};
};
STATIC_REQUIRE(o2->value == 59);
}