From 4ecc6ab86d7cf1e6e9d1561a58e622ffdcf52ba7 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Wed, 20 Aug 2025 09:31:52 -0600 Subject: [PATCH] :art: Make multi-arg `transform` work on `std::optional` Problem: - Sometimes we want to use `std::optional` (not just `stdx::optional`) as an applicative functor. Solution: - Make multi-arg `transform` work on `std::optional` as well as `stdx::optional`. --- include/stdx/optional.hpp | 23 ++++++++++++++++++++--- test/optional.cpp | 17 +++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/include/stdx/optional.hpp b/include/stdx/optional.hpp index 21034e3..0d7537e 100644 --- a/include/stdx/optional.hpp +++ b/include/stdx/optional.hpp @@ -318,21 +318,38 @@ template > class optional { template optional(T) -> optional; -template +constexpr bool optional_like = + stdx::is_specialization_of_v, optional> or + stdx::is_specialization_of_v, std::optional>; + +template , optional>)>> +auto convert_optional(Ts const &...) -> optional; +template , + std::optional>)>> +auto convert_optional(Ts const &...) -> std::optional; +} // namespace detail + +template )>> constexpr auto transform(F &&f, Ts &&...ts) { using func_t = stdx::remove_cvref_t; using R = std::invoke_result_t< func_t, forward_like_t::value_type>...>; + using O = decltype(detail::convert_optional(ts...)); if ((... and ts.has_value())) { - return optional{with_result_of{[&] { + return O{with_result_of{[&] { return std::forward(f)(std::forward(ts).value()...); }}}; } - return optional{}; + return O{}; } } // namespace v1 } // namespace stdx diff --git a/test/optional.cpp b/test/optional.cpp index d9d768a..09acdb7 100644 --- a/test/optional.cpp +++ b/test/optional.cpp @@ -334,6 +334,16 @@ TEST_CASE("transform (multi-arg)", "[optional]") { auto o3 = transform([](S &x, S &y) { return S{x.value + y.value}; }, o1, o2); CHECK(o3->value == 59); + STATIC_REQUIRE(std::is_same_v>); +} + +TEST_CASE("multi-arg transform works on std::optional", "[optional]") { + auto o1 = std::optional{17}; + auto o2 = std::optional{42}; + auto o3 = stdx::transform([](S &x, S &y) { return S{x.value + y.value}; }, + o1, o2); + CHECK(o3->value == 59); + STATIC_REQUIRE(std::is_same_v>); } namespace { @@ -427,6 +437,13 @@ TEST_CASE("transform (multi-arg nonmovable)", "[optional]") { CHECK(o3->value == 59); } +TEST_CASE("transform (non-default tombstone)", "[optional]") { + constexpr auto o1 = stdx::optional>{17}; + constexpr auto o2 = transform([](auto x) { return S{x}; }, o1); + STATIC_REQUIRE(o2); + STATIC_REQUIRE(o2->value == 17); +} + #if __cpp_nontype_template_args >= 201911L TEST_CASE("tombstone with non-structural value", "[optional]") { constexpr auto ts_value = CX_VALUE(std::string_view{});