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`: 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; } 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; } }; 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; } 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 { diff --git a/src/policy/impl.cppm b/src/policy/impl.cppm index 5888221..0ec7c17 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; }; @@ -115,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; @@ -127,6 +170,15 @@ 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 { + 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); + return ref.load(order); +} + } // namespace details // Default protocol specializations. @@ -189,7 +241,64 @@ struct concurrency::handler { 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 = 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; @@ -200,10 +309,118 @@ struct concurrency::handler +struct concurrency::handler { + static constexpr bool enabled = details::atomic_ref_compatible_v; + using injection_type = concurrency::injection; + using result_type = std::expected; + + static auto load(CommonRep const &value) noexcept -> CommonRep { + return details::atomic_ref_load(value, std::memory_order_seq_cst); + } + + static auto store(CommonRep &value, CommonRep desired) noexcept -> 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, + std::memory_order_seq_cst); + } +}; + +template +struct concurrency::handler { + static constexpr bool enabled = details::atomic_ref_compatible_v; + using injection_type = concurrency::injection; + using result_type = std::expected; + + static auto load(CommonRep const &value) noexcept -> CommonRep { + return details::atomic_ref_load(value, std::memory_order_relaxed); + } + + static auto store(CommonRep &value, CommonRep desired) noexcept -> 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, + std::memory_order_relaxed); + } +}; + +template +struct concurrency::handler { + static constexpr bool enabled = details::atomic_ref_compatible_v; + using injection_type = concurrency::injection; + using result_type = std::expected; + + static auto load(CommonRep const &value) noexcept -> CommonRep { + return details::atomic_ref_load(value, std::memory_order_acquire); + } + + static auto store(CommonRep &value, CommonRep desired) noexcept -> 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, + std::memory_order_acquire); + } +}; + +template +struct concurrency::handler { + static constexpr bool enabled = details::atomic_ref_compatible_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 { 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 { diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 5c0d682..45ae186 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -1,65 +1,148 @@ module; +#include #include #include +#include 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_; } + [[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_; }; - 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 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; diff --git a/tests/basic/test_policies.cpp b/tests/basic/test_policies.cpp index 763073f..7872382 100644 --- a/tests/basic/test_policies.cpp +++ b/tests/basic/test_policies.cpp @@ -1,3 +1,4 @@ +#include #include #include import mcpplibs.primitives; @@ -6,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 { @@ -43,8 +53,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 +82,92 @@ 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(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);