diff --git a/.agents/docs/architecture.md b/.agents/docs/architecture.md index 312d6d7..db6cd65 100644 --- a/.agents/docs/architecture.md +++ b/.agents/docs/architecture.md @@ -49,6 +49,22 @@ graph TD ## 命名空间与 API 边界 +### 标准库依赖使用方式 + +由于`import std;`在目前 CMake 的构建系统中属于实验性特性,故使用标准库依赖仍然采用头文件方式。 +使用的预处理指令放于模块源文件的全局模块部分,示例: +```c++ +module; +#include +#include + +export module mcpplibs.primitives.submodule; + +export namespace mcpplibs::primitives::subnamepsace { + // Implements +} +``` + ### 公共 API(导出,稳定承诺) - `mcpplibs::primitives::std_bool` @@ -62,7 +78,7 @@ graph TD ### 内部实现(不导出,不承诺稳定) -- `mcpplibs::primitives::underlying::details::*` +- `mcpplibs::primitives::**::details::*` ### 约定 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b3989d8..5c1b51b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -12,6 +12,11 @@ - 技能目录: `../.agents/skills/` - mcpp-style-ref: `../.agents/skills/mcpp-style-ref/SKILL.md` +## 正在进行中的计划 + +- 计划目录:`../.github/prompts/` +- 当前计划:`../.github/prompts/plan-policyBehaviorProtocol.prompt.md` + ## 说明 - 新增或调整 Copilot 约束时,优先更新 `.agents/docs` 和 `.agents/skills`。 diff --git a/.github/prompts/plan-policyBehaviorProtocol.prompt.md b/.github/prompts/plan-policyBehaviorProtocol.prompt.md new file mode 100644 index 0000000..7bc994b --- /dev/null +++ b/.github/prompts/plan-policyBehaviorProtocol.prompt.md @@ -0,0 +1,137 @@ +## Plan: Policy 行为协议与分发规范 + +基于现有 traits/concept 架构,采用“policy handler 协议 + 固定 dispatcher + primitive 路由层”的设计。type 协商前移到编译期静态决议,运行期 dispatcher 固定为三层并按外到内调用:concurrency -> value(已知 common type)-> error。第一阶段覆盖四则运算(加减乘除),错误通道统一为 expected 返回;跨底层类型运算由 type policy 在编译期决定“是否允许 + common type”;value 层负责溢出判定与值调整/错误下放且需要接收 concurrency 层注入;error 层按策略执行最终错误处理。用户扩展边界限定为仅可特化 policy handler,以保持上层接口可复用、低耦合且强约束。 + +**Steps** +1. 明确协议核心抽象(阻塞后续步骤) +2. 在 policy 层定义行为协议接口(依赖步骤 1) +3. 在 operations 层建立统一分发入口(依赖步骤 2) +4. 在 primitive 层实现运算路由与返回类型统一(依赖步骤 3) +5. 为跨类型运算定义 type policy 协商规则(依赖步骤 4,部分可与步骤 6 并行) +6. 建立编译期约束与诊断信息(依赖步骤 2,可并行于步骤 5) +7. 增加测试矩阵与示例验证(依赖步骤 4、5、6) +8. 文档化协议与扩展边界(依赖步骤 7) + +**Phase 1: 协议基线(接口层)** +1. 在 [src/policy/traits.cppm](src/policy/traits.cppm) 复用 category 与 policy_type 概念,新增“行为协议概念”所需的基础类型约束(仅声明契约,不放具体行为)。 +2. 在 [src/policy/impl.cppm](src/policy/impl.cppm) 保留现有标签与 traits 特化,补充“策略能力声明位”对应的 traits 扩展位(用于编译期判定策略是否实现某 operation)。 +3. 新增 policy 协议模块(建议命名 mcpplibs.primitives.policy.handler),内容包含: +4. `policy_handler` 主模板(默认禁用) +5. `policy_handler_available` 概念(用于约束上层调用) +6. 标准返回别名(统一为 expected 语义) +7. 明确用户扩展点:只允许特化 handler,不开放分发规则特化。 + +**Phase 2: Operation 分发层(固定三层运行期链路)** +1. 在 [src/operations/impl.cppm](src/operations/impl.cppm) 维持 operation tag + arity 元数据,增加“可分发 operation”约束入口(例如合法 operation 概念)。 +2. 在 [src/operations/operators.cppm](src/operations/operators.cppm) 将 type 协商前移为编译期预处理,并实现运行期固定 dispatcher: +3. concurrency handler:负责并发包装(如加锁/解锁、内存屏障注入;具体机制可后续细化),并向 value 层注入并发执行上下文。 +4. value handler:接收已决议的 common type + concurrency 注入上下文,在 common type 上执行值域与溢出判定,决定“本层修正结果值”或“下放错误处理请求”。 +5. error handler:接收上层错误请求并按策略完成最终处理,统一映射到 expected 错误值。 +6. dispatcher 对外统一返回 expected,且不依赖具体 primitive 别名,仅依赖 traits 协议。 + +**Phase 3: Primitive 路由层(上层调用面)** +1. 在 [src/primitive/traits.cppm](src/primitive/traits.cppm) 复用 primitive_traits 与 resolve_policy_t,新增: +2. 跨类型运算协商 trait(由 type policy 决定是否允许、结果 common type) +3. 结果 primitive 重建工具(保持策略传播一致) +4. 在 primitive 对外运算入口中接入 operations dispatcher,覆盖加减乘除四个操作。 +5. 返回类型统一为 expected(成功值为 primitive,失败值为错误域类型)。 + +**Phase 4: 聚合导出与契约稳定** +1. 在 [src/primitives.cppm](src/primitives.cppm) 增加新协议模块与分发层导出,保持聚合入口一致。 +2. 在 [src/policy/utility.cppm](src/policy/utility.cppm) 保持 common policy 选择逻辑不变,仅补充与 handler 可用性相关的辅助别名(若需要)。 + +**Phase 5: 验证与回归** +1. 在 [tests](tests) 新增 policy 行为协议测试: +2. handler 未实现时应在编译期失败(约束强) +3. 四则运算均返回 expected +4. 相同 type policy 下跨类型协商正确/错误路径正确 +5. 在 [examples/basic.cpp](examples/basic.cpp) 增加最小示例: +6. 默认策略下四则运算 +7. 自定义 policy handler 的单点扩展示例(仅特化 handler) + + +**Interface Contracts(最小签名清单)** +1. Dispatcher 总入口(放在 [src/operations/operators.cppm](src/operations/operators.cppm)): +2. 输入:OperationTag、Lhs Primitive、Rhs Primitive、四类 policy(由 primitive_traits 解析)。 +3. 输出:expected。 +4. 约束:type 协商在编译期完成;运行期调用顺序固定为 concurrency -> value -> error。 +5. 编译期 type 协商合约(静态层,不进入运行期链路): +6. 输入:操作标签、Lhs/Rhs 静态类型信息、type policy。 +7. 输出:type_decision(is_allowed、common_type、diagnostic_id)。 +8. 责任:拒绝非法运算并确定 common_type;其结果作为运行期 value 层输入前提。 +9. concurrency handler 合约(放在 policy handler 模块): +10. 输入:operation_context、下一层 continuation、type_decision。 +11. 输出:concurrency_injection + 下一层 expected 透传能力。 +12. 责任:加锁/解锁、内存屏障、线程可见性包装;向 value 层注入并发执行上下文,不做值域判定。 +13. value handler 合约: +14. 输入:common_type 下的 lhs/rhs、operation_context、concurrency_injection。 +15. 输出:二选一路径: +16. 路径 A:直接给出成功值(已修正或原值)。 +17. 路径 B:下发 error_request(包含错误类别、上下文、候选回退值)。 +18. 责任:执行溢出/下溢/除零等值域检查,并决定是否本层修正。 +19. error handler 合约: +20. 输入:error_request + operation_context。 +21. 输出:expected 的最终错误分支或恢复成功值分支。 +22. 责任:按 error policy 落地处理(例如映射错误域、终止、抛错转译为 expected 错误值)。 +23. 跨层数据结构建议: +24. operation_context:封装 op tag、源 primitive traits、policy 句柄、调试标签。 +25. type_decision:封装 is_allowed、common_type、diagnostic_id。 +26. concurrency_injection:封装 guard_handle、memory_order/屏障策略、可选调度标签。 +27. value_decision:封装 has_value、value、error_request。 +28. error_request:封装 error_kind、reason、operation_context、可选 fallback。 +29. 扩展边界: +30. 用户只可特化三类运行期 handler(concurrency/value/error)与静态 type 协商策略实现,不可替换 dispatcher 顺序、不可改动跨层数据结构主骨架。 + +**Relevant files** +- [src/policy/traits.cppm](src/policy/traits.cppm) — 复用并扩展 policy 概念边界,承载行为协议约束基础。 +- [src/policy/impl.cppm](src/policy/impl.cppm) — 保持标签定义,补充策略能力元信息挂点。 +- [src/policy/utility.cppm](src/policy/utility.cppm) — 与 common_policies 选择逻辑对齐,必要时提供 handler 检查辅助。 +- [src/operations/impl.cppm](src/operations/impl.cppm) — operation 元数据与分发前置约束。 +- [src/operations/operators.cppm](src/operations/operators.cppm) — dispatcher 核心实现位置。 +- [src/primitive/traits.cppm](src/primitive/traits.cppm) — policy 解析与跨类型协商 trait 的主落点。 +- [src/primitives.cppm](src/primitives.cppm) — 对外聚合导出。 +- [tests](tests) — 编译期约束与行为结果验证。 +- [examples/basic.cpp](examples/basic.cpp) — API 用法与扩展示例。 + +**Verification** +1. 编译期契约验证: +2. 未提供对应 policy_handler 的 operation 调用必须报清晰静态断言。 +3. type policy 禁止的跨类型运算必须在编译期拒绝。 +4. 固定链路验证: +5. type 协商必须在编译期完成,运行期 dispatcher 必须按 concurrency -> value -> error 顺序调用,禁止跳层与重排。 +6. value 层必须验证“接收 concurrency 注入”这一前提生效。 +7. value 层“值修正”与“错误下放”两条路径都需覆盖测试。 +8. 行为一致性验证: +9. 四则运算对默认策略与自定义策略均返回 expected。 +10. 失败路径(溢出/除零/不兼容类型)按 error policy 语义编码到 expected 错误值。 +11. unchecked_value 路径验证: +12. 不触发 error policy,保持原生算术语义(含 UB 风险)并通过测试明确边界。 +13. 导出稳定性验证: +14. 通过聚合模块导入可直接访问 policy 协议、operation 分发、primitive 运算。 +15. 运行 tests 与示例构建,确认无回归。 + +**Decisions** +- 错误通道:默认路径统一返回 expected。 +- dispatcher 形态:type 协商前移到编译期,运行期固定三层链路 concurrency -> value -> error,不开放顺序重排。 +- 跨底层类型运算:由 type policy 在编译期决定可行性与 common type。 +- value 层职责:判定溢出并决定“本层值修正”或“下放 error 层处理”。 +- unchecked_value 语义:不做错误处理,不调用 error policy,行为尽量贴近原生 C/C++(包含 UB 风险)。 +- 扩展边界:仅开放 policy handler 特化,不开放分发规则。 +- 第一阶段覆盖范围:Addition/Subtraction/Multiplication/Division 全部纳入。 +- In scope:policy 行为协议、operation 分发、primitive 路由、测试与文档。 +- Out of scope:并发策略的运行时同步原语实现细节(atomic/lock-free/锁策略具体执行体)。 + +## Underlying Bridge Execution Contract (Runtime) + +1. `dispatch` 在 value 阶段前统一执行 `to_rep`,将原始 value 映射到可协商的 `rep_type`。 +2. `type_handler` 的协商对象是 `lhs_rep/rhs_rep`,而非原始 value type。 +3. 若任一输入 `is_valid_rep(...) == false`,立即构造 `runtime_error_kind::domain_error` 并进入 error policy。 +4. 通过校验后执行 `from_rep -> to_rep` 规范化,再进入 value handler 与 op binding。 +5. 当前结果值仍按 `common_rep` 回传;comparison 最小闭环采用 `0/1` 表示(`static_cast(bool)`)。 +6. 本契约不改变 dispatcher 链路顺序,也不改变错误枚举体系。 + +**Further Considerations** +1. expected 的错误载体类型建议先统一为轻量错误枚举,再逐步演进到可扩展错误域,以减少首版模板复杂度。 +2. type policy 的 common type 规则建议先采用“显式白名单 + static_assert 诊断”,避免首版引入过宽的隐式提升。 +3. 后续可新增“native 快速路径”作为可选 API:当组合为 primitive 时,提供非 expected 返回通道以最大化贴近原生 C/C++ 性能与行为。 +4. 后续可新增 C API 适配层(extern "C" 薄封装,POD 入参与返回值),内部复用 unchecked/native 路径,优先保障与现有 C 调用约定兼容。 diff --git a/CMakeLists.txt b/CMakeLists.txt index d7ab2b1..afca2fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -project(mcpplibs-primitives VERSION 0.1.0 LANGUAGES CXX) +project(mcpplibs-primitives VERSION 0.2.0 LANGUAGES CXX) find_package(Threads REQUIRED) @@ -36,7 +36,7 @@ endif() # Library add_library(mcpplibs-primitives STATIC) -file(GLOB_RECURSE CXX_MODULE_FILES +file(GLOB_RECURSE CXX_MODULE_FILES CONFIGURE_DEPENDS src/*.cppm ) @@ -60,8 +60,7 @@ target_include_directories(mcpplibs-primitives PUBLIC ) # Examples -add_executable(basic examples/basic.cpp) -target_link_libraries(basic PRIVATE mcpplibs-primitives) +add_subdirectory(examples EXCLUDE_FROM_ALL) # Testing option(BUILD_TESTING "Build the testing directories" ON) diff --git a/README.md b/README.md index 3cb412f..119a66d 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ 该库在 `primitive` 类型上重载了常见的 C++ 算术、位运算和一元运算符。算术行为受策略(policy)控制: -- 值策略(`checked_value` / `saturating_value` / `unchecked_value`)决定溢出行为; -- 错误策略(`throw_error` / `expected_error` / `terminate_error`)决定在 `checked_value` 且发生错误时的处理方式。 +- 值策略(`policy::value::checked` / `policy::value::saturating` / `policy::value::unchecked`)决定溢出行为; +- 错误策略(`policy::error::throwing` / `policy::error::expected` / `policy::error::terminate`)决定在 `policy::value::checked` 且发生错误时的处理方式。 示例: @@ -32,11 +32,35 @@ using namespace mcpplibs::primitives::policy; primitive a{1}, b{2}; auto c = a + b; // primitive -primitive x{std::numeric_limits::max()}; -primitive y{1}; -auto maybe = x + y; // std::expected, std::overflow_error> +primitive x{std::numeric_limits::max()}; +primitive y{1}; +auto maybe = x + y; // std::expected, policy::error::kind> ``` +## Policy 协议命名空间 + +自定义 policy 时,协议入口已按职责拆分到子命名空间: + +- `policy::type::handler` / `policy::type::handler_available` +- `policy::concurrency::handler` / `policy::concurrency::injection` +- `policy::value::handler` / `policy::value::decision` +- `policy::error::handler` / `policy::error::request` / `policy::error::kind` + +预设 policy 标签也按类别归档: + +- `policy::value::{checked, unchecked, saturating}` +- `policy::type::{strict, compatible, transparent}` +- `policy::error::{throwing, expected, terminate}` +- `policy::concurrency::{none, atomic}` + +默认策略位于 `policy::defaults`: + +- `policy::defaults::value` +- `policy::defaults::type` +- `policy::defaults::error` +- `policy::defaults::concurrency` + + ## 项目结构 ``` diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..b5b0ea1 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,16 @@ +set(PRIMITIVES_EXAMPLE_SOURCES + ex01_default_arithmetic.cpp + ex02_type_policy.cpp + ex03_value_policy.cpp + ex04_error_policy.cpp + ex05_concurrency_policy.cpp + ex06_custom_underlying.cpp + ex07_custom_policy.cpp + ex08_custom_operation.cpp +) + +foreach(example_source IN LISTS PRIMITIVES_EXAMPLE_SOURCES) + get_filename_component(example_name ${example_source} NAME_WE) + add_executable(${example_name} ${example_source}) + target_link_libraries(${example_name} PRIVATE mcpplibs-primitives) +endforeach() diff --git a/examples/basic.cpp b/examples/basic.cpp deleted file mode 100644 index 83b2790..0000000 --- a/examples/basic.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include -#include - -import mcpplibs.primitives; - -int main() { - using namespace mcpplibs::primitives; - using namespace mcpplibs::primitives::policy; - using namespace mcpplibs::primitives::operators; - using namespace mcpplibs::primitives::types; - - // Operators - I32<> a{3}; - I32<> b{4}; - auto c = a + b; // primitive - std::cout << "3 + 4 = " << static_cast(c) << "\n"; - - // Error handling - I32 lhs{1}; - I32 rhs{std::numeric_limits::max()}; - - try { - auto res = lhs + rhs; - } catch (const std::exception &e) { - std::cout << "An exception occurred: " << e.what() << "\n"; - } - - I64 lhs2{1}; - I64 rhs2{std::numeric_limits::max()}; - - auto res2 = lhs2 + rhs2; - - // Saturating - I32 s1{std::numeric_limits::max()}; - I32 s2{1}; - auto sat = s1 + s2; // saturating -> stays max - std::cout << "saturating max + 1 = " << static_cast(sat) - << "\n"; - - // Mixed-type addition - using expected_type = I64; - expected_type L1{5}; - I32 L2{6}; - auto mix = L1 + L2; // common_type -> I64 - std::cout << "5 + 6 = " << mix.value() << "\n"; - std::cout << std::boolalpha; - std::cout << "Does type of mix is I64: " << std::same_as << std::endl; - - return 0; -} diff --git a/examples/ex01_default_arithmetic.cpp b/examples/ex01_default_arithmetic.cpp new file mode 100644 index 0000000..7d8285d --- /dev/null +++ b/examples/ex01_default_arithmetic.cpp @@ -0,0 +1,35 @@ +#include +#include + +import mcpplibs.primitives; + +using namespace mcpplibs::primitives; + +int main() { + // Point 1: Use built-in primitive type aliases and framework operators. + using namespace mcpplibs::primitives::types; + using namespace mcpplibs::primitives::operators; + + using value_t = I32<>; + + // Prepare operands. + auto const a = value_t{40}; + auto const b = value_t{2}; + + // Run the four arithmetic operators routed through dispatcher. + auto const sum = a + b; + auto const diff = a - b; + auto const prod = a * b; + auto const quot = a / b; + + // Validate all dispatches succeeded. + if (!sum.has_value() || !diff.has_value() || !prod.has_value() || + !quot.has_value()) { + std::cerr << "default arithmetic failed\n"; + return 1; + } + + std::cout << "sum=" << sum->value() << ", diff=" << diff->value() + << ", prod=" << prod->value() << ", quot=" << quot->value() << '\n'; + return 0; +} diff --git a/examples/ex02_type_policy.cpp b/examples/ex02_type_policy.cpp new file mode 100644 index 0000000..0d1b531 --- /dev/null +++ b/examples/ex02_type_policy.cpp @@ -0,0 +1,40 @@ +#include +#include + +import mcpplibs.primitives; + +using namespace mcpplibs::primitives; + +int main() { + // Point 2: Compare different type_policy behaviors. + // Case A: strict policy rejects mixed underlying reps at compile time. + using strict_i32 = primitive; + using strict_i64 = primitive; + + using strict_meta = + operations::dispatcher_meta; + + static_assert(std::is_same_v); + static_assert( + !policy::type::handler::allowed); + + // Case B: compatible policy allows mixed arithmetic reps and negotiates a + // common type. + using compatible_meta = operations::dispatcher_meta< + operations::Addition, + primitive, + primitive, + policy::error::kind>; + + static_assert( + std::is_same_v); + + std::cout << "type_policy demo passed\n"; + return 0; +} diff --git a/examples/ex03_value_policy.cpp b/examples/ex03_value_policy.cpp new file mode 100644 index 0000000..5054d01 --- /dev/null +++ b/examples/ex03_value_policy.cpp @@ -0,0 +1,49 @@ +#include +#include + +import mcpplibs.primitives; + +using namespace mcpplibs::primitives; + +int main() { + // Point 3: Compare checked / unchecked / saturating value policies. + using checked_t = + primitive; + using unchecked_t = primitive; + using saturating_t = primitive; + + // Same overflow input for all three policies. + auto const lhs_checked = checked_t{65530}; + auto const rhs_checked = checked_t{20}; + auto const checked_result = operations::add(lhs_checked, rhs_checked); + + auto const lhs_unchecked = unchecked_t{65530}; + auto const rhs_unchecked = unchecked_t{20}; + auto const unchecked_result = operations::add(lhs_unchecked, rhs_unchecked); + + auto const lhs_saturating = saturating_t{65530}; + auto const rhs_saturating = saturating_t{20}; + auto const saturating_result = + operations::add(lhs_saturating, rhs_saturating); + + // Expected outcomes: + // checked -> error, unchecked -> wrap, saturating -> clamp. + if (checked_result.has_value()) { + std::cerr << "checked policy should report overflow\n"; + return 1; + } + if (!unchecked_result.has_value() || unchecked_result->value() != 14) { + std::cerr << "unchecked policy should wrap\n"; + return 1; + } + if (!saturating_result.has_value() || saturating_result->value() != 65535) { + std::cerr << "saturating policy should clamp\n"; + return 1; + } + + std::cout << "checked=overflow, unchecked=" << unchecked_result->value() + << ", saturating=" << saturating_result->value() << '\n'; + return 0; +} diff --git a/examples/ex04_error_policy.cpp b/examples/ex04_error_policy.cpp new file mode 100644 index 0000000..60baa95 --- /dev/null +++ b/examples/ex04_error_policy.cpp @@ -0,0 +1,43 @@ +#include +#include + +import mcpplibs.primitives; + +using namespace mcpplibs::primitives; + +int main() { + // Point 4: Compare error_policy behavior under divide-by-zero. + using expected_t = + primitive; + using throwing_t = + primitive; + + // Case A: expected policy returns error payload. + auto const e_lhs = expected_t{42}; + auto const e_rhs = expected_t{0}; + auto const expected_result = operations::div(e_lhs, e_rhs); + if (expected_result.has_value() || + expected_result.error() != policy::error::kind::divide_by_zero) { + std::cerr << "expected policy did not return divide_by_zero\n"; + return 1; + } + + // Case B: throwing policy raises exception. + auto const t_lhs = throwing_t{42}; + auto const t_rhs = throwing_t{0}; + + bool caught = false; + try { + (void)operations::div(t_lhs, t_rhs); + } catch (std::runtime_error const &) { + caught = true; + } + + if (!caught) { + std::cerr << "throwing policy did not throw\n"; + return 1; + } + + std::cout << "error_policy demo passed\n"; + return 0; +} diff --git a/examples/ex05_concurrency_policy.cpp b/examples/ex05_concurrency_policy.cpp new file mode 100644 index 0000000..ac9e382 --- /dev/null +++ b/examples/ex05_concurrency_policy.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +import mcpplibs.primitives; + +using namespace mcpplibs::primitives; + +int main() { + // Point 5: Use atomic concurrency policy and verify concurrent consistency. + using atomic_t = + primitive; + + auto const lhs = atomic_t{12}; + auto const rhs = atomic_t{30}; + + std::atomic mismatch_count{0}; + std::vector workers; + workers.reserve(4); + + // Run many concurrent dispatches. Any error or wrong value is counted. + for (int i = 0; i < 4; ++i) { + workers.emplace_back([&]() { + for (int n = 0; n < 10000; ++n) { + auto const out = operations::add(lhs, rhs); + if (!out.has_value() || out->value() != 42) { + mismatch_count.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + + for (auto &worker : workers) { + worker.join(); + } + + // A non-zero mismatch count indicates unexpected behavior under concurrency. + if (mismatch_count.load(std::memory_order_relaxed) != 0) { + std::cerr << "atomic policy path mismatch\n"; + return 1; + } + + std::cout << "concurrency_policy demo passed\n"; + return 0; +} diff --git a/examples/ex06_custom_underlying.cpp b/examples/ex06_custom_underlying.cpp new file mode 100644 index 0000000..f521627 --- /dev/null +++ b/examples/ex06_custom_underlying.cpp @@ -0,0 +1,86 @@ +#include + +import mcpplibs.primitives; + +using namespace mcpplibs::primitives; + +// Point 6 / Step 1: Define custom domain value types. +struct UserInteger { + int value; +}; + +struct NonNegativeInt { + int value; +}; + +// Point 6 / Step 2: Register underlying::traits for UserInteger. +// This type has a full int bridge and accepts all reps. +template <> struct mcpplibs::primitives::underlying::traits { + using value_type = UserInteger; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return UserInteger{value}; + } + + static constexpr auto is_valid_rep(rep_type) noexcept -> bool { return true; } +}; + +// Point 6 / Step 3: Register underlying::traits for NonNegativeInt. +// This one demonstrates custom rep validation (rep must be non-negative). +template <> struct mcpplibs::primitives::underlying::traits { + using value_type = NonNegativeInt; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr auto to_rep(value_type value) noexcept -> rep_type { + return value.value; + } + + static constexpr auto from_rep(rep_type value) noexcept -> value_type { + return NonNegativeInt{value}; + } + + static constexpr auto is_valid_rep(rep_type value) noexcept -> bool { + return value >= 0; + } +}; + +int main() { + // Point 6 / Step 4A: Call operations on UserInteger custom underlying. + using user_t = + primitive; + + auto const ua = user_t{UserInteger{40}}; + auto const ub = user_t{UserInteger{2}}; + auto const user_result = operations::add(ua, ub); + if (!user_result.has_value() || user_result->value() != 42) { + std::cerr << "custom UserInteger underlying failed\n"; + return 1; + } + + // Point 6 / Step 4B: Demonstrate dispatcher rejecting invalid reps. + // Here -1 fails is_valid_rep and is mapped to domain_error. + using nonneg_t = primitive; + auto const na = nonneg_t{NonNegativeInt{-1}}; + auto const nb = nonneg_t{NonNegativeInt{2}}; + auto const nonneg_result = operations::add(na, nb); + if (nonneg_result.has_value() || + nonneg_result.error() != policy::error::kind::domain_error) { + std::cerr << "invalid rep should be rejected by dispatcher\n"; + return 1; + } + + std::cout << "custom underlying demo passed\n"; + return 0; +} diff --git a/examples/ex07_custom_policy.cpp b/examples/ex07_custom_policy.cpp new file mode 100644 index 0000000..b793012 --- /dev/null +++ b/examples/ex07_custom_policy.cpp @@ -0,0 +1,141 @@ +#include +#include +#include + +import mcpplibs.primitives; +import mcpplibs.primitives.operations.invoker; + +using namespace mcpplibs::primitives; + +namespace demo { +// Point 7 / Step 1: Define one custom tag for each policy dimension. +struct custom_value {}; +struct custom_type {}; +struct custom_error {}; +struct custom_concurrency {}; +} // namespace demo + +// Point 7 / Step 2: Register tags into policy::traits. +template <> struct mcpplibs::primitives::policy::traits { + using policy_type = demo::custom_value; + static constexpr bool enabled = true; + static constexpr auto kind = category::value; +}; + +template <> struct mcpplibs::primitives::policy::traits { + using policy_type = demo::custom_type; + static constexpr bool enabled = true; + static constexpr auto kind = category::type; +}; + +template <> struct mcpplibs::primitives::policy::traits { + using policy_type = demo::custom_error; + static constexpr bool enabled = true; + static constexpr auto kind = category::error; +}; + +template <> +struct mcpplibs::primitives::policy::traits { + using policy_type = demo::custom_concurrency; + static constexpr bool enabled = true; + static constexpr auto kind = category::concurrency; +}; + +// Point 7 / Step 3A: Implement custom type handler. +// Always allow and negotiate through std::common_type_t. +template +struct mcpplibs::primitives::policy::type::handler { + static constexpr bool enabled = true; + static constexpr bool allowed = true; + static constexpr unsigned diagnostic_id = 0; + using common_rep = std::common_type_t; +}; + +// Point 7 / Step 3B: Implement custom concurrency handler. +template +struct mcpplibs::primitives::policy::concurrency::handler< + demo::custom_concurrency, OpTag, CommonRep, ErrorPayload> { + static constexpr bool enabled = true; + using injection_type = mcpplibs::primitives::policy::concurrency::injection; + using result_type = std::expected; + + static constexpr auto inject() noexcept -> injection_type { + injection_type out{}; + out.fence_before = true; + out.fence_after = false; + return out; + } +}; + +// Point 7 / Step 3C: Implement custom value handler. +// Complex point: finalize() post-processes decision and adjusts output. +template +struct mcpplibs::primitives::policy::value::handler { + static constexpr bool enabled = true; + static constexpr bool may_adjust_value = true; + using decision_type = + mcpplibs::primitives::policy::value::decision; + using result_type = std::expected; + + static constexpr auto + finalize(decision_type decision, + mcpplibs::primitives::policy::concurrency::injection const &) + -> decision_type { + if (decision.has_value) { + decision.value = static_cast(decision.value + 1); + } + return decision; + } +}; + +// Point 7 / Step 3D: Provide binding for custom value policy + Addition. +// Without this specialization, runtime::run_value static_assert will fail. +template +struct mcpplibs::primitives::operations::runtime::op_binding< + mcpplibs::primitives::operations::Addition, demo::custom_value, CommonRep> { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> mcpplibs::primitives::policy::value::decision { + mcpplibs::primitives::policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs + rhs); + return out; + } +}; + +// Point 7 / Step 3E: Implement custom error handler. +template +struct mcpplibs::primitives::policy::error::handler { + static constexpr bool enabled = true; + using request_type = mcpplibs::primitives::policy::error::request; + using result_type = std::expected; + + static constexpr auto resolve(request_type const &) -> result_type { + return std::unexpected(policy::error::kind::unspecified); + } +}; + +int main() { + // Point 7 / Step 4: Compose all custom tags and execute a call path. + using custom_t = primitive; + + auto const lhs = custom_t{20}; + auto const rhs = custom_t{21}; + auto const result = operations::add(lhs, rhs); + + if (!result.has_value() || result->value() != 42) { + std::cerr << "custom policy pipeline failed\n"; + return 1; + } + + std::cout << "custom policy demo passed\n"; + return 0; +} diff --git a/examples/ex08_custom_operation.cpp b/examples/ex08_custom_operation.cpp new file mode 100644 index 0000000..902a667 --- /dev/null +++ b/examples/ex08_custom_operation.cpp @@ -0,0 +1,103 @@ +#include + +import mcpplibs.primitives; +import mcpplibs.primitives.operations.invoker; + +using namespace mcpplibs::primitives; + +namespace demo_ops { +// Point 8 / Step 1: Define custom operation tags. +struct Average {}; +struct GreaterThan {}; +struct BitAnd {}; +} // namespace demo_ops + +// Point 8 / Step 2: Register operation traits for +// arithmetic/comparison/bitwise. +template <> struct mcpplibs::primitives::operations::traits { + using op_tag = demo_ops::Average; + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::arithmetic; +}; + +template <> +struct mcpplibs::primitives::operations::traits { + using op_tag = demo_ops::GreaterThan; + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::comparison; +}; + +template <> struct mcpplibs::primitives::operations::traits { + using op_tag = demo_ops::BitAnd; + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::bitwise; +}; + +// Point 8 / Step 3: Provide runtime op_binding for each new operation. +// Complex point: these specializations plug directly into run_value dispatch. +template +struct mcpplibs::primitives::operations::runtime::op_binding< + demo_ops::Average, policy::value::checked, CommonRep> { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast((lhs + rhs) / 2); + return out; + } +}; + +template +struct mcpplibs::primitives::operations::runtime::op_binding< + demo_ops::GreaterThan, policy::value::checked, CommonRep> { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs > rhs); + return out; + } +}; + +template +struct mcpplibs::primitives::operations::runtime::op_binding< + demo_ops::BitAnd, policy::value::checked, CommonRep> { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs & rhs); + return out; + } +}; + +int main() { + // Point 8 / Step 4: Invoke each custom operation through operations::apply. + using value_t = + primitive; + + auto const a = value_t{10}; + auto const b = value_t{6}; + + auto const avg = operations::apply(a, b); + auto const gt = operations::apply(a, b); + auto const band = operations::apply(a, b); + + if (!avg.has_value() || !gt.has_value() || !band.has_value()) { + std::cerr << "custom operation dispatch failed\n"; + return 1; + } + + std::cout << "avg=" << avg->value() << ", gt=" << gt->value() + << ", bitand=" << band->value() << '\n'; + return 0; +} diff --git a/examples/xmake.lua b/examples/xmake.lua index 74fcb7a..4dd4589 100644 --- a/examples/xmake.lua +++ b/examples/xmake.lua @@ -2,8 +2,28 @@ add_rules("mode.debug", "mode.release") set_languages("c++23") +local examples = { + "ex01_default_arithmetic", + "ex02_type_policy", + "ex03_value_policy", + "ex04_error_policy", + "ex05_concurrency_policy", + "ex06_custom_underlying", + "ex07_custom_policy", + "ex08_custom_operation" +} + +-- CI compatibility alias: keep `xmake run basic` working. target("basic") set_kind("binary") - add_files("basic.cpp") + add_files("ex01_default_arithmetic.cpp") add_deps("mcpplibs-primitives") set_policy("build.c++.modules", true) + +for _, name in ipairs(examples) do + target(name) + set_kind("binary") + add_files(name .. ".cpp") + add_deps("mcpplibs-primitives") + set_policy("build.c++.modules", true) +end diff --git a/src/operations/dispatcher.cppm b/src/operations/dispatcher.cppm new file mode 100644 index 0000000..64a5e7d --- /dev/null +++ b/src/operations/dispatcher.cppm @@ -0,0 +1,167 @@ +module; + +#include +#include + +export module mcpplibs.primitives.operations.dispatcher; + +import mcpplibs.primitives.operations.traits; +import mcpplibs.primitives.operations.invoker; +import mcpplibs.primitives.primitive.impl; +import mcpplibs.primitives.primitive.traits; +import mcpplibs.primitives.policy.handler; +import mcpplibs.primitives.policy.impl; +import mcpplibs.primitives.underlying; + +export namespace mcpplibs::primitives::operations { + +template +concept primitive_instance = requires { + typename primitives::traits::primitive_traits< + std::remove_cvref_t>::value_type; +}; + +template +struct dispatcher_meta { + using lhs_primitive = std::remove_cvref_t; + using rhs_primitive = std::remove_cvref_t; + + using lhs_traits = primitives::traits::primitive_traits; + using rhs_traits = primitives::traits::primitive_traits; + + using lhs_value_type = lhs_traits::value_type; + using rhs_value_type = rhs_traits::value_type; + + using lhs_rep = underlying::traits::rep_type; + using rhs_rep = underlying::traits::rep_type; + + using type_policy = lhs_traits::type_policy; + using value_policy = lhs_traits::value_policy; + using error_policy = lhs_traits::error_policy; + using concurrency_policy = lhs_traits::concurrency_policy; + + using common_rep = + policy::type::handler::common_rep; + + static constexpr bool type_ready = + policy::type::handler_available; + static constexpr bool type_protocol_ready = + policy::type::handler_protocol; + static constexpr bool concurrency_ready = + policy::concurrency::handler_available; + static constexpr bool concurrency_protocol_ready = + policy::concurrency::handler_protocol; + static constexpr bool value_ready = + policy::value::handler_available; + static constexpr bool value_protocol_ready = + policy::value::handler_protocol; + static constexpr bool error_ready = + policy::error::handler_available; + static constexpr bool error_protocol_ready = + policy::error::handler_protocol; +}; + +template +using dispatch_result_t = std::expected< + typename dispatcher_meta::common_rep, + ErrorPayload>; + +// Dispatcher pipeline: compile-time negotiation plus runtime chain +// (concurrency -> value -> error) through selected policy handlers. +template +constexpr auto dispatch(Lhs const &lhs, Rhs const &rhs) + -> dispatch_result_t { + using meta = dispatcher_meta; + using common_rep = meta::common_rep; + using concurrency_handler_t = + policy::concurrency::handler; + using value_handler_t = + policy::value::handler; + + static_assert( + meta::type_ready, + "Missing type_handler specialization for this operation/policy"); + static_assert(meta::type_protocol_ready, + "type::handler does not satisfy the policy protocol contract"); + static_assert(policy::type::handler::allowed, + "Type policy rejected this operation for the given operands"); + static_assert(op_capability_valid_v, + "Operation must declare a non-none capability_mask"); + static_assert( + meta::concurrency_ready, + "Missing concurrency_handler specialization for this operation/policy"); + static_assert( + meta::concurrency_protocol_ready, + "concurrency::handler does not satisfy the policy protocol contract"); + static_assert( + meta::value_ready, + "Missing value_handler specialization for this operation/policy"); + static_assert(meta::value_protocol_ready, + "value::handler does not satisfy the policy protocol contract"); + static_assert( + meta::error_ready, + "Missing error_handler specialization for this operation/policy"); + static_assert(meta::error_protocol_ready, + "error::handler does not satisfy the policy protocol contract"); + + // Runtime stage 1: concurrency context injection. + auto const injection = + runtime::inject_concurrency(); + + // Runtime stage 2: value path. + auto const lhs_rep_raw = + underlying::traits::to_rep(lhs.value()); + auto const rhs_rep_raw = + underlying::traits::to_rep(rhs.value()); + + if (!underlying::traits::is_valid_rep( + lhs_rep_raw) || + !underlying::traits::is_valid_rep( + rhs_rep_raw)) { + policy::error::request request{}; + request.code = policy::error::kind::domain_error; + request.reason = "invalid underlying representation"; + return runtime::resolve_error(request); + } + + auto const lhs_value_normalized = + underlying::traits::from_rep(lhs_rep_raw); + auto const rhs_value_normalized = + underlying::traits::from_rep(rhs_rep_raw); + + auto const lhs_common = static_cast( + underlying::traits::to_rep( + lhs_value_normalized)); + auto const rhs_common = static_cast( + underlying::traits::to_rep( + rhs_value_normalized)); + + auto const decision = + runtime::run_value(lhs_common, rhs_common, + injection); + + if (decision.has_value) { + return decision.value; + } + + // Runtime stage 3: error policy. + return runtime::resolve_error(decision.error); +} + +} // namespace mcpplibs::primitives::operations diff --git a/src/operations/impl.cppm b/src/operations/impl.cppm index 647f1b4..deecfc6 100644 --- a/src/operations/impl.cppm +++ b/src/operations/impl.cppm @@ -1,606 +1,62 @@ -module; -#include -#include -#include -#include -#include -#include -#include - export module mcpplibs.primitives.operations.impl; import mcpplibs.primitives.operations.traits; -import mcpplibs.primitives.policy; -import mcpplibs.primitives.primitive; -import mcpplibs.primitives.underlying; export namespace mcpplibs::primitives::operations { -namespace details { - -template -struct is_expected : std::false_type {}; - -template -struct is_expected> : std::true_type {}; - -template -inline constexpr bool is_expected_v = is_expected::value; - -template constexpr bool add_overflow(T lhs, T rhs) noexcept { - if constexpr (!std::is_integral_v) { - return false; - } else if constexpr (std::is_signed_v) { - using limits = std::numeric_limits; - return (rhs > 0 && lhs > limits::max() - rhs) || - (rhs < 0 && lhs < limits::min() - rhs); - } else { - using limits = std::numeric_limits; - return lhs > limits::max() - rhs; - } -} - -template constexpr bool sub_overflow(T lhs, T rhs) noexcept { - if constexpr (!std::is_integral_v) { - return false; - } else if constexpr (std::is_signed_v) { - using limits = std::numeric_limits; - return (rhs < 0 && lhs > limits::max() + rhs * static_cast(-1)) || - (rhs > 0 && lhs < limits::min() + rhs); - } else { - return lhs < rhs; - } -} - -template constexpr bool mul_overflow(T lhs, T rhs) noexcept { - if constexpr (!std::is_integral_v) { - return false; - } else if (lhs == 0 || rhs == 0) { - return false; - } else if constexpr (std::is_signed_v) { - using limits = std::numeric_limits; - if (lhs == -1) { - return rhs == limits::min(); - } - if (rhs == -1) { - return lhs == limits::min(); - } - if (lhs > 0) { - return (rhs > 0) ? lhs > limits::max() / rhs : rhs < limits::min() / lhs; - } - return (rhs > 0) ? lhs < limits::min() / rhs - : lhs != 0 && rhs < limits::max() / lhs; - } else { - using limits = std::numeric_limits; - return lhs > limits::max() / rhs; - } -} - -template constexpr T saturating_add(T lhs, T rhs) noexcept { - if constexpr (!std::is_integral_v) { - return static_cast(lhs + rhs); - } else { - using limits = std::numeric_limits; - if (!add_overflow(lhs, rhs)) { - return static_cast(lhs + rhs); - } - if constexpr (std::is_signed_v) { - return rhs >= 0 ? limits::max() : limits::min(); - } else { - return limits::max(); - } - } -} - -template constexpr T saturating_sub(T lhs, T rhs) noexcept { - if constexpr (!std::is_integral_v) { - return static_cast(lhs - rhs); - } else { - using limits = std::numeric_limits; - if (!sub_overflow(lhs, rhs)) { - return static_cast(lhs - rhs); - } - if constexpr (std::is_signed_v) { - return rhs > 0 ? limits::min() : limits::max(); - } else { - return limits::min(); - } - } -} - -template constexpr T saturating_mul(T lhs, T rhs) noexcept { - if constexpr (!std::is_integral_v) { - return static_cast(lhs * rhs); - } else { - using limits = std::numeric_limits; - if (!mul_overflow(lhs, rhs)) { - return static_cast(lhs * rhs); - } - if constexpr (std::is_signed_v) { - return ((lhs < 0) ^ (rhs < 0)) ? limits::min() : limits::max(); - } else { - return limits::max(); - } - } -} - -} // namespace details - -// ===== Built-in value policy behaviors ===== - -template -struct value_policy_behavior { - template static T add(T lhs, T rhs) { - if (details::add_overflow(lhs, rhs)) { - throw std::overflow_error("overflow in add"); - } - return static_cast(lhs + rhs); - } - - template static T sub(T lhs, T rhs) { - if (details::sub_overflow(lhs, rhs)) { - throw std::overflow_error("overflow in sub"); - } - return static_cast(lhs - rhs); - } - - template static T mul(T lhs, T rhs) { - if (details::mul_overflow(lhs, rhs)) { - throw std::overflow_error("overflow in mul"); - } - return static_cast(lhs * rhs); - } -}; - -template -struct value_policy_behavior { - template static constexpr T add(T lhs, T rhs) noexcept { - return static_cast(lhs + rhs); - } - - template static constexpr T sub(T lhs, T rhs) noexcept { - return static_cast(lhs - rhs); - } - - template static constexpr T mul(T lhs, T rhs) noexcept { - return static_cast(lhs * rhs); - } -}; - -template -struct value_policy_behavior { - template static constexpr T add(T lhs, T rhs) noexcept { - return details::saturating_add(lhs, rhs); - } - - template static constexpr T sub(T lhs, T rhs) noexcept { - return details::saturating_sub(lhs, rhs); - } - - template static constexpr T mul(T lhs, T rhs) noexcept { - return details::saturating_mul(lhs, rhs); - } -}; - -// ===== Built-in type policy behaviors ===== - -template -struct type_policy_behavior { - template using result_type = std::remove_cv_t; - - template - static constexpr Out cast_lhs(In const &value) noexcept { - return static_cast(value); - } - - template - static constexpr Out cast_rhs(In const &value) noexcept { - static_assert(std::same_as, std::remove_cv_t>, - "strict_type requires both operands to have same type"); - return static_cast(value); - } -}; - -template -struct type_policy_behavior { - template - using result_type = std::common_type_t; - - template - static constexpr Out cast_lhs(In const &value) noexcept { - return static_cast(value); - } - - template - static constexpr Out cast_rhs(In const &value) noexcept { - static_assert(underlying::traits>::kind == - underlying::traits>::kind, - "category_compatible_type requires same underlying category"); - return static_cast(value); - } -}; - -template -struct type_policy_behavior { - template - using result_type = std::common_type_t; - - template - static constexpr Out cast_lhs(In const &value) noexcept { - return static_cast(value); - } - - template - static constexpr Out cast_rhs(In const &value) noexcept { - return static_cast(value); - } -}; - -// ===== Built-in error policy behaviors ===== - -template -struct error_policy_behavior { - template - static constexpr Result evaluate(Fn &&fn) noexcept(noexcept(fn())) { - return static_cast(fn()); - } -}; - -template -struct error_policy_behavior { - template - static constexpr Result evaluate(Fn &&fn) noexcept(noexcept(fn())) { - if constexpr (details::is_expected_v) { - return Result{std::in_place, std::forward(fn)()}; - } else { - return static_cast(fn()); - } - } -}; - -template -struct error_policy_behavior { - template - static Result evaluate(Fn &&fn) noexcept { - if constexpr (noexcept(fn())) { - return static_cast(fn()); - } else { - try { - return static_cast(fn()); - } catch (...) { - std::terminate(); - } - } - } -}; - -// ===== Built-in concurrency policy behaviors ===== +struct Addition {}; +struct Subtraction {}; +struct Multiplication {}; +struct Division {}; +struct Equal {}; +struct NotEqual {}; -template -struct concurrency_policy_behavior { - template - static constexpr auto execute(Fn &&fn) noexcept(noexcept(fn())) - -> decltype(fn()) { - return fn(); - } -}; +template <> struct traits { + using op_tag = Addition; -template -struct concurrency_policy_behavior { - template - static auto execute(Fn &&fn) noexcept(noexcept(fn())) -> decltype(fn()) { - std::atomic_signal_fence(std::memory_order_seq_cst); - auto result = fn(); - std::atomic_signal_fence(std::memory_order_seq_cst); - return result; - } + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::arithmetic; }; -// default underlying operations +template <> struct traits { + using op_tag = Subtraction; -template -struct traits { static constexpr bool enabled = true; - static constexpr bool noexcept_invocable = true; - - using value_policy = resolved_value_policy_t; - using type_policy = resolved_type_policy_t; - using error_policy = resolved_error_policy_t; - using concurrency_policy = resolved_concurrency_policy_t; - - using result_type = - type_policy_behavior::template result_type; - - static constexpr result_type invoke(L const &lhs, R const &rhs) noexcept { - return concurrency_policy_behavior< - concurrency_policy, add_tag>::execute([&]() constexpr noexcept { - return error_policy_behavior::template evaluate< - result_type>([&]() constexpr noexcept { - return value_policy_behavior::template add< - result_type>( - type_policy_behavior::template cast_lhs(lhs), - type_policy_behavior::template cast_rhs(rhs)); - }); - }); - } + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::arithmetic; }; -template -struct traits { - static constexpr bool enabled = true; - static constexpr bool noexcept_invocable = true; - - using value_policy = resolved_value_policy_t; - using type_policy = resolved_type_policy_t; - using error_policy = resolved_error_policy_t; - using concurrency_policy = resolved_concurrency_policy_t; - - using result_type = - type_policy_behavior::template result_type; +template <> struct traits { + using op_tag = Multiplication; - static constexpr result_type invoke(L const &lhs, R const &rhs) noexcept { - return concurrency_policy_behavior< - concurrency_policy, sub_tag>::execute([&]() constexpr noexcept { - return error_policy_behavior::template evaluate< - result_type>([&]() constexpr noexcept { - return value_policy_behavior::template sub< - result_type>( - type_policy_behavior::template cast_lhs(lhs), - type_policy_behavior::template cast_rhs(rhs)); - }); - }); - } -}; - -template -struct traits { static constexpr bool enabled = true; - static constexpr bool noexcept_invocable = true; - - using value_policy = resolved_value_policy_t; - using type_policy = resolved_type_policy_t; - using error_policy = resolved_error_policy_t; - using concurrency_policy = resolved_concurrency_policy_t; - - using result_type = - type_policy_behavior::template result_type; - - static constexpr result_type invoke(L const &lhs, R const &rhs) noexcept { - return concurrency_policy_behavior< - concurrency_policy, mul_tag>::execute([&]() constexpr noexcept { - return error_policy_behavior::template evaluate< - result_type>([&]() constexpr noexcept { - return value_policy_behavior::template mul< - result_type>( - type_policy_behavior::template cast_lhs(lhs), - type_policy_behavior::template cast_rhs(rhs)); - }); - }); - } + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::arithmetic; }; -// primitive operations (keep lhs policy set by default) -template -struct traits, primitive, - Policies...> { - static constexpr bool enabled = true; - static constexpr bool noexcept_invocable = true; - - using value_policy = resolved_value_policy_t; - using type_policy = resolved_type_policy_t; - using error_policy = resolved_error_policy_t; - using concurrency_policy = resolved_concurrency_policy_t; - - using rep_type = - type_policy_behavior::template result_type; - using raw_result_type = primitive; - using result_type = std::conditional_t< - std::is_same_v, - std::expected, raw_result_type>; - - static result_type invoke(primitive const &lhs, - primitive const &rhs) { - // Prepare casted representations - rep_type lhs_rep = - type_policy_behavior::template cast_lhs( - lhs.value()); - rep_type rhs_rep = - type_policy_behavior::template cast_rhs( - rhs.value()); - - // If value policy is checked, handle overflow according to error policy. - if constexpr (std::is_same_v) { - if (details::add_overflow(lhs_rep, rhs_rep)) { - if constexpr (std::is_same_v) { - throw std::overflow_error("overflow in add"); - } else if constexpr (std::is_same_v) { - std::terminate(); - } else if constexpr (std::is_same_v) { - return std::unexpected(std::overflow_error("overflow in add")); - } - } - } +template <> struct traits { + using op_tag = Division; - const auto rep = concurrency_policy_behavior::execute([&]() { - return error_policy_behavior::template evaluate([&]() { - return value_policy_behavior::template add(lhs_rep, - rhs_rep); - }); - }); - - if constexpr (std::is_same_v) { - return raw_result_type{rep}; - } else { - return raw_result_type{rep}; - } - } -}; - -template -struct traits, primitive, - Policies...> { static constexpr bool enabled = true; - static constexpr bool noexcept_invocable = true; - - using value_policy = resolved_value_policy_t; - using type_policy = resolved_type_policy_t; - using error_policy = resolved_error_policy_t; - using concurrency_policy = resolved_concurrency_policy_t; - - using rep_type = - type_policy_behavior::template result_type; - using raw_result_type = primitive; - using result_type = std::conditional_t< - std::is_same_v, - std::expected, raw_result_type>; - - static result_type invoke(primitive const &lhs, - primitive const &rhs) { - rep_type lhs_rep = - type_policy_behavior::template cast_lhs( - lhs.value()); - rep_type rhs_rep = - type_policy_behavior::template cast_rhs( - rhs.value()); - - if constexpr (std::is_same_v) { - if (details::sub_overflow(lhs_rep, rhs_rep)) { - if constexpr (std::is_same_v) { - throw std::overflow_error("overflow in sub"); - } else if constexpr (std::is_same_v) { - std::terminate(); - } else if constexpr (std::is_same_v) { - return std::unexpected(std::overflow_error("overflow in sub")); - } - } - } - - const auto rep = concurrency_policy_behavior::execute([&]() { - return error_policy_behavior::template evaluate([&]() { - return value_policy_behavior::template sub< - rep_type>(lhs_rep, rhs_rep); - }); - }); - - if constexpr (std::is_same_v) { - return raw_result_type{rep}; - } else { - return raw_result_type{rep}; - } - } + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::arithmetic; }; -template -struct traits, primitive, - Policies...> { - static constexpr bool enabled = true; - static constexpr bool noexcept_invocable = true; - - using value_policy = resolved_value_policy_t; - using type_policy = resolved_type_policy_t; - using error_policy = resolved_error_policy_t; - using concurrency_policy = resolved_concurrency_policy_t; +template <> struct traits { + using op_tag = Equal; - using rep_type = - type_policy_behavior::template result_type; - using raw_result_type = primitive; - using result_type = std::conditional_t< - std::is_same_v, - std::expected, raw_result_type>; - - static result_type invoke(primitive const &lhs, - primitive const &rhs) { - rep_type lhs_rep = - type_policy_behavior::template cast_lhs( - lhs.value()); - rep_type rhs_rep = - type_policy_behavior::template cast_rhs( - rhs.value()); - - if constexpr (std::is_same_v) { - if (details::mul_overflow(lhs_rep, rhs_rep)) { - if constexpr (std::is_same_v) { - throw std::overflow_error("overflow in mul"); - } else if constexpr (std::is_same_v) { - std::terminate(); - } else if constexpr (std::is_same_v) { - return std::unexpected(std::overflow_error("overflow in mul")); - } - } - } - - const auto rep = concurrency_policy_behavior::execute([&]() { - return error_policy_behavior::template evaluate([&]() { - return value_policy_behavior::template mul< - rep_type>(lhs_rep, rhs_rep); - }); - }); - - if constexpr (std::is_same_v) { - return raw_result_type{rep}; - } else { - return raw_result_type{rep}; - } - } -}; - -struct add_fn { - template - requires binary_operation - auto operator()(L &&lhs, R &&rhs) const - -> result_t { - using impl = traits, std::remove_cvref_t, - Policies...>; - return impl::invoke(lhs, rhs); - } + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::comparison; }; -struct sub_fn { - template - requires binary_operation - auto operator()(L &&lhs, R &&rhs) const - -> result_t { - using impl = traits, std::remove_cvref_t, - Policies...>; - return impl::invoke(lhs, rhs); - } -}; +template <> struct traits { + using op_tag = NotEqual; -struct mul_fn { - template - requires binary_operation - auto operator()(L &&lhs, R &&rhs) const - -> result_t { - using impl = traits, std::remove_cvref_t, - Policies...>; - return impl::invoke(lhs, rhs); - } + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::comparison; }; -inline constexpr add_fn add{}; -inline constexpr sub_fn sub{}; -inline constexpr mul_fn mul{}; - -} // namespace mcpplibs::primitives::operations +} // namespace mcpplibs::primitives::operations \ No newline at end of file diff --git a/src/operations/invoker.cppm b/src/operations/invoker.cppm new file mode 100644 index 0000000..db1788b --- /dev/null +++ b/src/operations/invoker.cppm @@ -0,0 +1,681 @@ +module; + +#include +#include +#include +#include +#include +#include +#include + +export module mcpplibs.primitives.operations.invoker; + +import mcpplibs.primitives.operations.traits; +import mcpplibs.primitives.operations.impl; +import mcpplibs.primitives.policy.handler; +import mcpplibs.primitives.policy.impl; +import mcpplibs.primitives.policy.traits; + +export namespace mcpplibs::primitives::operations::runtime { + +namespace details { + +template +constexpr auto make_error(policy::error::kind kind, char const *reason, + std::optional lhs = std::nullopt, + std::optional rhs = std::nullopt) + -> policy::value::decision { + policy::value::decision out{}; + out.has_value = false; + out.error.code = kind; + out.error.reason = reason; + out.error.lhs_value = lhs; + out.error.rhs_value = rhs; + return out; +} + +template +constexpr auto checked_add(T lhs, T rhs) -> policy::value::decision { + if constexpr (!std::is_integral_v || std::is_same_v) { + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs + rhs); + return out; + } else if constexpr (std::is_unsigned_v) { + auto const maxv = std::numeric_limits::max(); + if (lhs > maxv - rhs) { + return make_error(policy::error::kind::overflow, + "checked addition overflow", lhs, rhs); + } + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs + rhs); + return out; + } else { + auto const maxv = std::numeric_limits::max(); + auto const minv = std::numeric_limits::min(); + if ((rhs > 0) && (lhs > maxv - rhs)) { + return make_error(policy::error::kind::overflow, + "checked addition overflow", lhs, rhs); + } + if ((rhs < 0) && (lhs < minv - rhs)) { + return make_error(policy::error::kind::underflow, + "checked addition underflow", lhs, rhs); + } + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs + rhs); + return out; + } +} + +template +constexpr auto checked_sub(T lhs, T rhs) -> policy::value::decision { + if constexpr (!std::is_integral_v || std::is_same_v) { + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs - rhs); + return out; + } else if constexpr (std::is_unsigned_v) { + if (lhs < rhs) { + return make_error(policy::error::kind::underflow, + "checked subtraction underflow", lhs, rhs); + } + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs - rhs); + return out; + } else { + auto const maxv = std::numeric_limits::max(); + auto const minv = std::numeric_limits::min(); + if ((rhs < 0) && (lhs > maxv + rhs)) { + return make_error(policy::error::kind::overflow, + "checked subtraction overflow", lhs, rhs); + } + if ((rhs > 0) && (lhs < minv + rhs)) { + return make_error(policy::error::kind::underflow, + "checked subtraction underflow", lhs, rhs); + } + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs - rhs); + return out; + } +} + +template +constexpr auto checked_mul(T lhs, T rhs) -> policy::value::decision { + if constexpr (!std::is_integral_v || std::is_same_v) { + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs * rhs); + return out; + } else { + if (lhs == T{} || rhs == T{}) { + policy::value::decision out{}; + out.has_value = true; + out.value = T{}; + return out; + } + + if constexpr (std::is_unsigned_v) { + auto const maxv = std::numeric_limits::max(); + if (lhs > maxv / rhs) { + return make_error(policy::error::kind::overflow, + "checked multiplication overflow", lhs, rhs); + } + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs * rhs); + return out; + } else { + auto const maxv = std::numeric_limits::max(); + auto const minv = std::numeric_limits::min(); + + if (lhs > 0) { + if (rhs > 0) { + if (lhs > maxv / rhs) { + return make_error(policy::error::kind::overflow, + "checked multiplication overflow", lhs, rhs); + } + } else { + if (rhs < minv / lhs) { + return make_error(policy::error::kind::underflow, + "checked multiplication underflow", lhs, rhs); + } + } + } else { + if (rhs > 0) { + if (lhs < minv / rhs) { + return make_error(policy::error::kind::underflow, + "checked multiplication underflow", lhs, rhs); + } + } else { + if (lhs != 0 && rhs < maxv / lhs) { + return make_error(policy::error::kind::overflow, + "checked multiplication overflow", lhs, rhs); + } + } + } + + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs * rhs); + return out; + } + } +} + +template +constexpr auto checked_div(T lhs, T rhs) -> policy::value::decision { + if (rhs == T{}) { + return make_error(policy::error::kind::divide_by_zero, + "checked division by zero", lhs, rhs); + } + + if constexpr (std::is_integral_v && std::is_signed_v) { + auto const minv = std::numeric_limits::min(); + if (lhs == minv && rhs == static_cast(-1)) { + return make_error(policy::error::kind::overflow, + "checked division overflow", lhs, rhs); + } + } + + if constexpr (requires { lhs / rhs; }) { + policy::value::decision out{}; + out.has_value = true; + out.value = static_cast(lhs / rhs); + return out; + } + + return make_error( + policy::error::kind::unspecified, + "checked division not supported for negotiated common type", lhs, rhs); +} + +template +constexpr auto compare_equal(T lhs, T rhs) -> policy::value::decision { + policy::value::decision out{}; + if constexpr (requires { lhs == rhs; }) { + out.has_value = true; + out.value = T{lhs == rhs}; + return out; + } + + return make_error(policy::error::kind::unspecified, + "comparison equality not supported for negotiated " + "common type"); +} + +template +constexpr auto compare_not_equal(T lhs, T rhs) -> policy::value::decision { + policy::value::decision out{}; + if constexpr (requires { lhs != rhs; }) { + out.has_value = true; + out.value = T{lhs != rhs}; + return out; + } + + return make_error(policy::error::kind::unspecified, + "comparison inequality not supported for negotiated " + "common type"); +} + +template +constexpr auto unchecked_add(T lhs, T rhs) -> policy::value::decision { + policy::value::decision out{}; + out.has_value = true; + + if constexpr (requires { lhs + rhs; }) { + out.value = static_cast(lhs + rhs); + return out; + } + + out.value = T{}; + return out; +} + +template +constexpr auto unchecked_sub(T lhs, T rhs) -> policy::value::decision { + policy::value::decision out{}; + out.has_value = true; + + if constexpr (requires { lhs - rhs; }) { + out.value = static_cast(lhs - rhs); + return out; + } + + out.value = T{}; + return out; +} + +template +constexpr auto unchecked_mul(T lhs, T rhs) -> policy::value::decision { + policy::value::decision out{}; + out.has_value = true; + + if constexpr (requires { lhs * rhs; }) { + out.value = static_cast(lhs * rhs); + return out; + } + + out.value = T{}; + return out; +} + +template +constexpr auto unchecked_div(T lhs, T rhs) -> policy::value::decision { + policy::value::decision out{}; + out.has_value = true; + + // Intentionally no guards: unchecked policy delegates error/UB behavior + // to the underlying language/runtime semantics. + out.value = static_cast(lhs / rhs); + return out; +} + +template constexpr auto saturating_add(T lhs, T rhs) -> T { + if constexpr (!std::is_integral_v || std::is_same_v) { + return static_cast(lhs + rhs); + } else if constexpr (std::is_unsigned_v) { + auto const maxv = std::numeric_limits::max(); + return (lhs > maxv - rhs) ? maxv : static_cast(lhs + rhs); + } else { + auto const maxv = std::numeric_limits::max(); + auto const minv = std::numeric_limits::min(); + if ((rhs > 0) && (lhs > maxv - rhs)) { + return maxv; + } + if ((rhs < 0) && (lhs < minv - rhs)) { + return minv; + } + return static_cast(lhs + rhs); + } +} + +template constexpr auto saturating_sub(T lhs, T rhs) -> T { + if constexpr (!std::is_integral_v || std::is_same_v) { + return static_cast(lhs - rhs); + } else if constexpr (std::is_unsigned_v) { + return (lhs < rhs) ? T{} : static_cast(lhs - rhs); + } else { + auto const maxv = std::numeric_limits::max(); + auto const minv = std::numeric_limits::min(); + if ((rhs < 0) && (lhs > maxv + rhs)) { + return maxv; + } + if ((rhs > 0) && (lhs < minv + rhs)) { + return minv; + } + return static_cast(lhs - rhs); + } +} + +template constexpr auto saturating_mul(T lhs, T rhs) -> T { + if constexpr (!std::is_integral_v || std::is_same_v) { + return static_cast(lhs * rhs); + } else { + if (lhs == T{} || rhs == T{}) { + return T{}; + } + + if constexpr (std::is_unsigned_v) { + auto const maxv = std::numeric_limits::max(); + return (lhs > maxv / rhs) ? maxv : static_cast(lhs * rhs); + } else { + auto const maxv = std::numeric_limits::max(); + auto const minv = std::numeric_limits::min(); + + if (lhs > 0) { + if (rhs > 0) { + if (lhs > maxv / rhs) { + return maxv; + } + } else { + if (rhs < minv / lhs) { + return minv; + } + } + } else { + if (rhs > 0) { + if (lhs < minv / rhs) { + return minv; + } + } else { + if (lhs != 0 && rhs < maxv / lhs) { + return maxv; + } + } + } + + return static_cast(lhs * rhs); + } + } +} + +template +constexpr auto make_unsupported(char const *reason) + -> policy::value::decision { + return make_error(policy::error::kind::unspecified, reason); +} + +template +constexpr auto make_div_zero(char const *reason) + -> policy::value::decision { + return make_error(policy::error::kind::divide_by_zero, reason); +} + +constexpr auto apply_runtime_fence(bool enabled) noexcept -> void { + if (!enabled) { + return; + } + + if (!std::is_constant_evaluated()) { + std::atomic_thread_fence(std::memory_order_seq_cst); + } +} + +} // namespace details + +template +struct op_binding { + static constexpr bool enabled = false; + + static constexpr auto apply(CommonRep, CommonRep) + -> policy::value::decision { + policy::value::decision out{}; + out.has_value = false; + out.error.code = policy::error::kind::unspecified; + out.error.reason = "operation binding is not implemented"; + return out; + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + if constexpr (requires { details::checked_add(lhs, rhs); }) { + return details::checked_add(lhs, rhs); + } + + return details::make_unsupported( + "checked addition not supported for negotiated common type"); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + if constexpr (requires { details::checked_sub(lhs, rhs); }) { + return details::checked_sub(lhs, rhs); + } + + return details::make_unsupported( + "checked subtraction not supported for negotiated common type"); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + if constexpr (requires { details::checked_mul(lhs, rhs); }) { + return details::checked_mul(lhs, rhs); + } + + return details::make_unsupported( + "checked multiplication not supported for negotiated common type"); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + if constexpr (requires { details::checked_div(lhs, rhs); }) { + return details::checked_div(lhs, rhs); + } + + return details::make_unsupported( + "checked division not supported for negotiated common type"); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::unchecked_add(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::unchecked_sub(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::unchecked_mul(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::unchecked_div(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + policy::value::decision out{}; + if constexpr (requires { details::saturating_add(lhs, rhs); }) { + out.has_value = true; + out.value = details::saturating_add(lhs, rhs); + return out; + } + return details::make_unsupported( + "saturating addition not supported for negotiated common type"); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + policy::value::decision out{}; + if constexpr (requires { details::saturating_sub(lhs, rhs); }) { + out.has_value = true; + out.value = details::saturating_sub(lhs, rhs); + return out; + } + return details::make_unsupported( + "saturating subtraction not supported for negotiated common type"); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + policy::value::decision out{}; + if constexpr (requires { details::saturating_mul(lhs, rhs); }) { + out.has_value = true; + out.value = details::saturating_mul(lhs, rhs); + return out; + } + return details::make_unsupported( + "saturating multiplication not supported for negotiated common type"); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + policy::value::decision out{}; + + if (rhs == CommonRep{}) { + return details::make_div_zero("saturating division by zero"); + } + + if constexpr (requires { lhs / rhs; }) { + out.has_value = true; + out.value = static_cast(lhs / rhs); + return out; + } + + return details::make_unsupported( + "saturating division not supported for negotiated common type"); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_equal(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_equal(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_equal(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_not_equal(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_not_equal(lhs, rhs); + } +}; + +template +struct op_binding { + static constexpr bool enabled = true; + + static constexpr auto apply(CommonRep lhs, CommonRep rhs) + -> policy::value::decision { + return details::compare_not_equal(lhs, rhs); + } +}; + +template +concept op_binding_available = requires { + requires operation; + requires policy::value_policy; + requires op_binding::enabled; +}; + +template +constexpr auto inject_concurrency() -> policy::concurrency::injection { + static_cast(sizeof(CommonRep)); + static_assert(ConcurrencyHandler::enabled, + "Selected concurrency handler is not enabled"); + static_assert(std::same_as, + "concurrency handler must use policy::concurrency::injection"); + + return ConcurrencyHandler::inject(); +} + +template +constexpr auto run_value(CommonRep lhs, CommonRep rhs, + policy::concurrency::injection const &injection) + -> policy::value::decision { + static_cast(sizeof(ErrorPayload)); + static_assert(ValueHandler::enabled, "Selected value handler is not enabled"); + static_assert( + std::same_as>, + "value handler decision_type must match policy::value::decision"); + + static_assert( + op_binding_available, + "Missing operation binding specialization for this OpTag/common type"); + + details::apply_runtime_fence(injection.fence_before); + + auto decision = op_binding::apply(lhs, rhs); + auto finalized = ValueHandler::finalize(std::move(decision), injection); + + details::apply_runtime_fence(injection.fence_after); + return finalized; +} + +template +constexpr auto resolve_error(policy::error::request const &request) + -> std::expected { + using handler_t = + policy::error::handler; + return handler_t::resolve(request); +} + +} // namespace mcpplibs::primitives::operations::runtime diff --git a/src/operations/operations.cppm b/src/operations/operations.cppm index f7efa82..cabb288 100644 --- a/src/operations/operations.cppm +++ b/src/operations/operations.cppm @@ -4,4 +4,5 @@ export module mcpplibs.primitives.operations; export import mcpplibs.primitives.operations.traits; export import mcpplibs.primitives.operations.impl; +export import mcpplibs.primitives.operations.dispatcher; export import mcpplibs.primitives.operations.operators; \ No newline at end of file diff --git a/src/operations/operators.cppm b/src/operations/operators.cppm index e7e9219..9ecb6e0 100644 --- a/src/operations/operators.cppm +++ b/src/operations/operators.cppm @@ -1,141 +1,130 @@ module; -#include + +#include + export module mcpplibs.primitives.operations.operators; -import mcpplibs.primitives.underlying; -import mcpplibs.primitives.policy; -import mcpplibs.primitives.primitive; +import mcpplibs.primitives.operations.traits; +import mcpplibs.primitives.operations.dispatcher; import mcpplibs.primitives.operations.impl; +import mcpplibs.primitives.primitive.impl; +import mcpplibs.primitives.primitive.traits; +import mcpplibs.primitives.policy.handler; -export namespace mcpplibs::primitives::operators { -template - requires std_numeric -constexpr auto operator~(const primitive &p) { - // Placeholder: unary bitwise NOT not yet forwarded to operations layer. - // TODO: forward to operations::bit_not when implemented. - return primitives::primitive(~p.value()); +export namespace mcpplibs::primitives::operations { + +template +using primitive_dispatch_result_t = std::expected< + typename mcpplibs::primitives::traits::make_primitive_t< + typename dispatcher_meta::common_rep, + typename mcpplibs::primitives::traits::primitive_traits::policies>, + ErrorPayload>; + +template +constexpr auto apply(Lhs const &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + using result_primitive = + typename primitive_dispatch_result_t::value_type; + + auto const raw = dispatch(lhs, rhs); + if (!raw.has_value()) { + return std::unexpected(raw.error()); + } + + return result_primitive{*raw}; } -template - requires std_numeric -constexpr auto operator+(const primitive &p) { - // Placeholder: unary plus not yet forwarded to operations layer. - // TODO: forward to operations::unary_plus when implemented. - return primitives::primitive(+p.value()); +template +constexpr auto add(Lhs const &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply(lhs, rhs); } -template - requires std_numeric -constexpr auto operator-(const primitive &p) { - // Placeholder: unary minus not yet forwarded to operations layer. - // TODO: forward to operations::unary_neg when implemented. - return primitives::primitive(-p.value()); +template +constexpr auto sub(Lhs const &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply(lhs, rhs); } -template - requires std_numeric && std_numeric -constexpr auto operator+(const primitive &lhs, - const primitive &rhs) { - return operations::add(lhs, rhs); +template +constexpr auto mul(Lhs const &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply(lhs, rhs); } -template - requires std_numeric && std_numeric -constexpr auto operator-(const primitive &lhs, - const primitive &rhs) { - return operations::sub(lhs, rhs); +template +constexpr auto div(Lhs const &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply(lhs, rhs); } -template -constexpr auto operator*(const primitive &lhs, - const primitive &rhs) { - return operations::mul(lhs, rhs); +template +constexpr auto equal(Lhs const &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply(lhs, rhs); } -template -constexpr auto operator/(const primitive &lhs, - const primitive &rhs) { - // Placeholder: division not yet forwarded to operations layer. - // TODO: forward to operations::div when implemented (handle policies). - using result_type = std::common_type_t; - return primitives::primitive( - static_cast(lhs.value()) / - static_cast(rhs.value())); +template +constexpr auto not_equal(Lhs const &lhs, Rhs const &rhs) + -> primitive_dispatch_result_t { + return apply(lhs, rhs); } -template -constexpr auto operator%(const primitive &lhs, - const primitive &rhs) { - // Placeholder: modulo not yet forwarded to operations layer. - // TODO: forward to operations::mod when implemented (handle policies). - using result_type = std::common_type_t; - return primitives::primitive( - static_cast(lhs.value()) % - static_cast(rhs.value())); +} // namespace mcpplibs::primitives::operations + +export namespace mcpplibs::primitives::operators { + +template +constexpr auto operator+(Lhs const &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::add(lhs, rhs); } -template -constexpr auto operator<<(const primitive &lhs, - const primitive &rhs) { - // Placeholder: left shift not yet forwarded to operations layer. - // TODO: forward to operations::shl when implemented (handle policies). - using result_type = std::common_type_t; - return primitives::primitive( - static_cast(lhs.value()) - << static_cast(rhs.value())); +template +constexpr auto operator-(Lhs const &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::sub(lhs, rhs); } -template -constexpr auto operator>>(const primitive &lhs, - const primitive &rhs) { - // Placeholder: right shift not yet forwarded to operations layer. - // TODO: forward to operations::shr when implemented (handle policies). - using result_type = std::common_type_t; - return primitives::primitive( - static_cast(lhs.value()) >> - static_cast(rhs.value())); +template +constexpr auto operator*(Lhs const &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::mul(lhs, rhs); } -template -constexpr auto operator&(const primitive &lhs, - const primitive &rhs) { - // Placeholder: bitwise AND not yet forwarded to operations layer. - // TODO: forward to operations::bit_and when implemented (handle policies). - using result_type = std::common_type_t; - return primitives::primitive( - static_cast(lhs.value()) & - static_cast(rhs.value())); +template +constexpr auto operator/(Lhs const &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::div(lhs, rhs); } -template -constexpr auto operator|(const primitive &lhs, - const primitive &rhs) { - // Placeholder: bitwise OR not yet forwarded to operations layer. - // TODO: forward to operations::bit_or when implemented (handle policies). - using result_type = std::common_type_t; - return primitives::primitive( - static_cast(lhs.value()) | - static_cast(rhs.value())); +template +constexpr auto operator==(Lhs const &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::equal(lhs, rhs); } -template -constexpr auto operator^(const primitive &lhs, - const primitive &rhs) { - // Placeholder: bitwise XOR not yet forwarded to operations layer. - // TODO: forward to operations::bit_xor when implemented (handle policies). - using result_type = std::common_type_t; - return primitives::primitive( - static_cast(lhs.value()) ^ - static_cast(rhs.value())); +template +constexpr auto operator!=(Lhs const &lhs, Rhs const &rhs) + -> operations::primitive_dispatch_result_t { + return operations::not_equal(lhs, rhs); } + } // namespace mcpplibs::primitives::operators \ No newline at end of file diff --git a/src/operations/traits.cppm b/src/operations/traits.cppm index ac18323..82976a7 100644 --- a/src/operations/traits.cppm +++ b/src/operations/traits.cppm @@ -1,168 +1,64 @@ -module; -#include -#include -#include - export module mcpplibs.primitives.operations.traits; -import mcpplibs.primitives.policy; -import mcpplibs.primitives.primitive; -import mcpplibs.primitives.underlying; - export namespace mcpplibs::primitives::operations { -struct add_tag {}; -struct sub_tag {}; -struct mul_tag {}; - -template -struct traits { - static constexpr bool enabled = false; - static constexpr bool noexcept_invocable = false; - using result_type = void; -}; - -template -concept binary_operation = - traits, std::remove_cvref_t, - Policies...>::enabled; - -template -concept nothrow_binary_operation = - binary_operation && - traits, std::remove_cvref_t, - Policies...>::noexcept_invocable; - -template -using result_t = - traits, std::remove_cvref_t, - Policies...>::result_type; - -namespace details { - -template -struct first_value_policy_impl { - using type = Fallback; -}; - -template -struct first_value_policy_impl { - using type = std::conditional_t< - policy::value_policy, First, - typename first_value_policy_impl::type>; -}; - -template -struct first_type_policy_impl { - using type = Fallback; +enum class dimension : unsigned char { + unary = 1, + binary = 2, }; -template -struct first_type_policy_impl { - using type = std::conditional_t< - policy::type_policy, First, - typename first_type_policy_impl::type>; +enum class capability : unsigned char { + none = 0, + arithmetic = 1 << 0, + comparison = 1 << 1, + bitwise = 1 << 2, }; -template -struct first_error_policy_impl { - using type = Fallback; -}; - -template -struct first_error_policy_impl { - using type = std::conditional_t< - policy::error_policy, First, - typename first_error_policy_impl::type>; -}; - -template -struct first_concurrency_policy_impl { - using type = Fallback; -}; - -template -struct first_concurrency_policy_impl { - using type = std::conditional_t< - policy::concurrency_policy, First, - typename first_concurrency_policy_impl::type>; -}; - -} // namespace details +constexpr auto has_capability(capability mask, capability cap) noexcept + -> bool { + return (static_cast(mask) & static_cast(cap)) != + 0; +} -template -struct value_policy_behavior { - template - static constexpr T add(T lhs, T rhs) noexcept { - return static_cast(lhs + rhs); - } +template struct traits { + using op_tag = OpTag; - template - static constexpr T sub(T lhs, T rhs) noexcept { - return static_cast(lhs - rhs); - } - - template - static constexpr T mul(T lhs, T rhs) noexcept { - return static_cast(lhs * rhs); - } + // Operation metadata (default-disabled). + // capability_mask is the single source of truth for capability declaration. + static constexpr bool enabled = false; + static constexpr auto arity = static_cast(0); + static constexpr auto capability_mask = capability::none; }; -template -struct type_policy_behavior { - template - using result_type = std::common_type_t; +template +concept operation = traits::enabled; - template - static constexpr Out cast_lhs(In const &value) noexcept { - return static_cast(value); - } - - template - static constexpr Out cast_rhs(In const &value) noexcept { - return static_cast(value); - } -}; +template +concept unary_operation = + operation && traits::arity == dimension::unary; -template -struct error_policy_behavior { - template - static constexpr Result evaluate(Fn &&fn) noexcept(noexcept(fn())) { - return static_cast(std::forward(fn)()); - } -}; +template +concept binary_operation = + operation && traits::arity == dimension::binary; -template -struct concurrency_policy_behavior { - template - static constexpr auto execute(Fn &&fn) noexcept(noexcept(fn())) - -> decltype(fn()) { - return std::forward(fn)(); - } -}; +template +inline constexpr bool op_has_capability_v = + operation && has_capability(traits::capability_mask, Cap); -template -using resolved_value_policy_t = - details::first_value_policy_impl< - std::tuple_element_t<0, policy::common_policies_t>, - Policies...>::type; +template +concept arithmetic_operation = + op_has_capability_v; -template -using resolved_type_policy_t = - details::first_type_policy_impl< - std::tuple_element_t<1, policy::common_policies_t>, - Policies...>::type; +template +concept comparison_operation = + op_has_capability_v; -template -using resolved_error_policy_t = - details::first_error_policy_impl< - std::tuple_element_t<2, policy::common_policies_t>, - Policies...>::type; +template +concept bitwise_operation = op_has_capability_v; -template -using resolved_concurrency_policy_t = - details::first_concurrency_policy_impl< - std::tuple_element_t<3, policy::common_policies_t>, - Policies...>::type; +// Validates that an enabled operation has a non-none capability_mask declared. +template +inline constexpr bool op_capability_valid_v = + !operation || traits::capability_mask != capability::none; -} // namespace mcpplibs::primitives::operations +} // namespace mcpplibs::primitives::operations \ No newline at end of file diff --git a/src/policy/handler.cppm b/src/policy/handler.cppm new file mode 100644 index 0000000..bacf8eb --- /dev/null +++ b/src/policy/handler.cppm @@ -0,0 +1,235 @@ +module; + +#include +#include +#include +#include +#include +#include + +export module mcpplibs.primitives.policy.handler; + +import mcpplibs.primitives.policy.traits; +import mcpplibs.primitives.operations.traits; + +export namespace mcpplibs::primitives::policy { + +namespace error { + +// Unified runtime error kind carried across the policy chain. +enum class kind : unsigned char { + none = 0, + invalid_type_combination, + overflow, + underflow, + divide_by_zero, + domain_error, + unspecified, +}; + +template struct request { + kind code = kind::none; + std::string_view reason{}; + std::optional lhs_value{}; + std::optional rhs_value{}; + std::optional fallback_value{}; +}; + +template +struct handler { + static constexpr bool enabled = false; + using request_type = request; + using result_type = std::expected; + + static constexpr auto resolve(request_type const &) -> result_type { + return std::unexpected(ErrorPayload{}); + } +}; + +template +concept handler_protocol = + error_policy && operations::operation && + (!std::same_as) && requires { + typename handler::request_type; + typename handler::result_type; + { + handler::enabled + } -> std::convertible_to; + requires handler::enabled; + requires std::same_as::request_type, + request>; + requires std::same_as< + typename handler::result_type, + std::expected>; + { + handler::resolve( + std::declval const &>()) + } -> std::same_as::result_type>; + }; + +template +concept handler_available = + handler_protocol && + handler::enabled; + +} // namespace error + +namespace concurrency { + +struct injection { + bool fence_before = false; + bool fence_after = false; +}; + +template +struct handler { + static constexpr bool enabled = false; + using injection_type = injection; + using result_type = std::expected; + + static constexpr auto inject() noexcept -> injection_type { return {}; } +}; + +template +concept handler_protocol = requires { + requires concurrency_policy; + requires operations::operation; + typename handler::injection_type; + typename handler::result_type; + { + handler::enabled + } -> std::convertible_to; + requires handler::enabled; + requires std::same_as< + typename handler::injection_type, + injection>; + requires std::same_as< + typename handler::result_type, + std::expected>; + { + handler::inject() + } noexcept -> std::same_as< + typename handler::injection_type>; +}; + +template +concept handler_available = requires { + requires handler::enabled; + requires handler_protocol; +}; + +} // namespace concurrency + +namespace type { + +template +struct operation_context { + using op_tag = OpTag; + using lhs_rep = LhsRep; + using rhs_rep = RhsRep; + using common_rep = CommonRep; +}; + +// Compile-time type negotiation protocol. +template +struct handler { + static constexpr bool enabled = false; + static constexpr bool allowed = false; + static constexpr unsigned diagnostic_id = 0; + using common_rep = void; +}; + +template +concept handler_protocol = requires { + requires type_policy; + requires operations::operation; + typename handler::common_rep; + { + handler::enabled + } -> std::convertible_to; + { + handler::allowed + } -> std::convertible_to; + { + handler::diagnostic_id + } -> std::convertible_to; + requires handler::enabled; +}; + +template +concept handler_available = requires { + requires handler::enabled; + requires handler_protocol; +}; + +} // namespace type + +namespace value { + +template struct decision { + bool has_value = false; + CommonRep value{}; + error::request error{}; +}; + +// Runtime value-check protocol. +template +struct handler { + static constexpr bool enabled = false; + static constexpr bool may_adjust_value = false; + using decision_type = decision; + using result_type = std::expected; + + static constexpr auto finalize(decision_type decision, + concurrency::injection const &) + -> decision_type { + return decision; + } +}; + +template +concept handler_protocol = + value_policy && operations::operation && + (!std::same_as) && requires { + typename handler::decision_type; + typename handler::result_type; + { + handler::enabled + } -> std::convertible_to; + { + handler::may_adjust_value + } -> std::convertible_to; + requires handler::enabled; + requires std::same_as::decision_type, + decision>; + requires std::same_as< + typename handler::result_type, + std::expected>; + { + handler::finalize( + std::declval>(), + std::declval()) + } -> std::same_as::decision_type>; + }; + +template +concept handler_available = + handler_protocol && + handler::enabled; + +} // namespace value + +} // namespace mcpplibs::primitives::policy diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index 1891055..ff2c8a3 100644 --- a/src/policy/impl.cppm +++ b/src/policy/impl.cppm @@ -1,91 +1,300 @@ module; +#include +#include +#include +#include +#include +#include export module mcpplibs.primitives.policy.impl; +import mcpplibs.primitives.operations.traits; +import mcpplibs.primitives.operations.impl; import mcpplibs.primitives.policy.traits; +import mcpplibs.primitives.policy.handler; +import mcpplibs.primitives.underlying.traits; export namespace mcpplibs::primitives::policy { -template <> struct traits { - using policy_type = checked_value; +namespace value { +struct checked {}; +struct unchecked {}; +struct saturating {}; +} // namespace value + +namespace type { +struct strict {}; +struct compatible {}; +struct transparent {}; +} // namespace type + +namespace error { +struct throwing {}; +struct expected {}; +struct terminate {}; +} // namespace error + +namespace concurrency { +struct none {}; +struct atomic {}; +} // namespace concurrency + +template <> struct traits { + using policy_type = value::checked; static constexpr bool enabled = true; static constexpr auto kind = category::value; }; -template <> struct traits { - using policy_type = unchecked_value; +template <> struct traits { + using policy_type = value::unchecked; static constexpr bool enabled = true; static constexpr auto kind = category::value; }; -template <> struct traits { - using policy_type = saturating_value; +template <> struct traits { + using policy_type = value::saturating; static constexpr bool enabled = true; static constexpr auto kind = category::value; }; -template <> struct traits { - using policy_type = strict_type; +template <> struct traits { + using policy_type = type::strict; static constexpr bool enabled = true; static constexpr auto kind = category::type; }; -template <> struct traits { - using policy_type = category_compatible_type; +template <> struct traits { + using policy_type = type::compatible; static constexpr bool enabled = true; static constexpr auto kind = category::type; }; -template <> struct traits { - using policy_type = transparent_type; +template <> struct traits { + using policy_type = type::transparent; static constexpr bool enabled = true; static constexpr auto kind = category::type; }; -template <> struct traits { - using policy_type = throw_error; +template <> struct traits { + using policy_type = error::throwing; static constexpr bool enabled = true; static constexpr auto kind = category::error; }; -template <> struct traits { - using policy_type = expected_error; +template <> struct traits { + using policy_type = error::expected; static constexpr bool enabled = true; static constexpr auto kind = category::error; }; -template <> struct traits { - using policy_type = terminate_error; +template <> struct traits { + using policy_type = error::terminate; static constexpr bool enabled = true; static constexpr auto kind = category::error; }; -template <> struct traits { - using policy_type = single_thread; +template <> struct traits { + using policy_type = concurrency::none; static constexpr bool enabled = true; static constexpr auto kind = category::concurrency; }; -template <> struct traits { - using policy_type = atomic; +template <> struct traits { + using policy_type = concurrency::atomic; static constexpr bool enabled = true; static constexpr auto kind = category::concurrency; }; -template -concept policy_type = traits

