From c15becf529a352272da22748465f464e0fd2b199 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:42:13 +0800 Subject: [PATCH 01/14] feat: Complete traits for operation-level atomicity --- src/policy/handler.cppm | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/policy/handler.cppm b/src/policy/handler.cppm index bacf8eb..bc74ce1 100644 --- a/src/policy/handler.cppm +++ b/src/policy/handler.cppm @@ -1,5 +1,6 @@ module; +#include #include #include #include @@ -84,6 +85,8 @@ namespace concurrency { struct injection { bool fence_before = false; bool fence_after = false; + std::memory_order order_before = std::memory_order_seq_cst; + std::memory_order order_after = std::memory_order_seq_cst; }; template ; static constexpr auto inject() noexcept -> injection_type { return {}; } + + static constexpr auto load(CommonRep const &) noexcept -> CommonRep { + return CommonRep{}; + } + + static constexpr auto store(CommonRep &, CommonRep) noexcept -> void {} + + static constexpr auto compare_exchange(CommonRep &, CommonRep &, + CommonRep) noexcept -> bool { + return false; + } }; template ; }; +template +concept handler_access_protocol = requires { + requires concurrency_policy; + { + handler::enabled + } -> std::convertible_to; + requires handler::enabled; + { + handler::load( + std::declval()) + } noexcept -> std::same_as; + { + handler::store( + std::declval(), std::declval()) + } noexcept -> std::same_as; + { + handler::compare_exchange( + std::declval(), std::declval(), + std::declval()) + } noexcept -> std::same_as; +}; + +template +concept handler_access_available = requires { + requires handler::enabled; + requires handler_access_protocol; +}; + } // namespace concurrency namespace type { From 239c448692c83c9d2ea174f8323c0f624d5cb65f Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:43:06 +0800 Subject: [PATCH 02/14] feat: Refine memory order concurrency strategy and implement traits --- src/policy/impl.cppm | 193 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 188 insertions(+), 5 deletions(-) diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index 5888221..82bd674 100644 --- a/src/policy/impl.cppm +++ b/src/policy/impl.cppm @@ -1,4 +1,5 @@ module; +#include #include #include #include @@ -6,7 +7,6 @@ module; #include #include - export module mcpplibs.primitives.policy.impl; import mcpplibs.primitives.operations.traits; @@ -37,7 +37,10 @@ struct terminate {}; namespace concurrency { struct none {}; -struct atomic {}; +struct fenced {}; +struct fenced_relaxed {}; +struct fenced_acq_rel {}; +struct fenced_seq_cst {}; } // namespace concurrency template <> struct traits { @@ -100,8 +103,26 @@ template <> struct traits { static constexpr auto kind = category::concurrency; }; -template <> struct traits { - using policy_type = concurrency::atomic; +template <> struct traits { + using policy_type = concurrency::fenced; + static constexpr bool enabled = true; + static constexpr auto kind = category::concurrency; +}; + +template <> struct traits { + using policy_type = concurrency::fenced_relaxed; + static constexpr bool enabled = true; + static constexpr auto kind = category::concurrency; +}; + +template <> struct traits { + using policy_type = concurrency::fenced_acq_rel; + static constexpr bool enabled = true; + static constexpr auto kind = category::concurrency; +}; + +template <> struct traits { + using policy_type = concurrency::fenced_seq_cst; static constexpr bool enabled = true; static constexpr auto kind = category::concurrency; }; @@ -189,7 +210,7 @@ struct concurrency::handler { template -struct concurrency::handler { static constexpr bool enabled = true; static constexpr bool requires_external_sync = true; @@ -200,10 +221,172 @@ struct concurrency::handler +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; + + static constexpr auto inject() noexcept -> injection_type { + injection_type out{}; + out.fence_before = true; + out.fence_after = true; + out.order_before = std::memory_order_relaxed; + out.order_after = std::memory_order_relaxed; + return out; + } +}; + +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; + + static constexpr auto inject() noexcept -> injection_type { + injection_type out{}; + out.fence_before = true; + out.fence_after = true; + out.order_before = std::memory_order_acquire; + out.order_after = std::memory_order_release; + return out; + } +}; + +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; + + static constexpr auto inject() noexcept -> injection_type { + injection_type out{}; + out.fence_before = true; + out.fence_after = true; + out.order_before = std::memory_order_seq_cst; + out.order_after = std::memory_order_seq_cst; + return out; + } +}; + +template +struct concurrency::handler { + static constexpr bool enabled = std::is_trivially_copyable_v; + using injection_type = concurrency::injection; + using result_type = std::expected; + + static auto load(CommonRep const &value) noexcept -> CommonRep { + std::atomic_ref ref(value); + return ref.load(std::memory_order_seq_cst); + } + + static auto store(CommonRep &value, CommonRep desired) noexcept -> void { + std::atomic_ref ref(value); + ref.store(desired, std::memory_order_seq_cst); + } + + static auto compare_exchange(CommonRep &value, CommonRep &expected, + CommonRep desired) noexcept -> bool { + std::atomic_ref ref(value); + return ref.compare_exchange_strong(expected, desired, + std::memory_order_seq_cst, + std::memory_order_seq_cst); + } +}; + +template +struct concurrency::handler { + static constexpr bool enabled = std::is_trivially_copyable_v; + using injection_type = concurrency::injection; + using result_type = std::expected; + + static auto load(CommonRep const &value) noexcept -> CommonRep { + std::atomic_ref ref(value); + return ref.load(std::memory_order_relaxed); + } + + static auto store(CommonRep &value, CommonRep desired) noexcept -> void { + std::atomic_ref ref(value); + ref.store(desired, std::memory_order_relaxed); + } + + static auto compare_exchange(CommonRep &value, CommonRep &expected, + CommonRep desired) noexcept -> bool { + std::atomic_ref ref(value); + return ref.compare_exchange_strong(expected, desired, + std::memory_order_relaxed, + std::memory_order_relaxed); + } +}; + +template +struct concurrency::handler { + static constexpr bool enabled = std::is_trivially_copyable_v; + using injection_type = concurrency::injection; + using result_type = std::expected; + + static auto load(CommonRep const &value) noexcept -> CommonRep { + std::atomic_ref ref(value); + return ref.load(std::memory_order_acquire); + } + + static auto store(CommonRep &value, CommonRep desired) noexcept -> void { + std::atomic_ref ref(value); + ref.store(desired, std::memory_order_release); + } + + static auto compare_exchange(CommonRep &value, CommonRep &expected, + CommonRep desired) noexcept -> bool { + std::atomic_ref ref(value); + return ref.compare_exchange_strong(expected, desired, + std::memory_order_acq_rel, + std::memory_order_acquire); + } +}; + +template +struct concurrency::handler { + static constexpr bool enabled = std::is_trivially_copyable_v; + using injection_type = concurrency::injection; + using result_type = std::expected; + + static auto load(CommonRep const &value) noexcept -> CommonRep { + return concurrency::handler::load(value); + } + + static auto store(CommonRep &value, CommonRep desired) noexcept -> void { + concurrency::handler::store(value, desired); + } + + static auto compare_exchange(CommonRep &value, CommonRep &expected, + CommonRep desired) noexcept -> bool { + return concurrency::handler::compare_exchange(value, expected, + desired); + } +}; + template struct value::handler { From 880cada3674e9d9c480159eb9babd7f47773b00d Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:45:02 +0800 Subject: [PATCH 03/14] feat: Add concurrent access interface to primitive type --- src/primitive/impl.cppm | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 5c0d682..d0a16aa 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -6,18 +6,97 @@ export module mcpplibs.primitives.primitive.impl; import mcpplibs.primitives.underlying.traits; import mcpplibs.primitives.policy.traits; +import mcpplibs.primitives.policy.impl; +import mcpplibs.primitives.policy.handler; export namespace mcpplibs::primitives { +namespace details { + +template struct count_concurrency_policies; + +template <> struct count_concurrency_policies<> { + static constexpr std::size_t value = 0; +}; + +template +struct count_concurrency_policies { + static constexpr bool is_match = + policy::traits::kind == policy::category::concurrency; + static constexpr std::size_t value = + count_concurrency_policies::value + (is_match ? 1u : 0u); +}; + +template struct resolve_concurrency_policy; + +template <> struct resolve_concurrency_policy<> { + using type = policy::defaults::concurrency; +}; + +template +struct resolve_concurrency_policy { + using type = std::conditional_t< + policy::traits::kind == policy::category::concurrency, First, + typename resolve_concurrency_policy::type>; +}; + +template +using resolve_concurrency_policy_t = + typename resolve_concurrency_policy::type; + +} // namespace details + template class primitive { public: using value_type = T; using policies = std::tuple; + using concurrency_policy = details::resolve_concurrency_policy_t; + + static_assert(details::count_concurrency_policies::value <= 1, + "Multiple concurrency policies are not allowed"); + constexpr explicit primitive(value_type v) noexcept : value_(v) {} constexpr value_type &value() noexcept { return value_; } [[nodiscard]] constexpr value_type const &value() const noexcept { return value_; } constexpr explicit operator value_type() const noexcept { return value_; } + [[nodiscard]] auto load() const noexcept -> value_type { + using access_handler_t = + policy::concurrency::handler; + static_assert( + policy::concurrency::handler_access_available, + "Selected concurrency policy does not provide primitive " + "load/store/CAS support"); + return access_handler_t::load(value_); + } + + auto store(value_type desired) noexcept -> void { + using access_handler_t = + policy::concurrency::handler; + static_assert( + policy::concurrency::handler_access_available, + "Selected concurrency policy does not provide primitive " + "load/store/CAS support"); + access_handler_t::store(value_, desired); + } + + auto compare_exchange(value_type &expected, value_type desired) noexcept + -> bool { + using access_handler_t = + policy::concurrency::handler; + static_assert( + policy::concurrency::handler_access_available, + "Selected concurrency policy does not provide primitive " + "load/store/CAS support"); + return access_handler_t::compare_exchange(value_, expected, desired); + } + private: value_type value_; }; From 371bb529dce7680687a2b869bd9ed8517b065ac8 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:45:22 +0800 Subject: [PATCH 04/14] style: Fmt code --- src/primitive/impl.cppm | 84 +++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index d0a16aa..45ae186 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -1,6 +1,8 @@ module; +#include #include #include +#include export module mcpplibs.primitives.primitive.impl; @@ -57,7 +59,9 @@ public: constexpr explicit primitive(value_type v) noexcept : value_(v) {} constexpr value_type &value() noexcept { return value_; } - [[nodiscard]] constexpr value_type const &value() const noexcept { return value_; } + [[nodiscard]] constexpr value_type const &value() const noexcept { + return value_; + } constexpr explicit operator value_type() const noexcept { return value_; } [[nodiscard]] auto load() const noexcept -> value_type { @@ -101,44 +105,44 @@ private: value_type value_; }; - namespace types { - template - using Bool = primitive; - - template - using UChar = primitive; - template - using Char8 = primitive; - template - using Char16 = primitive; - template - using Char32 = primitive; - template - using WChar = primitive; - - template - using U8 = primitive; - template - using U16 = primitive; - template - using U32 = primitive; - template - using U64 = primitive; - template - using I8 = primitive; - template - using I16 = primitive; - template - using I32 = primitive; - template - using I64 = primitive; - - template - using F32 = primitive; - template - using F64 = primitive; - template - using F80 = primitive; - } +namespace types { +template +using Bool = primitive; + +template +using UChar = primitive; +template +using Char8 = primitive; +template +using Char16 = primitive; +template +using Char32 = primitive; +template +using WChar = primitive; + +template +using U8 = primitive; +template +using U16 = primitive; +template +using U32 = primitive; +template +using U64 = primitive; +template +using I8 = primitive; +template +using I16 = primitive; +template +using I32 = primitive; +template +using I64 = primitive; + +template +using F32 = primitive; +template +using F64 = primitive; +template +using F80 = primitive; +} // namespace types } // namespace mcpplibs::primitives From c087af73d7ae03f13ca230bce42cfd5327621a7b Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:46:41 +0800 Subject: [PATCH 05/14] refactor: Remove hard-coded code in the memory barrier injection logic --- src/operations/invoker.cppm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/operations/invoker.cppm b/src/operations/invoker.cppm index db1788b..0772329 100644 --- a/src/operations/invoker.cppm +++ b/src/operations/invoker.cppm @@ -365,13 +365,14 @@ constexpr auto make_div_zero(char const *reason) return make_error(policy::error::kind::divide_by_zero, reason); } -constexpr auto apply_runtime_fence(bool enabled) noexcept -> void { +constexpr auto apply_runtime_fence(bool enabled, + std::memory_order order) noexcept -> void { if (!enabled) { return; } if (!std::is_constant_evaluated()) { - std::atomic_thread_fence(std::memory_order_seq_cst); + std::atomic_thread_fence(order); } } @@ -660,12 +661,12 @@ constexpr auto run_value(CommonRep lhs, CommonRep rhs, op_binding_available, "Missing operation binding specialization for this OpTag/common type"); - details::apply_runtime_fence(injection.fence_before); + details::apply_runtime_fence(injection.fence_before, injection.order_before); auto decision = op_binding::apply(lhs, rhs); auto finalized = ValueHandler::finalize(std::move(decision), injection); - details::apply_runtime_fence(injection.fence_after); + details::apply_runtime_fence(injection.fence_after, injection.order_after); return finalized; } From d7920c2c0c0d8918083b874757e4ae4f55441d20 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:47:06 +0800 Subject: [PATCH 06/14] refactor: Use new tag name --- src/policy/utility.cppm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/policy/utility.cppm b/src/policy/utility.cppm index 51de8c7..4ec0eff 100644 --- a/src/policy/utility.cppm +++ b/src/policy/utility.cppm @@ -64,7 +64,7 @@ template <> struct priority_error { }; template <> struct priority_concurrency { - using type = std::tuple; + using type = std::tuple; }; template struct common_policies { From 9b18fb1df173176ed922913e126a1313659ff314 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:48:12 +0800 Subject: [PATCH 07/14] test: Complete the traits tests for the concurrency strategy --- tests/basic/test_policies.cpp | 65 ++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/tests/basic/test_policies.cpp b/tests/basic/test_policies.cpp index 763073f..aa26f08 100644 --- a/tests/basic/test_policies.cpp +++ b/tests/basic/test_policies.cpp @@ -1,3 +1,4 @@ +#include #include #include import mcpplibs.primitives; @@ -43,8 +44,20 @@ TEST(PolicyTraitsTest, BuiltinPoliciesHaveCategories) { 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::concurrency); + + 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, policy::category::concurrency); EXPECT_TRUE((policy::traits::enabled)); @@ -60,24 +73,60 @@ TEST(PolicyTraitsTest, BuiltinPoliciesHaveCategories) { 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 fenced_injection = fenced_handler::inject(); auto const single_injection = single_handler::inject(); - EXPECT_TRUE(atomic_injection.fence_before); - EXPECT_TRUE(atomic_injection.fence_after); + EXPECT_TRUE(fenced_injection.fence_before); + EXPECT_TRUE(fenced_injection.fence_after); + EXPECT_EQ(fenced_injection.order_before, std::memory_order_seq_cst); + EXPECT_EQ(fenced_injection.order_after, std::memory_order_seq_cst); EXPECT_FALSE(single_injection.fence_before); EXPECT_FALSE(single_injection.fence_after); } +TEST(PolicyConcurrencyTest, FencedVariantsUseExpectedMemoryOrders) { + using relaxed_handler = + policy::concurrency::handler; + using acq_rel_handler = + policy::concurrency::handler; + + auto const relaxed = relaxed_handler::inject(); + auto const acq_rel = acq_rel_handler::inject(); + + EXPECT_EQ(relaxed.order_before, std::memory_order_relaxed); + EXPECT_EQ(relaxed.order_after, std::memory_order_relaxed); + EXPECT_EQ(acq_rel.order_before, std::memory_order_acquire); + EXPECT_EQ(acq_rel.order_after, std::memory_order_release); +} + +TEST(PolicyConcurrencyTest, PrimitiveAccessHandlerProtocolByPolicy) { + EXPECT_TRUE(( + policy::concurrency::handler_access_available)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, int>)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, int>)); + EXPECT_TRUE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, int>)); + EXPECT_FALSE( + (policy::concurrency::handler_access_available)); +} + TEST(PolicyProtocolTest, BuiltinHandlersSatisfyProtocolConcepts) { static_assert(policy::type::handler_protocol); From c89b99a317240be04a6af856aa539b13791dfdb6 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:49:44 +0800 Subject: [PATCH 08/14] test: Add a new concurrency strategy test and correct the original test name --- tests/basic/test_operations.cpp | 58 ++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/tests/basic/test_operations.cpp b/tests/basic/test_operations.cpp index d8bdf4b..ceb1fcc 100644 --- a/tests/basic/test_operations.cpp +++ b/tests/basic/test_operations.cpp @@ -5,7 +5,6 @@ #include #include - import mcpplibs.primitives; using namespace mcpplibs::primitives; @@ -87,9 +86,9 @@ TEST(OperationsTest, UncheckedDivisionUsesRawArithmeticWhenValid) { EXPECT_EQ(result->value(), 25); } -TEST(OperationsTest, AtomicPolicyPathReturnsExpectedValue) { +TEST(OperationsTest, FencedPolicyPathReturnsExpectedValue) { using value_t = - primitive; auto const lhs = value_t{12}; @@ -101,9 +100,9 @@ TEST(OperationsTest, AtomicPolicyPathReturnsExpectedValue) { EXPECT_EQ(result->value(), 42); } -TEST(OperationsTest, AtomicPolicyConcurrentInvocationsRemainConsistent) { +TEST(OperationsTest, FencedPolicyConcurrentInvocationsRemainConsistent) { using value_t = - primitive; constexpr int kThreadCount = 8; @@ -148,6 +147,55 @@ TEST(OperationsTest, AtomicPolicyConcurrentInvocationsRemainConsistent) { EXPECT_EQ(mismatch_count.load(std::memory_order_relaxed), 0); } +TEST(OperationsTest, PrimitiveFencedLoadStoreAndCasWork) { + using value_t = + primitive; + + auto value = value_t{1}; + EXPECT_EQ(value.load(), 1); + + value.store(4); + EXPECT_EQ(value.load(), 4); + + auto expected = 4; + EXPECT_TRUE(value.compare_exchange(expected, 7)); + EXPECT_EQ(value.load(), 7); + + expected = 9; + EXPECT_FALSE(value.compare_exchange(expected, 11)); + EXPECT_EQ(expected, 7); +} + +TEST(OperationsTest, PrimitiveFencedCasSupportsConcurrentIncrements) { + using value_t = + primitive; + + constexpr int kThreadCount = 6; + constexpr int kIterationsPerThread = 5000; + + auto counter = value_t{0}; + std::vector workers; + workers.reserve(kThreadCount); + + for (int i = 0; i < kThreadCount; ++i) { + workers.emplace_back([&]() { + for (int n = 0; n < kIterationsPerThread; ++n) { + auto expected = counter.load(); + while (!counter.compare_exchange(expected, expected + 1)) { + } + } + }); + } + + for (auto &worker : workers) { + worker.join(); + } + + EXPECT_EQ(counter.load(), kThreadCount * kIterationsPerThread); +} + TEST(OperationsTest, StrictTypeRejectsMixedTypesAtCompileTime) { using lhs_t = primitive; From 289489a14c35c68a43efcea00de2098e45ffef98 Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:50:20 +0800 Subject: [PATCH 09/14] docs: Update the documentation for the new concurrency strategy interface --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 119a66d..517f89e 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,29 @@ auto maybe = x + y; // std::expected, po - `policy::value::{checked, unchecked, saturating}` - `policy::type::{strict, compatible, transparent}` - `policy::error::{throwing, expected, terminate}` -- `policy::concurrency::{none, atomic}` +- `policy::concurrency::{none, fenced, fenced_relaxed, fenced_acq_rel, fenced_seq_cst}` + +并发策略说明: + +- `fenced*` 系列是操作级并发语义,通过策略注入内存序 fence; +- `primitive` 存储仍保持统一、零开销布局,不引入额外存储层抽象; +- `primitive::load/store/compare_exchange` 由并发策略的协议实现提供,若策略未实现该协议会在编译期报错。 + +示例(并发访问 API): + +```cpp +using shared_t = primitive; + +shared_t v{1}; +v.store(2); +auto expected = 2; +if (v.compare_exchange(expected, 3)) { + auto now = v.load(); + (void)now; +} +``` 默认策略位于 `policy::defaults`: From 78cb06bc2492cb2f210260d5038b47c0122855cd Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:51:12 +0800 Subject: [PATCH 10/14] docs: Add a new concurrency strategy example program --- examples/ex05_concurrency_policy.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/examples/ex05_concurrency_policy.cpp b/examples/ex05_concurrency_policy.cpp index e4da76a..27a6cc2 100644 --- a/examples/ex05_concurrency_policy.cpp +++ b/examples/ex05_concurrency_policy.cpp @@ -2,11 +2,12 @@ * Example: ex05_concurrency_policy * * Purpose: - * Demonstrate the atomic concurrency policy path under multi-threaded + * Demonstrate the fenced concurrency policy path under multi-threaded * repeated dispatch. * * Expected results: * - Concurrent add operations consistently produce value 42. + * - Primitive load/store/CAS APIs work under fenced policy. * - mismatch_count remains zero after all worker threads join. * - Program prints a success message and exits with code 0. */ @@ -21,13 +22,22 @@ 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}; + auto const lhs = fenced_t{12}; + auto const rhs = fenced_t{30}; + + auto concurrent_value = fenced_t{1}; + concurrent_value.store(2); + auto expected = 2; + if (!concurrent_value.compare_exchange(expected, 3) || + concurrent_value.load() != 3) { + std::cerr << "fenced load/store/CAS mismatch\n"; + return 1; + } std::atomic mismatch_count{0}; std::vector workers; @@ -51,7 +61,7 @@ int main() { // 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"; + std::cerr << "fenced policy path mismatch\n"; return 1; } From 568fcf22a1fb1ddacf8a03994d3ebf947738e8eb Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Thu, 19 Mar 2026 18:52:06 +0800 Subject: [PATCH 11/14] docs: Correct the code for the custom concurrency policy in the example program --- examples/ex07_custom_policy.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/ex07_custom_policy.cpp b/examples/ex07_custom_policy.cpp index 0c6e0d1..ef36c13 100644 --- a/examples/ex07_custom_policy.cpp +++ b/examples/ex07_custom_policy.cpp @@ -12,10 +12,12 @@ * - Program prints a success message and exits with code 0. */ +#include #include #include #include + import mcpplibs.primitives; import mcpplibs.primitives.operations.invoker; @@ -79,6 +81,8 @@ struct mcpplibs::primitives::policy::concurrency::handler< injection_type out{}; out.fence_before = true; out.fence_after = false; + out.order_before = std::memory_order_acquire; + out.order_after = std::memory_order_relaxed; return out; } }; From 168fcefc943ce4c45e3d2dbdc58158202da5ecfe Mon Sep 17 00:00:00 2001 From: FrozenLemonTee Date: Fri, 20 Mar 2026 00:02:26 +0800 Subject: [PATCH 12/14] refactor: Use helper to extract code --- src/policy/impl.cppm | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index 82bd674..d4f8814 100644 --- a/src/policy/impl.cppm +++ b/src/policy/impl.cppm @@ -148,6 +148,14 @@ inline constexpr bool rejects_arithmetic_for_boolean_or_character_v = is_arithmetic_operation_v && (is_boolean_or_character_v || is_boolean_or_character_v); +template +auto atomic_ref_load(T const &value, std::memory_order order) noexcept -> T { + // libc++ rejects std::atomic_ref; load through a non-mutating view. + auto &mutable_value = const_cast(value); + std::atomic_ref ref(mutable_value); + return ref.load(order); +} + } // namespace details // Default protocol specializations. @@ -292,8 +300,7 @@ struct concurrency::handler; static auto load(CommonRep const &value) noexcept -> CommonRep { - std::atomic_ref ref(value); - return ref.load(std::memory_order_seq_cst); + return details::atomic_ref_load(value, std::memory_order_seq_cst); } static auto store(CommonRep &value, CommonRep desired) noexcept -> void { @@ -318,8 +325,7 @@ struct concurrency::handler; static auto load(CommonRep const &value) noexcept -> CommonRep { - std::atomic_ref ref(value); - return ref.load(std::memory_order_relaxed); + return details::atomic_ref_load(value, std::memory_order_relaxed); } static auto store(CommonRep &value, CommonRep desired) noexcept -> void { @@ -344,8 +350,7 @@ struct concurrency::handler; static auto load(CommonRep const &value) noexcept -> CommonRep { - std::atomic_ref ref(value); - return ref.load(std::memory_order_acquire); + return details::atomic_ref_load(value, std::memory_order_acquire); } static auto store(CommonRep &value, CommonRep desired) noexcept -> void { From 156439f6fa9051d39cd6a2afd27154f143e05c82 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Fri, 20 Mar 2026 19:13:01 +0800 Subject: [PATCH 13/14] refactor: Add and use atomic_ref_compatible checks for concurrency handlers --- src/policy/impl.cppm | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index d4f8814..0ec7c17 100644 --- a/src/policy/impl.cppm +++ b/src/policy/impl.cppm @@ -136,6 +136,28 @@ using concurrency = concurrency::none; namespace details { +template > +struct atomic_ref_alignment_compatible : std::false_type {}; + +template +struct atomic_ref_alignment_compatible + : std::bool_constant<(alignof(T) >= + std::atomic_ref::required_alignment)> {}; + +template +inline constexpr bool atomic_ref_compatible_v = + atomic_ref_alignment_compatible::value; + +template constexpr void assert_atomic_ref_compatible() { + static_assert(std::is_trivially_copyable_v, + "concurrency::handler atomic access requires trivially " + "copyable CommonRep"); + static_assert( + atomic_ref_alignment_compatible::value, + "concurrency::handler atomic access requires alignof(CommonRep) to " + "satisfy std::atomic_ref::required_alignment"); +} + template inline constexpr bool is_arithmetic_operation_v = operations::op_has_capability_v; @@ -150,6 +172,7 @@ inline constexpr bool rejects_arithmetic_for_boolean_or_character_v = template auto atomic_ref_load(T const &value, std::memory_order order) noexcept -> T { + assert_atomic_ref_compatible(); // libc++ rejects std::atomic_ref; load through a non-mutating view. auto &mutable_value = const_cast(value); std::atomic_ref ref(mutable_value); @@ -295,7 +318,7 @@ struct concurrency::handler struct concurrency::handler { - static constexpr bool enabled = std::is_trivially_copyable_v; + static constexpr bool enabled = details::atomic_ref_compatible_v; using injection_type = concurrency::injection; using result_type = std::expected; @@ -304,12 +327,14 @@ struct concurrency::handler void { + details::assert_atomic_ref_compatible(); std::atomic_ref ref(value); ref.store(desired, std::memory_order_seq_cst); } static auto compare_exchange(CommonRep &value, CommonRep &expected, CommonRep desired) noexcept -> bool { + details::assert_atomic_ref_compatible(); std::atomic_ref ref(value); return ref.compare_exchange_strong(expected, desired, std::memory_order_seq_cst, @@ -320,7 +345,7 @@ struct concurrency::handler struct concurrency::handler { - static constexpr bool enabled = std::is_trivially_copyable_v; + static constexpr bool enabled = details::atomic_ref_compatible_v; using injection_type = concurrency::injection; using result_type = std::expected; @@ -329,12 +354,14 @@ struct concurrency::handler void { + details::assert_atomic_ref_compatible(); std::atomic_ref ref(value); ref.store(desired, std::memory_order_relaxed); } static auto compare_exchange(CommonRep &value, CommonRep &expected, CommonRep desired) noexcept -> bool { + details::assert_atomic_ref_compatible(); std::atomic_ref ref(value); return ref.compare_exchange_strong(expected, desired, std::memory_order_relaxed, @@ -345,7 +372,7 @@ struct concurrency::handler struct concurrency::handler { - static constexpr bool enabled = std::is_trivially_copyable_v; + static constexpr bool enabled = details::atomic_ref_compatible_v; using injection_type = concurrency::injection; using result_type = std::expected; @@ -354,12 +381,14 @@ struct concurrency::handler void { + details::assert_atomic_ref_compatible(); std::atomic_ref ref(value); ref.store(desired, std::memory_order_release); } static auto compare_exchange(CommonRep &value, CommonRep &expected, CommonRep desired) noexcept -> bool { + details::assert_atomic_ref_compatible(); std::atomic_ref ref(value); return ref.compare_exchange_strong(expected, desired, std::memory_order_acq_rel, @@ -370,7 +399,7 @@ struct concurrency::handler struct concurrency::handler { - static constexpr bool enabled = std::is_trivially_copyable_v; + static constexpr bool enabled = details::atomic_ref_compatible_v; using injection_type = concurrency::injection; using result_type = std::expected; From 6e14d3d22f8942af50a0ac56cb412e7696b4eb03 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee Date: Fri, 20 Mar 2026 19:13:08 +0800 Subject: [PATCH 14/14] test: Add tests for non-trivially copyable and low alignment representations in concurrency policies --- tests/basic/test_policies.cpp | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/basic/test_policies.cpp b/tests/basic/test_policies.cpp index aa26f08..7872382 100644 --- a/tests/basic/test_policies.cpp +++ b/tests/basic/test_policies.cpp @@ -7,6 +7,15 @@ using namespace mcpplibs::primitives; namespace { struct NullCapabilityProbe {}; + +struct NonTriviallyCopyableRep { + int value{0}; + ~NonTriviallyCopyableRep() {} +}; + +struct LowAlignmentRep { + unsigned char bytes[sizeof(std::uint64_t)]{}; +}; } // namespace template <> struct operations::traits { @@ -127,6 +136,38 @@ TEST(PolicyConcurrencyTest, PrimitiveAccessHandlerProtocolByPolicy) { int>)); } +TEST(PolicyConcurrencyTest, PrimitiveAccessRejectsNonTriviallyCopyableRep) { + EXPECT_FALSE(( + policy::concurrency::handler_access_available)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, NonTriviallyCopyableRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, NonTriviallyCopyableRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, NonTriviallyCopyableRep>)); +} + +TEST(PolicyConcurrencyTest, PrimitiveAccessRespectsAtomicRefAlignmentGate) { + constexpr bool requires_stronger_alignment = + std::atomic_ref::required_alignment > + alignof(LowAlignmentRep); + + if constexpr (requires_stronger_alignment) { + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_relaxed, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_acq_rel, LowAlignmentRep>)); + EXPECT_FALSE((policy::concurrency::handler_access_available< + policy::concurrency::fenced_seq_cst, LowAlignmentRep>)); + } else { + GTEST_SKIP() << "platform atomic_ref required_alignment does not exceed " + "alignof(LowAlignmentRep)"; + } +} + TEST(PolicyProtocolTest, BuiltinHandlersSatisfyProtocolConcepts) { static_assert(policy::type::handler_protocol);