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
4 changes: 2 additions & 2 deletions cmake/Findgimo-catch2.cmake
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright Dominic (DNKpp) Koepke 2025 - 2026.
# Copyright Dominic (DNKpp) Koepke 2025-2026.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# https://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -8,7 +8,7 @@ include(Gimo-get_cpm)
# fixes reporting in clion
set(CATCH_CONFIG_CONSOLE_WIDTH 800 CACHE STRING "")

CPMAddPackage("gh:catchorg/Catch2@3.13.0")
CPMAddPackage("gh:catchorg/Catch2@3.15.0")

if (Catch2_ADDED)
include("${Catch2_SOURCE_DIR}/extras/Catch.cmake")
Expand Down
47 changes: 33 additions & 14 deletions include/gimo/algorithm/OrElse.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright Dominic (DNKpp) Koepke 2025.
// Copyright Dominic (DNKpp) Koepke 2025-2026.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -21,31 +21,31 @@
namespace gimo::detail::or_else
{
template <typename Nullable, typename Action>
consteval Nullable* print_diagnostics()
consteval Nullable& print_diagnostics()
{
if constexpr (!std::is_invocable_v<Action>)
{
static_assert(always_false_v<Nullable>, "The or_else algorithm requires an action invocable without any arguments.");
}
else if constexpr (!std::same_as<Nullable, std::invoke_result_t<Action>>)
else if constexpr (!std::same_as<Nullable, std::invoke_result_t<Action>> && !std::is_void_v<std::invoke_result_t<Action>>)
{
static_assert(always_false_v<Nullable>, "The or_else algorithm requires an action returning the same nullable type.");
static_assert(always_false_v<Nullable>, "The or_else algorithm requires an action returning the same nullable type or void.");
}

return nullptr;
return std::declval<Nullable&>();
}

template <typename Action, nullable Nullable>
[[nodiscard]]
constexpr std::remove_cvref_t<Nullable> on_value([[maybe_unused]] Action&& action, Nullable&& opt)
constexpr std::remove_cvref_t<Nullable> on_value(Action&& /*action*/, Nullable&& opt)
{
return std::forward<Nullable>(opt);
}

template <typename Action, nullable Nullable, typename Next, typename... Steps>
[[nodiscard]]
constexpr auto on_value(
[[maybe_unused]] Action&& action,
Action&& /*action*/,
Nullable&& opt,
Next&& next,
Steps&&... steps)
Expand All @@ -57,9 +57,17 @@ namespace gimo::detail::or_else

template <typename Action, nullable Nullable>
[[nodiscard]]
constexpr std::remove_cvref_t<Nullable> on_null(Action&& action, [[maybe_unused]] Nullable&& opt)
constexpr std::remove_cvref_t<Nullable> on_null(Action&& action, Nullable&& /*opt*/)
{
return std::invoke(std::forward<Action>(action));
if constexpr (std::is_void_v<std::invoke_result_t<Action>>)
{
std::invoke(std::forward<Action>(action));
return null_v<Nullable>;
}
else
{
return std::invoke(std::forward<Action>(action));
}
}

template <nullable Nullable, typename Action, typename Next, typename... Steps>
Expand All @@ -72,13 +80,24 @@ namespace gimo::detail::or_else
std::forward<Steps>(steps)...);
}

template <nullable Nullable, typename Action, typename Next, typename... Steps>
requires std::is_void_v<std::invoke_result_t<Action>>
[[nodiscard]]
constexpr auto on_null(Action&& action, Nullable&& opt, Next&& next, Steps&&... steps)
{
return std::forward<Next>(next).on_null(
or_else::on_null(std::forward<Action>(action), std::forward<Nullable>(opt)),
std::forward<Steps>(steps)...);
}

struct traits
{
template <nullable Nullable, typename Action>
static constexpr bool is_applicable_on = requires {
requires std::same_as<
std::remove_cvref_t<Nullable>,
std::remove_cvref_t<std::invoke_result_t<Action>>>;
std::remove_cvref_t<Nullable>,
std::remove_cvref_t<std::invoke_result_t<Action>>>
|| std::is_void_v<std::invoke_result_t<Action>>;
};

template <typename Action, nullable Nullable, typename... Steps>
Expand All @@ -94,7 +113,7 @@ namespace gimo::detail::or_else
}
else
{
return *or_else::print_diagnostics<Nullable, Action>();
return or_else::print_diagnostics<Nullable, Action>();
}
}

Expand All @@ -111,7 +130,7 @@ namespace gimo::detail::or_else
}
else
{
return *or_else::print_diagnostics<Nullable, Action>();
return or_else::print_diagnostics<Nullable, Action>();
}
}
};
Expand All @@ -133,7 +152,7 @@ namespace gimo
* \return A Pipeline step containing the `or_else` algorithm.
* \details
* - **On Value**: Propagates the value state immediately (i.e., `action` is not executed).
* - **On Null**: Invokes the `action`. The `action` **must** return a `nullable` type.
* - **On Null**: Invokes the `action`. The `action` must either return the **exact same** `nullable` type or `void`.
* \see https://en.wikipedia.org/wiki/Monad_(functional_programming)
*/
template <typename Action>
Expand Down
4 changes: 2 additions & 2 deletions test/compile-errors/or_else/action-return-mismatch.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright Dominic (DNKpp) Koepke 2025.
// Copyright Dominic (DNKpp) Koepke 2025-2026.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -8,7 +8,7 @@

/*
<begin-expected-compile-error>
The or_else algorithm requires an action returning the same nullable type\.
The or_else algorithm requires an action returning the same nullable type or void\.
<end-expected-compile-error>
*/

Expand Down
21 changes: 20 additions & 1 deletion test/unit-tests/algorithm/OrElse.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright Dominic (DNKpp) Koepke 2025.
// Copyright Dominic (DNKpp) Koepke 2025-2026.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -180,3 +180,22 @@ TEMPLATE_LIST_TEST_CASE(
and finally::returns(1337);
CHECK(1337 == pipeline.apply(std::optional<int>{}));
}

TEST_CASE(
"gimo::or_else accepts actions returning void.",
"[algorithm]")
{
mimicpp::Mock<void()> action{};
auto const pipeline = or_else(std::ref(action));

SECTION("When a value is contained, the action is not invoked.")
{
CHECK(1337 == pipeline.apply(std::optional{1337}));
}

SECTION("When no value is contained, the action is invoked and null is returned.")
{
SCOPED_EXP action.expect_call();
CHECK(!pipeline.apply(std::optional<int>{}));
}
}
Loading