::enabled; +namespace defaults { +using value = value::checked; +using type = type::strict; +using error = error::throwing; +using concurrency = concurrency::none; +} // namespace defaults + +namespace details { + +template +inline constexpr bool is_arithmetic_operation_v = + operations::op_has_capability_v; + +template +inline constexpr bool is_boolean_or_character_v = std_bool || std_char; + +template +inline constexpr bool rejects_arithmetic_for_boolean_or_character_v = + is_arithmetic_operation_v && + (is_boolean_or_character_v || is_boolean_or_character_v); + +} // namespace details + +// Default protocol specializations. +template +struct type::handler { + static constexpr bool enabled = true; + static constexpr bool allowed = + std::same_as && + !details::rejects_arithmetic_for_boolean_or_character_v; + static constexpr unsigned diagnostic_id = + details::rejects_arithmetic_for_boolean_or_character_v + ? 3u + : (allowed ? 0u : 1u); + using common_rep = std::conditional_t; +}; + +template +struct type::handler { + static constexpr bool enabled = true; + static constexpr bool allowed = + std::is_arithmetic_v && std::is_arithmetic_v && + !details::rejects_arithmetic_for_boolean_or_character_v; + static constexpr unsigned diagnostic_id = + details::rejects_arithmetic_for_boolean_or_character_v + ? 3u + : (allowed ? 0u : 2u); + using common_rep = + std::conditional_t, void>; +}; + +template +struct type::handler { + static constexpr bool enabled = true; + static constexpr bool allowed = + !details::rejects_arithmetic_for_boolean_or_character_v; + static constexpr unsigned diagnostic_id = allowed ? 0u : 3u; + using common_rep = std::common_type_t; +}; + +template +struct concurrency::handler { + static constexpr bool enabled = true; + static constexpr bool requires_external_sync = false; + using injection_type = concurrency::injection; + using result_type = std::expected; -template -concept value_policy = policy_type

