diff --git a/docs/design_rationale.md b/docs/design_rationale.md index 6cc7841..bdb7918 100644 --- a/docs/design_rationale.md +++ b/docs/design_rationale.md @@ -218,12 +218,11 @@ zero changes to the library or to the box types. template requires beman::monadics::box::value_type> constexpr auto flatten(Box&& box) { - using Traits = beman::monadics::get_box_traits; - using InnerBox = typename Traits::value_type; - using ITraits = beman::monadics::get_box_traits; + using Traits = beman::monadics::get_box_traits; + using InnerBox = typename Traits::value_type; if (!Traits::has_value(box)) - return ITraits::make_error(Traits::error(std::forward(box))); + return beman::monadics::propagate_error(std::forward(box)); return Traits::value(std::forward(box)); } ``` diff --git a/include/beman/monadics/CMakeLists.txt b/include/beman/monadics/CMakeLists.txt index 50f3dde..9ece538 100644 --- a/include/beman/monadics/CMakeLists.txt +++ b/include/beman/monadics/CMakeLists.txt @@ -28,8 +28,8 @@ target_sources( detail/meta_rebind.hpp detail/or_else.hpp detail/pipe_adaptor.hpp - detail/rebox_value.hpp - detail/rebox_error.hpp + detail/propagate_value.hpp + detail/propagate_error.hpp detail/same_box.hpp detail/same_unqualified_as.hpp detail/transform.hpp diff --git a/include/beman/monadics/detail/and_then.hpp b/include/beman/monadics/detail/and_then.hpp index 95fe76b..0c3296f 100644 --- a/include/beman/monadics/detail/and_then.hpp +++ b/include/beman/monadics/detail/and_then.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -37,7 +37,7 @@ class and_then_t { using NewBox = decltype(invoke_with_value(std::forward(op).callable(key), std::forward(box))); - return rebox_error(std::forward(box)); + return propagate_error(std::forward(box)); } }; diff --git a/include/beman/monadics/detail/invoke_with_error.hpp b/include/beman/monadics/detail/invoke_with_error.hpp index 1e5a08e..e8436a8 100644 --- a/include/beman/monadics/detail/invoke_with_error.hpp +++ b/include/beman/monadics/detail/invoke_with_error.hpp @@ -11,13 +11,20 @@ namespace beman::monadics::detail { template> -[[nodiscard]] constexpr decltype(auto) invoke_with_error(Fn&& fn, Box&& box) noexcept - requires requires { - { Traits::error(std::forward(box)) }; - { std::forward(fn)(Traits::error(std::forward(box))) }; - } || requires { requires std::invocable; } +concept invocable_with_error = requires { + requires has_error_channel; + { std::declval()(Traits::error(std::declval())) }; +} || requires { + requires !has_error_channel; + requires std::invocable; +}; + +template +[[nodiscard]] constexpr decltype(auto) invoke_with_error(Fn&& fn, Box&& box) + requires invocable_with_error { - if constexpr (requires { Traits::error(std::forward(box)); }) { + using Traits = get_box_traits; + if constexpr (has_error_channel) { return std::forward(fn)(Traits::error(std::forward(box))); } else { return std::forward(fn)(); diff --git a/include/beman/monadics/detail/invoke_with_value.hpp b/include/beman/monadics/detail/invoke_with_value.hpp index a773242..d0c3665 100644 --- a/include/beman/monadics/detail/invoke_with_value.hpp +++ b/include/beman/monadics/detail/invoke_with_value.hpp @@ -10,15 +10,19 @@ namespace beman::monadics::detail { -template> -[[nodiscard]] constexpr decltype(auto) invoke_with_value(Fn&& fn, Box&& box) noexcept - requires requires { - { Traits::value(std::forward(box)) } -> std::same_as; - { fn() }; - } || requires { - { fn(Traits::value(std::forward(box))) }; - } +template> +concept invocable_with_value = requires { + requires std::is_void_v; + requires std::invocable; +} || requires { + { std::declval()(Traits::value(std::declval())) }; +}; + +template +[[nodiscard]] constexpr decltype(auto) invoke_with_value(Fn&& fn, Box&& box) + requires invocable_with_value { + using Traits = get_box_traits; if constexpr (std::is_void_v && std::invocable) { // should just invoke Traits::value(box); return std::forward(fn)(); diff --git a/include/beman/monadics/detail/or_else.hpp b/include/beman/monadics/detail/or_else.hpp index 035c3eb..4b55e6e 100644 --- a/include/beman/monadics/detail/or_else.hpp +++ b/include/beman/monadics/detail/or_else.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -36,7 +36,7 @@ class or_else_t { return invoke_with_error(std::forward(op).callable(key), std::forward(box)); } - return rebox_value(std::forward(box)); + return propagate_value(std::forward(box)); } }; diff --git a/include/beman/monadics/detail/propagate_error.hpp b/include/beman/monadics/detail/propagate_error.hpp new file mode 100644 index 0000000..4cbb466 --- /dev/null +++ b/include/beman/monadics/detail/propagate_error.hpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef BEMAN_MONADICS_DETAIL_PROPAGATE_ERROR_HPP +#define BEMAN_MONADICS_DETAIL_PROPAGATE_ERROR_HPP + +#include +#include + +#include + +namespace beman::monadics::detail { + +template +concept propagatable_error = same_box && (requires { + requires has_error_channel; + { get_box_traits::make_error(get_box_traits::error(std::declval())) }; +} || requires { + requires !has_error_channel; + { get_box_traits::make_error(get_box_traits::error()) }; +}); + +template + requires propagatable_error +[[nodiscard]] constexpr decltype(auto) propagate_error(Box&& box) { + using BoxTraits = get_box_traits; + using NewBoxTraits = get_box_traits; + + if constexpr (has_error_channel) { + return NewBoxTraits::make_error(BoxTraits::error(std::forward(box))); + } else { + return NewBoxTraits::make_error(BoxTraits::error()); + } +} + +} // namespace beman::monadics::detail + +#endif // BEMAN_MONADICS_DETAIL_PROPAGATE_ERROR_HPP diff --git a/include/beman/monadics/detail/rebox_value.hpp b/include/beman/monadics/detail/propagate_value.hpp similarity index 51% rename from include/beman/monadics/detail/rebox_value.hpp rename to include/beman/monadics/detail/propagate_value.hpp index 4bc80a1..4d82c06 100644 --- a/include/beman/monadics/detail/rebox_value.hpp +++ b/include/beman/monadics/detail/propagate_value.hpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef BEMAN_MONADICS_DETAIL_REBOX_VALUE_HPP -#define BEMAN_MONADICS_DETAIL_REBOX_VALUE_HPP +#ifndef BEMAN_MONADICS_DETAIL_PROPAGATE_VALUE_HPP +#define BEMAN_MONADICS_DETAIL_PROPAGATE_VALUE_HPP #include #include @@ -12,8 +12,16 @@ namespace beman::monadics::detail { template - requires same_box -[[nodiscard]] constexpr decltype(auto) rebox_value(Box&& box) noexcept { +concept propagatable_value = same_box && (requires { + requires std::is_void_v::value_type>; + { get_box_traits::make() }; +} || requires { + { get_box_traits::make(get_box_traits::value(std::declval())) }; +}); + +template + requires propagatable_value +[[nodiscard]] constexpr decltype(auto) propagate_value(Box&& box) { using BoxTraits = get_box_traits; using NewBoxTraits = get_box_traits; @@ -26,4 +34,4 @@ template } // namespace beman::monadics::detail -#endif // BEMAN_MONADICS_DETAIL_REBOX_VALUE_HPP +#endif // BEMAN_MONADICS_DETAIL_PROPAGATE_VALUE_HPP diff --git a/include/beman/monadics/detail/rebox_error.hpp b/include/beman/monadics/detail/rebox_error.hpp deleted file mode 100644 index bed5310..0000000 --- a/include/beman/monadics/detail/rebox_error.hpp +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#ifndef BEMAN_MONADICS_DETAIL_REBOX_ERROR_HPP -#define BEMAN_MONADICS_DETAIL_REBOX_ERROR_HPP - -#include -#include - -#include - -namespace beman::monadics::detail { - -template - requires same_box -[[nodiscard]] constexpr decltype(auto) rebox_error(Box&& box) noexcept { - using BoxTraits = get_box_traits; - using NewBoxTraits = get_box_traits; - - if constexpr (requires { BoxTraits::error(std::forward(box)); }) { - return NewBoxTraits::make_error(BoxTraits::error(std::forward(box))); - } else { - return NewBoxTraits::make_error(BoxTraits::error()); - } -} - -} // namespace beman::monadics::detail - -#endif // BEMAN_MONADICS_DETAIL_REBOX_ERROR_HPP diff --git a/include/beman/monadics/detail/transform.hpp b/include/beman/monadics/detail/transform.hpp index 18a5f64..cb932e5 100644 --- a/include/beman/monadics/detail/transform.hpp +++ b/include/beman/monadics/detail/transform.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -42,7 +42,7 @@ class transform_t { } } - return rebox_error(std::forward(box)); + return propagate_error(std::forward(box)); } }; diff --git a/include/beman/monadics/detail/transform_error.hpp b/include/beman/monadics/detail/transform_error.hpp index b8a4c8c..f1ad663 100644 --- a/include/beman/monadics/detail/transform_error.hpp +++ b/include/beman/monadics/detail/transform_error.hpp @@ -36,7 +36,7 @@ class transform_error_t { std::forward(box))); } - return rebox_value(std::forward(box)); + return propagate_value(std::forward(box)); // gcc11/12 internal crash with pipe_adaptor_t // return std::forward(box) | or_else([f = std::forward(op).callable(key)](auto&& e) { diff --git a/include/beman/monadics/monadics.hpp b/include/beman/monadics/monadics.hpp index 1d8c2b7..e8e5972 100644 --- a/include/beman/monadics/monadics.hpp +++ b/include/beman/monadics/monadics.hpp @@ -6,8 +6,12 @@ #include #include #include +#include +#include #include #include +#include +#include #include #include @@ -20,9 +24,13 @@ using detail::box; using detail::box_traits; using detail::get_box_traits; using detail::has_error_channel; +using detail::invoke_with_error; +using detail::invoke_with_value; using detail::or_else; using detail::or_elseable; using detail::pipe_adaptor; +using detail::propagate_error; +using detail::propagate_value; using detail::transform; using detail::transform_error; using detail::transform_errorable; diff --git a/tests/beman/monadics/detail/CMakeLists.txt b/tests/beman/monadics/detail/CMakeLists.txt index 00c45b0..96e8aff 100644 --- a/tests/beman/monadics/detail/CMakeLists.txt +++ b/tests/beman/monadics/detail/CMakeLists.txt @@ -23,8 +23,8 @@ set(tests meta_rebind_error meta_rebind pipe_adaptor - rebox_error - rebox_value + propagate_error + propagate_value same_box ) diff --git a/tests/beman/monadics/detail/has_box_traits.test.cpp b/tests/beman/monadics/detail/has_box_traits.test.cpp new file mode 100644 index 0000000..b330d55 --- /dev/null +++ b/tests/beman/monadics/detail/has_box_traits.test.cpp @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +#include + +namespace beman::monadics::detail::tests { + +namespace { + +template +struct box_traits {}; + +// ── has_valid_has_value_fn fixtures ────────────────────────────────────────── + +struct HasValueCorrect {}; +template<> +struct box_traits { + using value_type = int; + static bool has_value(const HasValueCorrect&) noexcept { return true; } +}; + +struct HasValueBadReturn {}; +struct NoBool {}; +template<> +struct box_traits { + using value_type = int; + static NoBool has_value(const HasValueBadReturn&) noexcept { return {}; } +}; + +struct HasValueNotNoexcept {}; +template<> +struct box_traits { + using value_type = int; + static bool has_value(const HasValueNotNoexcept&) { return true; } // missing noexcept +}; + +// ── has_valid_value_fn fixtures ─────────────────────────────────────────────── + +struct ValueCorrect { + int val{}; +}; +template<> +struct box_traits { + using value_type = int; + static int value(ValueCorrect&&) { return 0; } +}; + +struct ValueWrongReturn { + int val{}; +}; +template<> +struct box_traits { + using value_type = int; + static double value(ValueWrongReturn&&) { return 3.14; } +}; + +// ── has_valid_error_fn fixtures ─────────────────────────────────────────────── + +struct ErrorNullaryCorrect {}; +template<> +struct box_traits { + using error_type = int; + static int error() { return 0; } +}; + +struct ErrorUnaryCorrect { + int err{}; +}; +template<> +struct box_traits { + using error_type = int; + static int error(ErrorUnaryCorrect&& b) { return b.err; } +}; + +struct ErrorWrongReturn {}; +template<> +struct box_traits { + using error_type = int; + static double error(ErrorWrongReturn&&) { return 3.14; } +}; + +// ── has_valid_make_fn fixtures ──────────────────────────────────────────────── + +struct MakeCorrect { + int val{}; +}; +template<> +struct box_traits { + using value_type = int; + static MakeCorrect make(int v) { return {v}; } +}; + +struct OtherBox { + int val{}; +}; + +struct MakeWrongReturn { + int val{}; +}; +template<> +struct box_traits { + using value_type = int; + static OtherBox make(int v) { return {v}; } +}; + +// ── has_valid_make_error_fn fixtures ───────────────────────────────────────── + +struct MakeErrorCorrect { + int err{}; +}; +template<> +struct box_traits { + using error_type = int; + static MakeErrorCorrect make_error(int e) { return {e}; } +}; + +struct MakeErrorWrongReturn { + int err{}; +}; +template<> +struct box_traits { + using error_type = int; + static OtherBox make_error(int e) { return {e}; } +}; + +// ── rebind / rebind_error fixtures ──────────────────────────────────────────── + +template +struct ExpectedLike { + using value_type = T; + using error_type = E; + + template + using rebind = ExpectedLike; + + template + using rebind_error = ExpectedLike; +}; + +template +struct OptionalLike { + using value_type = T; +}; + +template +struct box_traits> { + using value_type = T; + using error_type = E; + + template + using rebind = ExpectedLike; + + template + using rebind_error = ExpectedLike; + + static bool has_value(const ExpectedLike&) noexcept { return true; } + static T value(ExpectedLike&&) { return {}; } + static E error(ExpectedLike&&) { return {}; } + static ExpectedLike make(T) { return {}; } + static ExpectedLike make_error(E) { return {}; } +}; + +// rebind goes to the wrong template family +template +struct BadRebindTraits { + using value_type = T; + using error_type = E; + + template + using rebind = OptionalLike; // wrong template family + + template + using rebind_error = ExpectedLike; +}; + +// rebind preserves the right shape but hard-codes the wrong error type +template +struct WrongErrorRebindTraits { + using value_type = T; + using error_type = E; + + template + using rebind = ExpectedLike; // error always becomes int + + template + using rebind_error = ExpectedLike; +}; + +// rebind_error goes to the wrong template family +template +struct BadRebindErrorTraits { + using value_type = T; + using error_type = E; + + template + using rebind = ExpectedLike; + + template + using rebind_error = OptionalLike; // wrong template family +}; + +// rebind_error preserves the right shape but hard-codes the wrong value type +template +struct WrongValueRebindErrorTraits { + using value_type = T; + using error_type = E; + + template + using rebind = ExpectedLike; + + template + using rebind_error = ExpectedLike; // value always becomes double +}; + +} // namespace + +// ── has_valid_has_value_fn ──────────────────────────────────────────────────── + +TEMPLATE_TEST_CASE_SIG("has_valid_has_value_fn", + "", + ((typename Box, bool Valid), Box, Valid), + (int, true), // no has_value -> passes + (HasValueCorrect, true), // correct signature + (HasValueBadReturn, false), // non-bool-convertible return + (HasValueNotNoexcept, false)) { + using Traits = box_traits; + if constexpr (Valid) { + STATIC_REQUIRE(has_valid_has_value_fn); + } else { + STATIC_REQUIRE_FALSE(has_valid_has_value_fn); + } +} + +// ── has_valid_value_fn ──────────────────────────────────────────────────────── + +TEMPLATE_TEST_CASE_SIG("has_valid_value_fn", + "", + ((typename Box, bool Valid), Box, Valid), + (int, true), // no value -> passes + (ValueCorrect, true), // correct return type + (ValueWrongReturn, false)) // returns double instead of int +{ + using Traits = box_traits; + if constexpr (Valid) { + STATIC_REQUIRE(has_valid_value_fn); + } else { + STATIC_REQUIRE_FALSE(has_valid_value_fn); + } +} + +// ── has_valid_error_fn ──────────────────────────────────────────────────────── + +TEMPLATE_TEST_CASE_SIG("has_valid_error_fn", + "", + ((typename Box, bool Valid), Box, Valid), + (int, true), // no error -> passes + (ErrorNullaryCorrect, true), // nullary, correct return + (ErrorUnaryCorrect, true), // unary, correct return + (ErrorWrongReturn, false)) // unary, wrong return type +{ + using Traits = box_traits; + if constexpr (Valid) { + STATIC_REQUIRE(has_valid_error_fn); + } else { + STATIC_REQUIRE_FALSE(has_valid_error_fn); + } +} + +// ── has_valid_make_fn ───────────────────────────────────────────────────────── + +TEMPLATE_TEST_CASE_SIG("has_valid_make_fn", + "", + ((typename Box, bool Valid), Box, Valid), + (int, true), // no make -> passes + (MakeCorrect, true), // returns Box + (MakeWrongReturn, false)) // returns OtherBox +{ + using Traits = box_traits; + if constexpr (Valid) { + STATIC_REQUIRE(has_valid_make_fn); + } else { + STATIC_REQUIRE_FALSE(has_valid_make_fn); + } +} + +// ── has_valid_make_error_fn ─────────────────────────────────────────────────── + +TEMPLATE_TEST_CASE_SIG("has_valid_make_error_fn", + "", + ((typename Box, bool Valid), Box, Valid), + (int, true), // no make_error -> passes + (MakeErrorCorrect, true), // returns Box + (MakeErrorWrongReturn, false)) // returns OtherBox +{ + using Traits = box_traits; + if constexpr (Valid) { + STATIC_REQUIRE(has_valid_make_error_fn); + } else { + STATIC_REQUIRE_FALSE(has_valid_make_error_fn); + } +} + +// ── has_valid_rebind ────────────────────────────────────────────────────────── + +using ExpectedIntDouble = ExpectedLike; + +TEST_CASE("has_valid_rebind - correct traits") { + STATIC_REQUIRE(has_valid_rebind>); +} + +TEST_CASE("has_valid_rebind - rebind returns wrong template family") { + STATIC_REQUIRE_FALSE(has_valid_rebind>); +} + +TEST_CASE("has_valid_rebind - rebind corrupts error type") { + STATIC_REQUIRE_FALSE(has_valid_rebind>); +} + +// ── has_valid_rebind_error ──────────────────────────────────────────────────── + +TEST_CASE("has_valid_rebind_error - correct traits") { + STATIC_REQUIRE(has_valid_rebind_error>); +} + +TEST_CASE("has_valid_rebind_error - rebind_error returns wrong template family") { + STATIC_REQUIRE_FALSE(has_valid_rebind_error>); +} + +TEST_CASE("has_valid_rebind_error - rebind_error corrupts value type") { + STATIC_REQUIRE_FALSE(has_valid_rebind_error>); +} + +// ── has_box_traits ──────────────────────────────────────────────────────────── + +TEST_CASE("has_box_traits - fully correct traits") { + STATIC_REQUIRE(has_box_traits>); +} + +TEST_CASE("has_box_traits - empty traits passes") { + STATIC_REQUIRE(has_box_traits>); +} + +TEST_CASE("has_box_traits - bad rebind fails") { + STATIC_REQUIRE_FALSE(has_box_traits>); +} + +TEST_CASE("has_box_traits - bad rebind_error fails") { + STATIC_REQUIRE_FALSE(has_box_traits>); +} + +} // namespace beman::monadics::detail::tests diff --git a/tests/beman/monadics/detail/rebox_error.test.cpp b/tests/beman/monadics/detail/propagate_error.test.cpp similarity index 88% rename from tests/beman/monadics/detail/rebox_error.test.cpp rename to tests/beman/monadics/detail/propagate_error.test.cpp index 2f556d6..641157d 100644 --- a/tests/beman/monadics/detail/rebox_error.test.cpp +++ b/tests/beman/monadics/detail/propagate_error.test.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#include +#include #include @@ -45,7 +45,7 @@ namespace beman::monadics::detail::tests { TEST_CASE("with-error-channel") { constexpr auto result = []() { Box src{1.0f}; - return rebox_error>(std::move(src)); + return propagate_error>(std::move(src)); }(); STATIC_REQUIRE(std::get<1>(result) == 1.0); @@ -54,7 +54,7 @@ TEST_CASE("with-error-channel") { TEST_CASE("without-error-channel") { constexpr auto result = []() { std::optional src{std::nullopt}; - return rebox_error>(std::move(src)); + return propagate_error>(std::move(src)); }(); STATIC_REQUIRE_FALSE(result.has_value()); diff --git a/tests/beman/monadics/detail/rebox_value.test.cpp b/tests/beman/monadics/detail/propagate_value.test.cpp similarity index 89% rename from tests/beman/monadics/detail/rebox_value.test.cpp rename to tests/beman/monadics/detail/propagate_value.test.cpp index 67955dc..7830fcf 100644 --- a/tests/beman/monadics/detail/rebox_value.test.cpp +++ b/tests/beman/monadics/detail/propagate_value.test.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#include +#include #include @@ -51,7 +51,7 @@ namespace beman::monadics::detail::tests { TEST_CASE("with-error-channel") { constexpr auto result = []() { Box src{1}; - return rebox_value>(std::move(src)); + return propagate_value>(std::move(src)); }(); STATIC_REQUIRE(std::get<0>(result) == 1.0); @@ -60,7 +60,7 @@ TEST_CASE("with-error-channel") { TEST_CASE("without-error-channel") { constexpr auto result = []() { std::optional src{10}; - return rebox_value>(std::move(src)); + return propagate_value>(std::move(src)); }(); STATIC_REQUIRE(result.value() == 10.0);