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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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+)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 3 additions & 28 deletions include/deferred/conditional.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>

#include "detail/map_result.hpp"
#include "evaluate.hpp"
#include "expression.hpp"
#include "type_traits/homogenized_type.hpp"
Expand All @@ -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<typename Result, typename T>
constexpr decltype(auto) map_conditional_result(T&& t)
{
if constexpr (std::is_void_v<Result>)
{
static_cast<void>(t);
}
else if constexpr (std::is_void_v<T>)
{
static_cast<void>(t);
return std::monostate{};
}
else
{
return std::forward<T>(t);
}
}

/**
* @brief Tag for conditional expressions without an @c else branch.
*/
Expand Down Expand Up @@ -133,7 +108,7 @@ class conditional_expression
}
else
{
return detail::map_conditional_result<base_result_type>(evaluate(branch.then));
return detail::map_result<base_result_type>([&] { return evaluate(branch.then); });
}
}
return evaluate_impl<I + 1>(std::forward<Self>(self));
Expand All @@ -149,7 +124,7 @@ class conditional_expression
}
else
{
return detail::map_conditional_result<result_type>(evaluate(self.m_else));
return detail::map_result<result_type>([&] { return evaluate(self.m_else); });
}
}
}
Expand Down
41 changes: 41 additions & 0 deletions include/deferred/detail/map_result.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2019-2026 Yiannis Papadopoulos <giannis.papadopoulos@gmail.com>
// SPDX-License-Identifier: MIT

#ifndef DEFERRED_MAP_RESULT_HPP
#define DEFERRED_MAP_RESULT_HPP

#include <functional>
#include <type_traits>
#include <utility>
#include <variant>

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<typename Result, typename F>
constexpr decltype(auto) map_result(F&& f)
{
if constexpr (std::is_void_v<Result>)
{
static_cast<void>(std::invoke(std::forward<F>(f)));
}
else if constexpr (std::is_void_v<std::invoke_result_t<F>>)
{
std::invoke(std::forward<F>(f));
return std::monostate{};
}
else
{
return std::invoke(std::forward<F>(f));
}
}

} // namespace deferred::detail

#endif
85 changes: 52 additions & 33 deletions include/deferred/switch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>

#include "detail/map_result.hpp"
#include "evaluate.hpp"
#include "expression.hpp"
#include "type_traits/homogenized_type.hpp"
Expand Down Expand Up @@ -149,35 +149,6 @@ concept CaseExpression = requires(std::remove_cvref_t<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<typename Result, typename T>
constexpr decltype(auto) map_switch_result(T&& t)
{
if constexpr (std::is_void_v<Result>)
{
static_cast<void>(t);
}
else if constexpr (std::is_void_v<T>)
{
static_cast<void>(t);
return std::monostate{};
}
else
{
return std::forward<T>(t);
}
}

} // namespace detail

/**
* @brief Deferred switch
*
Expand Down Expand Up @@ -221,6 +192,53 @@ class switch_expression
m_cases(std::forward<Default>(df), std::forward<Case>(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<typename... NewCases>
requires(sizeof...(NewCases) > 0 && (deferred::CaseExpression<NewCases> && ...))
[[nodiscard]] constexpr auto append(NewCases&&... new_cases) const&
{
using expanded_expression = switch_expression<ConditionExpression,
DefaultExpression,
CaseExpression...,
std::decay_t<NewCases>...>;
return std::apply(
[&](auto const& df, auto const&... cases) {
return expanded_expression(m_condition, df, cases..., std::forward<NewCases>(new_cases)...);
},
m_cases);
}

/**
* @brief Appends cases by moving owned expressions from this switch expression.
* @copydetails append
*/
template<typename... NewCases>
requires(sizeof...(NewCases) > 0 && (deferred::CaseExpression<NewCases> && ...))
[[nodiscard]] constexpr auto append(NewCases&&... new_cases) &&
{
using expanded_expression = switch_expression<ConditionExpression,
DefaultExpression,
CaseExpression...,
std::decay_t<NewCases>...>;
return std::apply(
[&](auto&& df, auto&&... cases) {
return expanded_expression(std::forward<ConditionExpression>(m_condition),
std::forward<decltype(df)>(df),
std::forward<decltype(cases)>(cases)...,
std::forward<NewCases>(new_cases)...);
},
std::move(m_cases));
}

private:
/**
* @brief Traverses the cases until one matches.
Expand All @@ -230,18 +248,18 @@ class switch_expression
template<std::size_t I, typename T>
[[nodiscard]] constexpr result_type choose_case(T const& t) const
{
if constexpr (I < std::tuple_size<decltype(m_cases)>::value)
if constexpr (I < std::tuple_size_v<decltype(m_cases)>)
{
if (std::get<I>(m_cases).compare(t))
{
return detail::map_switch_result<result_type>(std::get<I>(m_cases)());
return detail::map_result<result_type>([&] { return std::get<I>(m_cases)(); });
}

return choose_case<I + 1>(t);
}
else
{
return detail::map_switch_result<result_type>(std::get<0>(m_cases)());
return detail::map_result<result_type>([&] { return std::get<0>(m_cases)(); });
}
}

Expand Down Expand Up @@ -325,6 +343,7 @@ template<typename LabelExpression, typename BodyExpression>
* [] { 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.
Expand Down
15 changes: 3 additions & 12 deletions include/deferred/while.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,16 @@ class while_expression
{ }

/// @brief Evaluates the while loop.
constexpr void operator()() const&
constexpr void operator()() const
{
while (evaluate(m_condition))
{
evaluate(m_body);
}
}

/// @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))
{
Expand Down
22 changes: 22 additions & 0 deletions test/integration/switch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>();
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);
}
35 changes: 35 additions & 0 deletions test/unit/switch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include <catch2/catch_test_macros.hpp>

#include <memory>

#include "deferred/switch.hpp"
#include "deferred/type_traits/is_constant_expression.hpp"

Expand Down Expand Up @@ -94,3 +96,36 @@ TEST_CASE("switch with heterogeneous types", "[switch-variant]")
auto res2 = ex2();
CHECK(std::get<char const*>(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<result_type, std::variant<char const*, int, double>>);

CHECK(std::get<double>(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<int>(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<result_type, std::variant<int, std::monostate>>);

CHECK(std::holds_alternative<std::monostate>(expanded()));
}
Loading