&& (traits

::kind == category::value); + static constexpr auto inject() noexcept -> injection_type { + return injection_type{}; + } +}; -template -concept type_policy = policy_type

&& (traits

::kind == category::type); +template +struct concurrency::handler { + static constexpr bool enabled = true; + static constexpr bool requires_external_sync = true; + using injection_type = concurrency::injection; + using result_type = std::expected; -template -concept error_policy = policy_type

&& (traits

::kind == category::error); + static constexpr auto inject() noexcept -> injection_type { + injection_type out{}; + out.fence_before = true; + out.fence_after = true; + return out; + } +}; -template -concept concurrency_policy = - policy_type

&& (traits

::kind == category::concurrency); +template +struct value::handler { + static constexpr bool enabled = true; + static constexpr bool may_adjust_value = false; + using decision_type = value::decision; + using result_type = std::expected; + + static constexpr auto finalize(decision_type decision, + concurrency::injection const &) noexcept + -> decision_type { + return decision; + } +}; + +template +struct value::handler { + static constexpr bool enabled = true; + static constexpr bool may_adjust_value = false; + using decision_type = value::decision; + using result_type = std::expected; + + static constexpr auto finalize(decision_type decision, + concurrency::injection const &) noexcept + -> decision_type { + return decision; + } +}; + +template +struct value::handler { + static constexpr bool enabled = true; + static constexpr bool may_adjust_value = true; + using decision_type = value::decision; + using result_type = std::expected; + + static constexpr auto finalize(decision_type decision, + concurrency::injection const &) noexcept + -> decision_type { + return decision; + } +}; + +namespace details { +template +constexpr auto to_error_payload(error::kind kind) -> ErrorPayload { + if constexpr (std::same_as) { + return kind; + } else { + static_cast(kind); + return ErrorPayload{}; + } +} +} // namespace details + +template +struct error::handler { + static constexpr bool enabled = true; + static constexpr bool converts_to_expected = true; + using request_type = error::request; + using result_type = std::expected; + + static auto resolve(request_type const &request) -> result_type { + throw std::runtime_error(std::string{request.reason}); + } +}; + +template +struct error::handler { + static constexpr bool enabled = true; + static constexpr bool converts_to_expected = true; + using request_type = error::request; + using result_type = std::expected; + + static constexpr auto resolve(request_type const &request) -> result_type { + return std::unexpected( + details::to_error_payload(request.code)); + } +}; + +template +struct error::handler { + static constexpr bool enabled = true; + static constexpr bool converts_to_expected = false; + using request_type = error::request; + using result_type = std::expected; + + [[noreturn]] static auto resolve(request_type const &) -> result_type { + std::terminate(); + } +}; } // namespace mcpplibs::primitives::policy diff --git a/src/policy/policy.cppm b/src/policy/policy.cppm index 1b1af3f..744851b 100644 --- a/src/policy/policy.cppm +++ b/src/policy/policy.cppm @@ -4,4 +4,6 @@ export module mcpplibs.primitives.policy; // Entry module for policy: export traits and impl submodules. export import mcpplibs.primitives.policy.traits; +export import mcpplibs.primitives.policy.handler; export import mcpplibs.primitives.policy.impl; +export import mcpplibs.primitives.policy.utility; diff --git a/src/policy/traits.cppm b/src/policy/traits.cppm index 6b2af3d..c491a75 100644 --- a/src/policy/traits.cppm +++ b/src/policy/traits.cppm @@ -1,6 +1,4 @@ module; -#include -#include export module mcpplibs.primitives.policy.traits; @@ -11,118 +9,26 @@ export namespace mcpplibs::primitives::policy { enum class category { value, type, error, concurrency }; -// Built-in policy tags (defined here so `common_policies` can live in traits). -struct checked_value {}; -struct unchecked_value {}; -struct saturating_value {}; - -struct strict_type {}; -struct category_compatible_type {}; -struct transparent_type {}; - -struct throw_error {}; -struct expected_error {}; -struct terminate_error {}; - -struct single_thread {}; -struct atomic {}; - template struct traits { using policy_type = void; static constexpr bool enabled = false; static constexpr auto kind = static_cast(-1); }; -// Customizable priority hooks — users can specialize these templates to -// change how the library decides which policy wins when multiple are -// provided. Specializations live in implementation or user code. -template struct priority_value { - using type = std::tuple<>; -}; -template struct priority_type { - using type = std::tuple<>; -}; -template struct priority_error { - using type = std::tuple<>; -}; -template struct priority_concurrency { - using type = std::tuple<>; -}; - -// Library default priority ordering for built-in tags. Users can specialize -// these hooks in their own code to change global priority ordering. -template <> struct priority_value { - using type = std::tuple; -}; +template +concept policy_type = traits

