diff --git a/AGENTS.md b/AGENTS.md index 801eaeb..925a5a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,7 @@ This project is a C++23 header-only library for creating deferred evaluation exp ## Project Overview - **Purpose**: Provides a mechanism to define expressions (using constants, variables, and operators) that are evaluated lazily at a later point. +- **Switch expressions**: Existing switch expressions can be expanded with `append()`. - **Main Technologies**: - **Language**: C++23 - **Build System**: CMake (3.28.1+) diff --git a/README.md b/README.md index 6670fc0..fdd4b1b 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Library for creating deferred evaluation expressions in C++23. ``deferred`` provides: - functions to declare constants and variables, - functions to create deferred evaluation expressions from functions, +- expandable deferred switch expressions, - ``deferred``-enabled commonly used operators. Requirements diff --git a/include/deferred/conditional.hpp b/include/deferred/conditional.hpp index 52e9aa1..2850509 100644 --- a/include/deferred/conditional.hpp +++ b/include/deferred/conditional.hpp @@ -8,8 +8,8 @@ #include #include #include -#include +#include "detail/map_result.hpp" #include "evaluate.hpp" #include "expression.hpp" #include "type_traits/homogenized_type.hpp" @@ -18,31 +18,6 @@ namespace deferred { namespace detail { -/** - * @brief Maps the result of an evaluation to the target result type. - * @tparam Result Target result type. - * @tparam T Type of the evaluated expression. - * @param t Evaluated expression. - * @return Mapped result. - */ -template -constexpr decltype(auto) map_conditional_result(T&& t) -{ - if constexpr (std::is_void_v) - { - static_cast(t); - } - else if constexpr (std::is_void_v) - { - static_cast(t); - return std::monostate{}; - } - else - { - return std::forward(t); - } -} - /** * @brief Tag for conditional expressions without an @c else branch. */ @@ -133,7 +108,7 @@ class conditional_expression } else { - return detail::map_conditional_result(evaluate(branch.then)); + return detail::map_result([&] { return evaluate(branch.then); }); } } return evaluate_impl(std::forward(self)); @@ -149,7 +124,7 @@ class conditional_expression } else { - return detail::map_conditional_result(evaluate(self.m_else)); + return detail::map_result([&] { return evaluate(self.m_else); }); } } } diff --git a/include/deferred/detail/map_result.hpp b/include/deferred/detail/map_result.hpp new file mode 100644 index 0000000..0a3b264 --- /dev/null +++ b/include/deferred/detail/map_result.hpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2019-2026 Yiannis Papadopoulos +// SPDX-License-Identifier: MIT + +#ifndef DEFERRED_MAP_RESULT_HPP +#define DEFERRED_MAP_RESULT_HPP + +#include +#include +#include +#include + +namespace deferred::detail { + +/** + * @brief Maps the result of an evaluation to the target result type. + * @tparam Result Target result type. + * @tparam F Type of the function that evaluates the expression. + * @param f Function that evaluates the expression. + * @return Mapped result. + */ +template +constexpr decltype(auto) map_result(F&& f) +{ + if constexpr (std::is_void_v) + { + static_cast(std::invoke(std::forward(f))); + } + else if constexpr (std::is_void_v>) + { + std::invoke(std::forward(f)); + return std::monostate{}; + } + else + { + return std::invoke(std::forward(f)); + } +} + +} // namespace deferred::detail + +#endif diff --git a/include/deferred/switch.hpp b/include/deferred/switch.hpp index 33e13bb..46679b8 100644 --- a/include/deferred/switch.hpp +++ b/include/deferred/switch.hpp @@ -7,8 +7,8 @@ #include #include #include -#include +#include "detail/map_result.hpp" #include "evaluate.hpp" #include "expression.hpp" #include "type_traits/homogenized_type.hpp" @@ -149,35 +149,6 @@ concept CaseExpression = requires(std::remove_cvref_t t) { }(t); }; -namespace detail { - -/** - * @brief Maps the result of an evaluation to the target result type. - * @tparam Result Target result type. - * @tparam T Type of the evaluated expression. - * @param t Evaluated expression. - * @return Mapped result. - */ -template -constexpr decltype(auto) map_switch_result(T&& t) -{ - if constexpr (std::is_void_v) - { - static_cast(t); - } - else if constexpr (std::is_void_v) - { - static_cast(t); - return std::monostate{}; - } - else - { - return std::forward(t); - } -} - -} // namespace detail - /** * @brief Deferred switch * @@ -221,6 +192,53 @@ class switch_expression m_cases(std::forward(df), std::forward(cs)...) { } + /** + * @brief Appends cases to the switch expression. + * + * Existing cases are evaluated before the appended cases. This overload copies + * owned expressions and preserves referenced expressions. + * + * @tparam NewCases Types of the case expressions to append. + * @param new_cases Case expressions to append. + * @return A new switch expression containing the appended cases. + */ + template + requires(sizeof...(NewCases) > 0 && (deferred::CaseExpression && ...)) + [[nodiscard]] constexpr auto append(NewCases&&... new_cases) const& + { + using expanded_expression = switch_expression...>; + return std::apply( + [&](auto const& df, auto const&... cases) { + return expanded_expression(m_condition, df, cases..., std::forward(new_cases)...); + }, + m_cases); + } + + /** + * @brief Appends cases by moving owned expressions from this switch expression. + * @copydetails append + */ + template + requires(sizeof...(NewCases) > 0 && (deferred::CaseExpression && ...)) + [[nodiscard]] constexpr auto append(NewCases&&... new_cases) && + { + using expanded_expression = switch_expression...>; + return std::apply( + [&](auto&& df, auto&&... cases) { + return expanded_expression(std::forward(m_condition), + std::forward(df), + std::forward(cases)..., + std::forward(new_cases)...); + }, + std::move(m_cases)); + } + private: /** * @brief Traverses the cases until one matches. @@ -230,18 +248,18 @@ class switch_expression template [[nodiscard]] constexpr result_type choose_case(T const& t) const { - if constexpr (I < std::tuple_size::value) + if constexpr (I < std::tuple_size_v) { if (std::get(m_cases).compare(t)) { - return detail::map_switch_result(std::get(m_cases)()); + return detail::map_result([&] { return std::get(m_cases)(); }); } return choose_case(t); } else { - return detail::map_switch_result(std::get<0>(m_cases)()); + return detail::map_result([&] { return std::get<0>(m_cases)(); }); } } @@ -325,6 +343,7 @@ template * [] { return "10"; }), * case_([] { return foo(); }, * [] { return "result of foo"; })); + * auto expanded = ex.append(case_(11, [] { return "11"; })); * @endcode * * @tparam ConditionExpression Type of the condition expression. diff --git a/include/deferred/while.hpp b/include/deferred/while.hpp index ff8d543..a92629e 100644 --- a/include/deferred/while.hpp +++ b/include/deferred/while.hpp @@ -45,7 +45,7 @@ class while_expression { } /// @brief Evaluates the while loop. - constexpr void operator()() const& + constexpr void operator()() const { while (evaluate(m_condition)) { @@ -53,17 +53,8 @@ class while_expression } } - /// @copydoc while_expression::operator()() const& - constexpr void operator()() & - { - while (evaluate(m_condition)) - { - evaluate(m_body); - } - } - - /// @copydoc while_expression::operator()() const& - constexpr void operator()() && + /// @copydoc while_expression::operator()() const + constexpr void operator()() { while (evaluate(m_condition)) { diff --git a/test/integration/switch.cpp b/test/integration/switch.cpp index ae3e705..7d8f4c5 100644 --- a/test/integration/switch.cpp +++ b/test/integration/switch.cpp @@ -76,3 +76,25 @@ TEST_CASE("switch with expressions", "[switch-expressions]") label = 5; CHECK(ex() == 2); } + +TEST_CASE("append case to switch", "[switch-append]") +{ + auto var = deferred::variable(); + auto ex = deferred::switch_(var, + deferred::default_("unknown"), + deferred::case_(10, [] { return "10"; }), + deferred::case_(12, [] { return "12"; })); + auto const& source = ex; + auto expanded = source.append(deferred::case_(10, [] { return "new 10"; }), + deferred::case_(11, [] { return "11"; })); + + var = 10; + CHECK(std::strcmp(expanded(), "10") == 0); + + var = 11; + CHECK(std::strcmp(ex(), "unknown") == 0); + CHECK(std::strcmp(expanded(), "11") == 0); + + var = 13; + CHECK(std::strcmp(expanded(), "unknown") == 0); +} diff --git a/test/unit/switch.cpp b/test/unit/switch.cpp index a814d51..b201fce 100644 --- a/test/unit/switch.cpp +++ b/test/unit/switch.cpp @@ -3,6 +3,8 @@ #include +#include + #include "deferred/switch.hpp" #include "deferred/type_traits/is_constant_expression.hpp" @@ -94,3 +96,36 @@ TEST_CASE("switch with heterogeneous types", "[switch-variant]") auto res2 = ex2(); CHECK(std::get(res2) == std::string("unknown")); } + +TEST_CASE("append case with heterogeneous type", "[switch-append-variant]") +{ + auto ex = deferred::switch_(2, deferred::default_("unknown"), deferred::case_(1, 42)); + auto expanded = std::move(ex).append(deferred::case_(2, 2.5)); + + using result_type = decltype(expanded()); + static_assert(std::is_same_v>); + + CHECK(std::get(expanded()) == 2.5); +} + +TEST_CASE("append case to switch with move-only body", "[switch-append-move-only]") +{ + auto ex = + deferred::switch_(1, + deferred::default_(0), + deferred::case_(1, [value = std::make_unique(42)] { return *value; })); + auto expanded = std::move(ex).append(deferred::case_(2, 2)); + + CHECK(expanded() == 42); +} + +TEST_CASE("append case with void result", "[switch-append-void]") +{ + auto ex = deferred::switch_(2, deferred::default_(0), deferred::case_(1, 1)); + auto expanded = std::move(ex).append(deferred::case_(2, [] { })); + + using result_type = decltype(expanded()); + static_assert(std::is_same_v>); + + CHECK(std::holds_alternative(expanded())); +}