From e8ed2d7076e23ff9e534ebd836743ef10fcf97ad Mon Sep 17 00:00:00 2001 From: Yiannis Papadopoulos Date: Fri, 31 Jul 2026 22:02:47 -0400 Subject: [PATCH 1/3] Refactor conditional and switch expressions to use map_result; remove redundant map_conditional_result --- include/deferred/conditional.hpp | 31 ++------------------ include/deferred/detail/map_result.hpp | 40 ++++++++++++++++++++++++++ include/deferred/switch.hpp | 37 +++--------------------- include/deferred/while.hpp | 15 ++-------- 4 files changed, 50 insertions(+), 73 deletions(-) create mode 100644 include/deferred/detail/map_result.hpp diff --git a/include/deferred/conditional.hpp b/include/deferred/conditional.hpp index 52e9aa1..1140282 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(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(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..6c9daeb --- /dev/null +++ b/include/deferred/detail/map_result.hpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2019-2026 Yiannis Papadopoulos +// SPDX-License-Identifier: MIT + +#ifndef DEFERRED_DETAIL_MAP_RESULT_HPP +#define DEFERRED_DETAIL_MAP_RESULT_HPP + +#include +#include +#include + +namespace deferred::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_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 deferred::detail + +#endif diff --git a/include/deferred/switch.hpp b/include/deferred/switch.hpp index 33e13bb..1234543 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 * @@ -230,18 +201,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(std::get(m_cases)()); } return choose_case(t); } else { - return detail::map_switch_result(std::get<0>(m_cases)()); + return detail::map_result(std::get<0>(m_cases)()); } } 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)) { From d73ca9f3fa727b63d7f1895f8123b9395d9bfc13 Mon Sep 17 00:00:00 2001 From: Yiannis Papadopoulos Date: Fri, 31 Jul 2026 22:11:56 -0400 Subject: [PATCH 2/3] Add append functionality to switch expressions and corresponding tests --- AGENTS.md | 1 + README.md | 1 + include/deferred/switch.hpp | 48 +++++++++++++++++++++++++++++++++++++ test/integration/switch.cpp | 22 +++++++++++++++++ test/unit/switch.cpp | 24 +++++++++++++++++++ 5 files changed, 96 insertions(+) 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/switch.hpp b/include/deferred/switch.hpp index 1234543..194af1f 100644 --- a/include/deferred/switch.hpp +++ b/include/deferred/switch.hpp @@ -192,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. @@ -296,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/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..6f49678 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,25 @@ 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); +} From f3a41218abfb609c837c2a616ade7cffb13cc3df Mon Sep 17 00:00:00 2001 From: Yiannis Papadopoulos Date: Fri, 31 Jul 2026 22:23:53 -0400 Subject: [PATCH 3/3] Refactor map_result to accept a function for evaluation; update switch and conditional usages --- include/deferred/conditional.hpp | 4 ++-- include/deferred/detail/map_result.hpp | 21 +++++++++++---------- include/deferred/switch.hpp | 4 ++-- test/unit/switch.cpp | 11 +++++++++++ 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/include/deferred/conditional.hpp b/include/deferred/conditional.hpp index 1140282..2850509 100644 --- a/include/deferred/conditional.hpp +++ b/include/deferred/conditional.hpp @@ -108,7 +108,7 @@ class conditional_expression } else { - return detail::map_result(evaluate(branch.then)); + return detail::map_result([&] { return evaluate(branch.then); }); } } return evaluate_impl(std::forward(self)); @@ -124,7 +124,7 @@ class conditional_expression } else { - return detail::map_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 index 6c9daeb..0a3b264 100644 --- a/include/deferred/detail/map_result.hpp +++ b/include/deferred/detail/map_result.hpp @@ -1,9 +1,10 @@ // SPDX-FileCopyrightText: 2019-2026 Yiannis Papadopoulos // SPDX-License-Identifier: MIT -#ifndef DEFERRED_DETAIL_MAP_RESULT_HPP -#define DEFERRED_DETAIL_MAP_RESULT_HPP +#ifndef DEFERRED_MAP_RESULT_HPP +#define DEFERRED_MAP_RESULT_HPP +#include #include #include #include @@ -13,25 +14,25 @@ namespace deferred::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. + * @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(T&& t) +template +constexpr decltype(auto) map_result(F&& f) { if constexpr (std::is_void_v) { - static_cast(t); + static_cast(std::invoke(std::forward(f))); } - else if constexpr (std::is_void_v) + else if constexpr (std::is_void_v>) { - static_cast(t); + std::invoke(std::forward(f)); return std::monostate{}; } else { - return std::forward(t); + return std::invoke(std::forward(f)); } } diff --git a/include/deferred/switch.hpp b/include/deferred/switch.hpp index 194af1f..46679b8 100644 --- a/include/deferred/switch.hpp +++ b/include/deferred/switch.hpp @@ -252,14 +252,14 @@ class switch_expression { if (std::get(m_cases).compare(t)) { - return detail::map_result(std::get(m_cases)()); + return detail::map_result([&] { return std::get(m_cases)(); }); } return choose_case(t); } else { - return detail::map_result(std::get<0>(m_cases)()); + return detail::map_result([&] { return std::get<0>(m_cases)(); }); } } diff --git a/test/unit/switch.cpp b/test/unit/switch.cpp index 6f49678..b201fce 100644 --- a/test/unit/switch.cpp +++ b/test/unit/switch.cpp @@ -118,3 +118,14 @@ TEST_CASE("append case to switch with move-only body", "[switch-append-move-only 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())); +}