::enabled; -template <> struct priority_type { - using type = - std::tuple; -}; +template +concept value_policy = policy_type

&& (traits

::kind == category::value); -template <> struct priority_error { - using type = std::tuple; -}; - -template <> struct priority_concurrency { - using type = std::tuple; -}; +template +concept type_policy = policy_type

&& (traits

::kind == category::type); -// Default hooks for library defaults. Implementations should specialize -// these in the implementation submodule to bind to concrete tags. -// Library defaults (built-in tags). -using default_value = checked_value; -using default_type = strict_type; -using default_error = throw_error; -using default_concurrency = single_thread; - -// Internal helpers used by the merging algorithm. -namespace details { -template struct contains : std::false_type {}; -template -struct contains - : std::conditional_t, std::true_type, - contains> {}; - -template -struct pick_first_from_priority_impl; - -template -struct pick_first_from_priority_impl< - Default, std::tuple, Ps...> { - using type = std::conditional_t< - contains::value, FirstPriority, - typename pick_first_from_priority_impl< - Default, std::tuple, Ps...>::type>; -}; - -template -struct pick_first_from_priority_impl, Ps...> { - using type = Default; -}; - -} // namespace details - -// Primary merging metafunction. Consumers can use `common_policies_t<...>`. -template struct common_policies { - using value_policy = details::pick_first_from_priority_impl< - default_value, priority_value<>::type, Ps...>::type; - - using type_policy = details::pick_first_from_priority_impl< - default_type, priority_type<>::type, Ps...>::type; - - using error_policy = details::pick_first_from_priority_impl< - default_error, priority_error<>::type, Ps...>::type; - - using concurrency_policy = details::pick_first_from_priority_impl< - default_concurrency, priority_concurrency<>::type, Ps...>::type; - - using type = - std::tuple; -}; +template +concept error_policy = policy_type

