Skip to content

Commit 4ecc6ab

Browse files
committed
🎨 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`.
1 parent ca9e67a commit 4ecc6ab

File tree

2 files changed

+37
-3
lines changed

2 files changed

+37
-3
lines changed

include/stdx/optional.hpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,21 +318,38 @@ template <typename T, typename TS = tombstone_traits<T>> class optional {
318318

319319
template <typename T> optional(T) -> optional<T>;
320320

321-
template <typename F, typename... Ts,
321+
namespace detail {
322+
template <typename T>
323+
constexpr bool optional_like =
324+
stdx::is_specialization_of_v<stdx::remove_cvref_t<T>, optional> or
325+
stdx::is_specialization_of_v<stdx::remove_cvref_t<T>, std::optional>;
326+
327+
template <typename R, typename... Ts,
322328
typename = std::enable_if_t<
323329
(... and stdx::is_specialization_of_v<stdx::remove_cvref_t<Ts>,
324330
optional>)>>
331+
auto convert_optional(Ts const &...) -> optional<R>;
332+
template <typename R, typename... Ts,
333+
typename = std::enable_if_t<
334+
(... and stdx::is_specialization_of_v<stdx::remove_cvref_t<Ts>,
335+
std::optional>)>>
336+
auto convert_optional(Ts const &...) -> std::optional<R>;
337+
} // namespace detail
338+
339+
template <typename F, typename... Ts,
340+
typename = std::enable_if_t<(... and detail::optional_like<Ts>)>>
325341
constexpr auto transform(F &&f, Ts &&...ts) {
326342
using func_t = stdx::remove_cvref_t<F>;
327343
using R = std::invoke_result_t<
328344
func_t,
329345
forward_like_t<Ts, typename stdx::remove_cvref_t<Ts>::value_type>...>;
346+
using O = decltype(detail::convert_optional<R>(ts...));
330347
if ((... and ts.has_value())) {
331-
return optional<R>{with_result_of{[&] {
348+
return O{with_result_of{[&] {
332349
return std::forward<F>(f)(std::forward<Ts>(ts).value()...);
333350
}}};
334351
}
335-
return optional<R>{};
352+
return O{};
336353
}
337354
} // namespace v1
338355
} // namespace stdx

test/optional.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,16 @@ TEST_CASE("transform (multi-arg)", "[optional]") {
334334
auto o3 =
335335
transform([](S &x, S &y) { return S{x.value + y.value}; }, o1, o2);
336336
CHECK(o3->value == 59);
337+
STATIC_REQUIRE(std::is_same_v<decltype(o3), stdx::optional<S>>);
338+
}
339+
340+
TEST_CASE("multi-arg transform works on std::optional", "[optional]") {
341+
auto o1 = std::optional<S>{17};
342+
auto o2 = std::optional<S>{42};
343+
auto o3 = stdx::transform([](S &x, S &y) { return S{x.value + y.value}; },
344+
o1, o2);
345+
CHECK(o3->value == 59);
346+
STATIC_REQUIRE(std::is_same_v<decltype(o3), std::optional<S>>);
337347
}
338348

339349
namespace {
@@ -427,6 +437,13 @@ TEST_CASE("transform (multi-arg nonmovable)", "[optional]") {
427437
CHECK(o3->value == 59);
428438
}
429439

440+
TEST_CASE("transform (non-default tombstone)", "[optional]") {
441+
constexpr auto o1 = stdx::optional<int, stdx::tombstone_value<-1>>{17};
442+
constexpr auto o2 = transform([](auto x) { return S{x}; }, o1);
443+
STATIC_REQUIRE(o2);
444+
STATIC_REQUIRE(o2->value == 17);
445+
}
446+
430447
#if __cpp_nontype_template_args >= 201911L
431448
TEST_CASE("tombstone with non-structural value", "[optional]") {
432449
constexpr auto ts_value = CX_VALUE(std::string_view{});

0 commit comments

Comments
 (0)