&& (traits

::kind == category::error); -template -using common_policies_t = common_policies::type; +template +concept concurrency_policy = + policy_type

&& (traits

::kind == category::concurrency); } // namespace mcpplibs::primitives::policy diff --git a/src/policy/utility.cppm b/src/policy/utility.cppm new file mode 100644 index 0000000..51de8c7 --- /dev/null +++ b/src/policy/utility.cppm @@ -0,0 +1,90 @@ +module; +#include +#include + +export module mcpplibs.primitives.policy.utility; + +import mcpplibs.primitives.policy.traits; +import mcpplibs.primitives.policy.impl; + +namespace mcpplibs::primitives::policy::details { + +template struct contains : std::false_type {}; +template +struct contains + : std::conditional_t, std::true_type, + contains> {}; + +template +struct pick_first_from_priority_impl; + +template +struct pick_first_from_priority_impl< + Default, std::tuple, Ps...> { + using type = std::conditional_t< + contains::value, FirstPriority, + typename pick_first_from_priority_impl< + Default, std::tuple, Ps...>::type>; +}; + +template +struct pick_first_from_priority_impl, Ps...> { + using type = Default; +}; + +} // namespace mcpplibs::primitives::policy::details + +export namespace mcpplibs::primitives::policy { + +// Users can specialize these templates to customize policy selection order. +template struct priority_value { + using type = std::tuple<>; +}; +template struct priority_type { + using type = std::tuple<>; +}; +template struct priority_error { + using type = std::tuple<>; +}; +template struct priority_concurrency { + using type = std::tuple<>; +}; + +template <> struct priority_value { + using type = std::tuple; +}; + +template <> struct priority_type { + using type = std::tuple; +}; + +template <> struct priority_error { + using type = std::tuple; +}; + +template <> struct priority_concurrency { + using type = std::tuple; +}; + +template struct common_policies { + using value_policy = details::pick_first_from_priority_impl< + defaults::value, priority_value<>::type, Ps...>::type; + + using type_policy = details::pick_first_from_priority_impl< + defaults::type, priority_type<>::type, Ps...>::type; + + using error_policy = details::pick_first_from_priority_impl< + defaults::error, priority_error<>::type, Ps...>::type; + + using concurrency_policy = details::pick_first_from_priority_impl< + defaults::concurrency, priority_concurrency<>::type, Ps...>::type; + + using type = + std::tuple; +}; + +template +using common_policies_t = common_policies::type; + +} // namespace mcpplibs::primitives::policy diff --git a/src/primitive/traits.cppm b/src/primitive/traits.cppm index fb93e8d..4328827 100644 --- a/src/primitive/traits.cppm +++ b/src/primitive/traits.cppm @@ -8,7 +8,7 @@ import mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.policy; import mcpplibs.primitives.underlying; -// Internal implementation details — not exported. +// Internal implementation details - not exported. namespace mcpplibs::primitives::traits::details { using policy_category = policy::category; @@ -60,20 +60,13 @@ struct resolve_policy_impl { using type = std::conditional_t< std::is_same_v, std::conditional_t< - C == policy_category::value, - policy::default_value, + C == policy_category::value, policy::defaults::value, std::conditional_t< - C == policy_category::type, - policy::default_type, - std::conditional_t< - C == policy_category::error, - policy::default_error, - policy::default_concurrency - > - > - >, - found - >; + C == policy_category::type, policy::defaults::type, + std::conditional_t>>, + found>; }; template @@ -99,8 +92,8 @@ template using make_primitive_t = make_primitive::type; using default_policies = - std::tuple; + std::tuple; template struct primitive_traits; diff --git a/src/underlying/traits.cppm b/src/underlying/traits.cppm index f248bd9..c63396a 100644 --- a/src/underlying/traits.cppm +++ b/src/underlying/traits.cppm @@ -26,7 +26,7 @@ concept std_integer = std::integral> && (!std_bool) && (!std_char); template -concept std_numeric = std_integer || std_floating; +concept std_numeric = std_integer || std_floating; template concept std_underlying_type = @@ -59,7 +59,7 @@ template struct traits { }; } // namespace underlying -} +} // namespace mcpplibs::primitives namespace mcpplibs::primitives::underlying::details { @@ -91,7 +91,24 @@ concept has_rep_bridge = template concept has_std_rep_type = - has_rep_type && std_underlying_type>::rep_type>; + has_rep_type && + std_underlying_type>::rep_type>; + +template +concept has_custom_numeric_rep_type = + has_rep_type && + requires(typename traits>::rep_type a, + typename traits>::rep_type b) { + { a + b }; + { a - b }; + { a * b }; + { a / b }; + { a == b } -> std::convertible_to; + }; + +template +concept has_supported_rep_type = + has_std_rep_type || has_custom_numeric_rep_type; template consteval category category_of_std_underlying_type() { @@ -108,10 +125,14 @@ consteval category category_of_std_underlying_type() { template concept has_consistent_category = - has_category && has_std_rep_type && - (traits>::kind == - category_of_std_underlying_type< - typename traits>::rep_type>()); + has_category && has_supported_rep_type && + ((has_std_rep_type && + (traits>::kind == + category_of_std_underlying_type< + typename traits>::rep_type>())) || + (!has_std_rep_type && + (traits>::kind == category::integer || + traits>::kind == category::floating))); } // namespace mcpplibs::primitives::underlying::details @@ -119,33 +140,32 @@ export namespace mcpplibs::primitives { template concept underlying_type = - underlying::details::enabled && - underlying::details::has_category && + underlying::details::enabled && underlying::details::has_category && underlying::details::has_rep_bridge && - underlying::details::has_std_rep_type && + underlying::details::has_supported_rep_type && underlying::details::has_consistent_category; template concept boolean_underlying_type = underlying_type && (underlying::traits>::kind == - underlying::category::boolean); + underlying::category::boolean); template concept character_underlying_type = underlying_type && (underlying::traits>::kind == - underlying::category::character); + underlying::category::character); template concept integer_underlying_type = underlying_type && (underlying::traits>::kind == - underlying::category::integer); + underlying::category::integer); template concept floating_underlying_type = underlying_type && (underlying::traits>::kind == - underlying::category::floating); + underlying::category::floating); template -concept numeric_underlying_type = integer_underlying_type || floating_underlying_type; +concept numeric_underlying_type = + integer_underlying_type || floating_underlying_type; } // namespace mcpplibs::primitives - diff --git a/tests/basic/test_operations.cpp b/tests/basic/test_operations.cpp index a74c136..e321cb3 100644 --- a/tests/basic/test_operations.cpp +++ b/tests/basic/test_operations.cpp @@ -1,185 +1,338 @@ -#include -#include #include +#include +#include +#include +#include +#include import mcpplibs.primitives; using namespace mcpplibs::primitives; -namespace { - -struct debug_value_policy {}; -struct debug_type_policy {}; -struct debug_error_policy {}; -struct debug_concurrency_policy {}; - -} // namespace - -template <> -struct policy::traits { - using policy_type = debug_value_policy; - static constexpr bool enabled = true; - static constexpr auto kind = category::value; -}; - -template <> -struct policy::traits { - using policy_type = debug_type_policy; - static constexpr bool enabled = true; - static constexpr auto kind = category::type; -}; - -template <> -struct policy::traits { - using policy_type = debug_error_policy; - static constexpr bool enabled = true; - static constexpr auto kind = category::error; -}; - -template <> -struct policy::traits { - using policy_type = debug_concurrency_policy; - static constexpr bool enabled = true; - static constexpr auto kind = - category::concurrency; -}; - -template -struct operations::value_policy_behavior< - debug_value_policy, OpTag> { - template - static constexpr T add(T lhs, T rhs) noexcept { - return static_cast(lhs + rhs + 100); - } +TEST(OperationsTest, AddReturnsExpectedPrimitive) { + using lhs_t = primitive; + using rhs_t = primitive; - template - static constexpr T sub(T lhs, T rhs) noexcept { - return static_cast(lhs - rhs - 100); - } + auto const lhs = lhs_t{10}; + auto const rhs = rhs_t{32}; - template - static constexpr T mul(T lhs, T rhs) noexcept { - return static_cast(lhs * rhs * 2); - } -}; + auto const result = operations::add(lhs, rhs); -template -struct operations::type_policy_behavior< - debug_type_policy, OpTag> { - template - using result_type = long long; + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} - template - static constexpr Out cast_lhs(In const &value) noexcept { - return static_cast(value + 1); - } +TEST(OperationsTest, DivisionByZeroReturnsError) { + using value_t = primitive; - template - static constexpr Out cast_rhs(In const &value) noexcept { - return static_cast(value + 2); - } -}; - -template -struct operations::error_policy_behavior< - debug_error_policy, OpTag> { - template - static constexpr Result evaluate(Fn &&fn) noexcept(noexcept(fn())) { - return static_cast(fn() + static_cast(1000)); - } -}; - -template -struct operations::concurrency_policy_behavior< - debug_concurrency_policy, OpTag> { - template - static constexpr auto execute(Fn &&fn) noexcept(noexcept(fn())) - -> decltype(fn()) { - return fn() + 7; - } -}; + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + auto const result = operations::div(lhs, rhs); + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::divide_by_zero); +} -template <> -struct operations::value_policy_behavior< - debug_value_policy, operations::add_tag> { - template - static constexpr T add(T lhs, T rhs) noexcept { - return static_cast(lhs + rhs + 200); - } +TEST(OperationsTest, SaturatingAdditionClampsUnsignedOverflow) { + using value_t = primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), static_cast(65535)); +} + +TEST(OperationsTest, CheckedAdditionReportsUnsignedOverflow) { + using value_t = + primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), policy::error::kind::overflow); +} + +TEST(OperationsTest, UncheckedAdditionWrapsUnsignedOverflow) { + using value_t = + primitive; + + auto const lhs = value_t{static_cast(65530)}; + auto const rhs = value_t{static_cast(20)}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), static_cast(14)); +} - template - static constexpr T sub(T lhs, T rhs) noexcept { - return static_cast(lhs - rhs - 100); +TEST(OperationsTest, UncheckedDivisionUsesRawArithmeticWhenValid) { + using value_t = + primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{4}; + + auto const result = operations::div(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 25); +} + +TEST(OperationsTest, AtomicPolicyPathReturnsExpectedValue) { + using value_t = primitive; + + auto const lhs = value_t{12}; + auto const rhs = value_t{30}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, AtomicPolicyConcurrentInvocationsRemainConsistent) { + using value_t = primitive; + + constexpr int kThreadCount = 8; + constexpr int kIterationsPerThread = 20000; + + auto const lhs = value_t{12}; + auto const rhs = value_t{30}; + + std::atomic mismatch_count{0}; + std::atomic error_count{0}; + std::atomic start{false}; + + std::vector workers; + workers.reserve(kThreadCount); + + for (int i = 0; i < kThreadCount; ++i) { + workers.emplace_back([&]() { + while (!start.load(std::memory_order_acquire)) { + } + + for (int n = 0; n < kIterationsPerThread; ++n) { + auto const result = operations::add(lhs, rhs); + if (!result.has_value()) { + error_count.fetch_add(1, std::memory_order_relaxed); + continue; + } + + if (result->value() != 42) { + mismatch_count.fetch_add(1, std::memory_order_relaxed); + } + } + }); } - template - static constexpr T mul(T lhs, T rhs) noexcept { - return static_cast(lhs * rhs * 2); + start.store(true, std::memory_order_release); + + for (auto &worker : workers) { + worker.join(); } -}; -TEST(OperationsTest, UnderlyingOperationsWork) { - static_assert(operations::binary_operation); - static_assert(operations::binary_operation); - static_assert(operations::binary_operation); + EXPECT_EQ(error_count.load(std::memory_order_relaxed), 0); + EXPECT_EQ(mismatch_count.load(std::memory_order_relaxed), 0); +} + +TEST(OperationsTest, StrictTypeRejectsMixedTypesAtCompileTime) { + using lhs_t = primitive; + using rhs_t = primitive; + + using strict_handler = + policy::type::handler; + using strict_meta = operations::dispatcher_meta; + + static_assert(strict_handler::enabled); + static_assert(!strict_handler::allowed); + static_assert(std::is_same_v); - EXPECT_EQ(operations::add(2, 3), 5); - EXPECT_EQ(operations::sub(9, 4), 5); - EXPECT_EQ(operations::mul(3, 4), 12); + EXPECT_EQ(strict_handler::diagnostic_id, 1u); } -TEST(OperationsTest, PrimitiveOperationsWork) { +TEST(OperationsTest, StrictTypeAllowsSameTypeAtRuntime) { + using value_t = primitive; + + auto const lhs = value_t{19}; + auto const rhs = value_t{23}; + + auto const result = operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, BoolUnderlyingRejectsArithmeticOperationsAtCompileTime) { + using value_t = primitive; + using bool_handler = policy::type::handler; + using bool_meta = operations::dispatcher_meta; + + static_assert(bool_handler::enabled); + static_assert(!bool_handler::allowed); + static_assert(std::is_same_v); + + EXPECT_EQ(bool_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, CharUnderlyingRejectsArithmeticEvenWithTransparentType) { + using value_t = primitive; + using char_handler = policy::type::handler; + using char_meta = operations::dispatcher_meta; + + static_assert(char_handler::enabled); + static_assert(!char_handler::allowed); + static_assert(std::is_same_v); + + EXPECT_EQ(char_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, SignedAndUnsignedCharRejectArithmeticAtCompileTime) { + using signed_handler = + policy::type::handler; + using unsigned_handler = + policy::type::handler; + + static_assert(signed_handler::enabled); + static_assert(!signed_handler::allowed); + static_assert(unsigned_handler::enabled); + static_assert(!unsigned_handler::allowed); + + EXPECT_EQ(signed_handler::diagnostic_id, 3u); + EXPECT_EQ(unsigned_handler::diagnostic_id, 3u); +} + +TEST(OperationsTest, BoolUnderlyingAllowsComparisonOperations) { + using value_t = primitive; + + auto const lhs = value_t{true}; + auto const rhs = value_t{false}; + + auto const eq_result = operations::equal(lhs, rhs); + auto const ne_result = operations::not_equal(lhs, rhs); + + ASSERT_TRUE(eq_result.has_value()); + ASSERT_TRUE(ne_result.has_value()); + EXPECT_FALSE(eq_result->value()); + EXPECT_TRUE(ne_result->value()); +} + +TEST(OperationsTest, CharUnderlyingAllowsComparisonWithTransparentType) { + using value_t = primitive; + + auto const lhs = value_t{'a'}; + auto const rhs = value_t{'a'}; + + auto const eq_result = operations::equal(lhs, rhs); + + ASSERT_TRUE(eq_result.has_value()); + EXPECT_EQ(eq_result->value(), static_cast(1)); +} + +TEST(OperationsTest, PrimitiveAliasWorksWithFrameworkOperators) { + using namespace mcpplibs::primitives::types; + using namespace mcpplibs::primitives::operators; + using value_t = I32; + + auto const lhs = value_t{20}; + auto const rhs = value_t{22}; + + auto const result = lhs + rhs; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(OperationsTest, PrimitiveAliasMixesWithBuiltinArithmeticExplicitly) { using namespace mcpplibs::primitives::types; + using namespace mcpplibs::primitives::operators; + using value_t = I32; + + static_assert(!std::is_convertible_v); + + auto const lhs = value_t{40}; + auto const mixed = static_cast(lhs) + 2; + EXPECT_EQ(mixed, 42); + + auto const wrapped = value_t{mixed}; + auto const result = wrapped + value_t{1}; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 43); +} + +TEST(OperationsTest, OperatorEqualDelegatesToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = primitive; + + auto const lhs = value_t{7}; + auto const rhs = value_t{7}; + + auto const result = (lhs == rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 1); +} - using lhs_t = I8<>; - using rhs_t = I8<>; - - lhs_t lhs{10}; - rhs_t rhs{5}; - - auto added = operations::add(lhs, rhs); - auto subed = operations::sub(lhs, rhs); - auto muled = operations::mul(lhs, rhs); - - EXPECT_EQ(added.value(), 15); - EXPECT_EQ(subed.value(), 5); - EXPECT_EQ(muled.value(), 50); -} - -TEST(OperationsTest, PolicyBehaviorTraitsCoverAllPolicyCategories) { - EXPECT_EQ((operations::add.operator()< - debug_value_policy, debug_type_policy, debug_error_policy, - debug_concurrency_policy>(2, 3)), - 1215); - EXPECT_EQ((operations::sub.operator()< - debug_value_policy, debug_type_policy, debug_error_policy, - debug_concurrency_policy>(9, 4)), - 911); - EXPECT_EQ((operations::mul.operator()< - debug_value_policy, debug_type_policy, debug_error_policy, - debug_concurrency_policy>(3, 4)), - 1055); -} - - -TEST(OperationsTest, BuiltinPolicyTagsAreSpecialized) { - static_assert(std::is_same_v< - operations::result_t, - int>); - static_assert(std::is_same_v< - operations::result_t, - double>); - - constexpr int kMax = std::numeric_limits::max(); - constexpr int kMin = std::numeric_limits::min(); - - EXPECT_EQ((operations::add.operator()(kMax, 1)), - kMax); - EXPECT_EQ((operations::sub.operator()(kMin, 1)), - kMin); - EXPECT_EQ((operations::mul.operator()(kMax, 2)), - kMax); +TEST(OperationsTest, OperatorPlusDelegatesToDispatcher) { + using namespace mcpplibs::primitives::operators; + using value_t = primitive; + + auto const lhs = value_t{7}; + auto const rhs = value_t{8}; + + auto const result = lhs + rhs; + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 15); +} + +TEST(OperationsTest, ThrowErrorPolicyThrowsException) { + using value_t = primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + + EXPECT_THROW((void)operations::div(lhs, rhs), std::runtime_error); +} + +TEST(OperationsTest, ThrowErrorPolicyExceptionHasReasonMessage) { + using value_t = primitive; + + auto const lhs = value_t{100}; + auto const rhs = value_t{0}; + + try { + (void)operations::div(lhs, rhs); + FAIL() << "Expected std::runtime_error to be thrown"; + } catch (std::runtime_error const &e) { + EXPECT_NE(std::string(e.what()).find("division by zero"), + std::string::npos); + } catch (...) { + FAIL() << "Expected std::runtime_error"; + } } diff --git a/tests/basic/test_policies.cpp b/tests/basic/test_policies.cpp index 5c0c2d0..763073f 100644 --- a/tests/basic/test_policies.cpp +++ b/tests/basic/test_policies.cpp @@ -1,47 +1,145 @@ #include - +#include import mcpplibs.primitives; - using namespace mcpplibs::primitives; +namespace { +struct NullCapabilityProbe {}; +} // namespace + +template <> struct operations::traits { + using op_tag = NullCapabilityProbe; + + static constexpr bool enabled = true; + static constexpr auto arity = dimension::binary; + static constexpr auto capability_mask = capability::none; +}; + TEST(PolicyTraitsTest, BuiltinPoliciesHaveCategories) { using namespace policy; - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, policy::category::value); - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, policy::category::type); - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, policy::category::type); - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, policy::category::error); + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::error); - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, policy::category::error); - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, + policy::category::concurrency); + + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, policy::category::concurrency); - EXPECT_TRUE((policy::traits::enabled)); - EXPECT_EQ(policy::traits::kind, + EXPECT_TRUE((policy::traits::enabled)); + EXPECT_EQ(policy::traits::kind, policy::category::value); - EXPECT_TRUE((policy_type)); - EXPECT_TRUE((policy_type)); + EXPECT_TRUE((policy_type)); + EXPECT_TRUE((policy_type)); EXPECT_FALSE((policy_type)); - EXPECT_TRUE((std::is_same_v)); EXPECT_TRUE( - (std::is_same_v)); + (std::is_same_v)); + EXPECT_TRUE((std::is_same_v)); +} + +TEST(PolicyConcurrencyTest, AtomicInjectsFences) { + using atomic_handler = + policy::concurrency::handler; + using single_handler = policy::concurrency::handler; + + auto const atomic_injection = atomic_handler::inject(); + auto const single_injection = single_handler::inject(); + + EXPECT_TRUE(atomic_injection.fence_before); + EXPECT_TRUE(atomic_injection.fence_after); + EXPECT_FALSE(single_injection.fence_before); + EXPECT_FALSE(single_injection.fence_after); +} + +TEST(PolicyProtocolTest, BuiltinHandlersSatisfyProtocolConcepts) { + static_assert(policy::type::handler_protocol); + static_assert(policy::concurrency::handler_protocol); + static_assert(policy::value::handler_protocol); + static_assert(policy::error::handler_protocol); + + EXPECT_TRUE((policy::type::handler_protocol)); + EXPECT_TRUE((policy::concurrency::handler_protocol)); + EXPECT_TRUE((policy::value::handler_protocol)); + EXPECT_TRUE((policy::error::handler_protocol)); +} + +TEST(OperationTraitsTest, BuiltinArithmeticOperationsExposeCapability) { + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_FALSE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); + EXPECT_TRUE( + (operations::op_has_capability_v)); +} + +TEST(OperationTraitsTest, BuiltinOperationsHaveNonNullCapabilityMask) { + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); + EXPECT_TRUE((operations::op_capability_valid_v)); +} + +TEST(OperationTraitsTest, NullCapabilityMaskIsDetectedAtCompileTime) { + static_assert(!operations::op_capability_valid_v); + EXPECT_FALSE((operations::op_capability_valid_v)); } // Use the existing test runner main from other test translation unit. diff --git a/tests/basic/test_templates.cpp b/tests/basic/test_templates.cpp index 2c8ca8e..e68c560 100644 --- a/tests/basic/test_templates.cpp +++ b/tests/basic/test_templates.cpp @@ -20,25 +20,116 @@ struct BadKind { int value; }; +struct BigIntLike { + long long value; + + friend constexpr auto operator+(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(BigIntLike lhs, BigIntLike rhs) noexcept + -> BigIntLike { + return BigIntLike{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(BigIntLike lhs, BigIntLike rhs) noexcept + -> bool { + return lhs.value == rhs.value; + } +}; + +struct BadCustomKind { + long long value; + + friend constexpr auto operator+(BadCustomKind lhs, BadCustomKind rhs) noexcept + -> BadCustomKind { + return BadCustomKind{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(BadCustomKind lhs, BadCustomKind rhs) noexcept + -> BadCustomKind { + return BadCustomKind{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(BadCustomKind lhs, BadCustomKind rhs) noexcept + -> BadCustomKind { + return BadCustomKind{lhs.value * rhs.value}; + } + + friend constexpr auto operator/(BadCustomKind lhs, BadCustomKind rhs) noexcept + -> BadCustomKind { + return BadCustomKind{lhs.value / rhs.value}; + } + + friend constexpr auto operator==(BadCustomKind lhs, + BadCustomKind rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct MissingDivisionLike { + long long value; + + friend constexpr auto operator+(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value + rhs.value}; + } + + friend constexpr auto operator-(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value - rhs.value}; + } + + friend constexpr auto operator*(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept + -> MissingDivisionLike { + return MissingDivisionLike{lhs.value * rhs.value}; + } + + friend constexpr auto operator==(MissingDivisionLike lhs, + MissingDivisionLike rhs) noexcept -> bool { + return lhs.value == rhs.value; + } +}; + +struct NonNegativeInt { + int value; +}; + } // namespace -template <> -struct mcpplibs::primitives::underlying::traits { +template <> struct mcpplibs::primitives::underlying::traits { using value_type = UserInteger; using rep_type = int; static constexpr bool enabled = true; static constexpr auto kind = category::integer; - static constexpr rep_type to_rep(value_type value) noexcept { return value.value; } + static constexpr rep_type to_rep(value_type value) noexcept { + return value.value; + } - static constexpr value_type from_rep(rep_type value) noexcept { return UserInteger{value}; } + static constexpr value_type from_rep(rep_type value) noexcept { + return UserInteger{value}; + } static constexpr bool is_valid_rep(rep_type) noexcept { return true; } }; -template <> -struct mcpplibs::primitives::underlying::traits { +template <> struct mcpplibs::primitives::underlying::traits { using value_type = BadRep; using rep_type = BadRep; @@ -47,26 +138,100 @@ struct mcpplibs::primitives::underlying::traits { static constexpr rep_type to_rep(value_type value) noexcept { return value; } - static constexpr value_type from_rep(rep_type value) noexcept { return value; } + static constexpr value_type from_rep(rep_type value) noexcept { + return value; + } static constexpr bool is_valid_rep(rep_type) noexcept { return true; } }; -template <> -struct mcpplibs::primitives::underlying::traits { +template <> struct mcpplibs::primitives::underlying::traits { using value_type = BadKind; using rep_type = int; static constexpr bool enabled = true; static constexpr auto kind = category::floating; - static constexpr rep_type to_rep(value_type value) noexcept { return value.value; } + static constexpr rep_type to_rep(value_type value) noexcept { + return value.value; + } + + static constexpr value_type from_rep(rep_type value) noexcept { + return BadKind{value}; + } + + static constexpr bool is_valid_rep(rep_type) noexcept { return true; } +}; + +template <> struct mcpplibs::primitives::underlying::traits { + using value_type = BigIntLike; + using rep_type = BigIntLike; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr rep_type to_rep(value_type value) noexcept { return value; } + + static constexpr value_type from_rep(rep_type value) noexcept { + return value; + } + + static constexpr bool is_valid_rep(rep_type) noexcept { return true; } +}; + +template <> struct mcpplibs::primitives::underlying::traits { + using value_type = BadCustomKind; + using rep_type = BadCustomKind; + + static constexpr bool enabled = true; + static constexpr auto kind = category::boolean; + + static constexpr rep_type to_rep(value_type value) noexcept { return value; } + + static constexpr value_type from_rep(rep_type value) noexcept { + return value; + } + + static constexpr bool is_valid_rep(rep_type) noexcept { return true; } +}; + +template <> +struct mcpplibs::primitives::underlying::traits { + using value_type = MissingDivisionLike; + using rep_type = MissingDivisionLike; - static constexpr value_type from_rep(rep_type value) noexcept { return BadKind{value}; } + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr rep_type to_rep(value_type value) noexcept { return value; } + + static constexpr value_type from_rep(rep_type value) noexcept { + return value; + } static constexpr bool is_valid_rep(rep_type) noexcept { return true; } }; +template <> struct mcpplibs::primitives::underlying::traits { + using value_type = NonNegativeInt; + using rep_type = int; + + static constexpr bool enabled = true; + static constexpr auto kind = category::integer; + + static constexpr rep_type to_rep(value_type value) noexcept { + return value.value; + } + + static constexpr value_type from_rep(rep_type value) noexcept { + return NonNegativeInt{value}; + } + + static constexpr bool is_valid_rep(rep_type value) noexcept { + return value >= 0; + } +}; + TEST(PrimitiveTraitsTest, StandardTypeConcepts) { EXPECT_TRUE((mcpplibs::primitives::std_integer)); EXPECT_TRUE((mcpplibs::primitives::std_integer)); @@ -86,7 +251,7 @@ TEST(PrimitiveTraitsTest, StandardTypeConcepts) { EXPECT_TRUE((mcpplibs::primitives::std_underlying_type)); EXPECT_TRUE((mcpplibs::primitives::std_underlying_type)); - EXPECT_FALSE((mcpplibs::primitives::std_underlying_type)); + EXPECT_FALSE((mcpplibs::primitives::std_underlying_type)); } TEST(PrimitiveTraitsTest, UnderlyingTraitsDefaultsAndCustomRegistration) { @@ -95,13 +260,17 @@ TEST(PrimitiveTraitsTest, UnderlyingTraitsDefaultsAndCustomRegistration) { mcpplibs::primitives::underlying::category::integer); EXPECT_TRUE((mcpplibs::primitives::underlying_type)); - EXPECT_EQ(mcpplibs::primitives::underlying::traits::to_rep(UserInteger{7}), 7); + EXPECT_EQ(mcpplibs::primitives::underlying::traits::to_rep( + UserInteger{7}), + 7); EXPECT_FALSE((mcpplibs::primitives::underlying_type)); - EXPECT_FALSE((mcpplibs::primitives::underlying::traits::enabled)); + EXPECT_FALSE( + (mcpplibs::primitives::underlying::traits::enabled)); } -TEST(PrimitiveTraitsTest, UnderlyingTypeRequiresValidRepTypeAndCategoryConsistency) { +TEST(PrimitiveTraitsTest, + UnderlyingTypeRequiresValidRepTypeAndCategoryConsistency) { EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); EXPECT_FALSE((mcpplibs::primitives::underlying_type)); @@ -109,6 +278,67 @@ TEST(PrimitiveTraitsTest, UnderlyingTypeRequiresValidRepTypeAndCategoryConsisten EXPECT_FALSE((mcpplibs::primitives::underlying_type)); } +TEST(PrimitiveTraitsTest, UnderlyingTypeAllowsCustomNumericLikeRepType) { + EXPECT_TRUE((mcpplibs::primitives::underlying::traits::enabled)); + EXPECT_TRUE((mcpplibs::primitives::underlying_type)); +} + +TEST(PrimitiveTraitsTest, CustomNumericLikeRepTypeRejectsInvalidCategory) { + EXPECT_TRUE( + (mcpplibs::primitives::underlying::traits::enabled)); + EXPECT_FALSE((mcpplibs::primitives::underlying_type)); +} + +TEST(PrimitiveTraitsTest, CustomUnderlyingParticipatesInPrimitiveOperations) { + using value_t = mcpplibs::primitives::primitive< + BigIntLike, mcpplibs::primitives::policy::value::checked, + mcpplibs::primitives::policy::error::expected>; + + auto const lhs = value_t{BigIntLike{40}}; + auto const rhs = value_t{BigIntLike{2}}; + + auto const result = mcpplibs::primitives::operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value().value, 42); +} + +TEST(PrimitiveTraitsTest, CustomWrappedUnderlyingUsesRepBridgeForArithmetic) { + using value_t = mcpplibs::primitives::primitive< + UserInteger, mcpplibs::primitives::policy::value::checked, + mcpplibs::primitives::policy::error::expected>; + + auto const lhs = value_t{UserInteger{40}}; + auto const rhs = value_t{UserInteger{2}}; + + auto const result = mcpplibs::primitives::operations::add(lhs, rhs); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->value(), 42); +} + +TEST(PrimitiveTraitsTest, InvalidUnderlyingRepIsRejectedByDispatcher) { + using value_t = mcpplibs::primitives::primitive< + NonNegativeInt, mcpplibs::primitives::policy::value::checked, + mcpplibs::primitives::policy::error::expected>; + + auto const lhs = value_t{NonNegativeInt{-1}}; + auto const rhs = value_t{NonNegativeInt{2}}; + + auto const result = mcpplibs::primitives::operations::add(lhs, rhs); + + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), + mcpplibs::primitives::policy::error::kind::domain_error); +} + +TEST(PrimitiveTraitsTest, + CustomNumericLikeRepTypeRequiresDivisionOperatorForEligibility) { + EXPECT_TRUE( + (mcpplibs::primitives::underlying::traits::enabled)); + EXPECT_FALSE((mcpplibs::primitives::underlying_type)); +} + int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();