From 819add9e42ebb5dca332a557593a4f9788532da8 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 10:59:19 +0800 Subject: [PATCH 01/14] feat: Add literal operators for integral and floating-point types with range checks Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/underlying/impl.cppm | 106 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/underlying/impl.cppm b/src/underlying/impl.cppm index dbfccd7..4a966d0 100644 --- a/src/underlying/impl.cppm +++ b/src/underlying/impl.cppm @@ -1,4 +1,8 @@ module; +#include +#include +#include +#include #include export module mcpplibs.primitives.underlying.impl; @@ -34,4 +38,106 @@ struct mcpplibs::primitives::underlying::traits { static constexpr bool is_valid_rep(rep_type) noexcept { return true; } }; +namespace mcpplibs::primitives::underlying::details { + +template +consteval auto cast_integer_literal(unsigned long long value) -> T { + if (value > static_cast(std::numeric_limits::max())) { + throw std::out_of_range{"integer literal is out of range for target underlying type"}; + } + + return static_cast(value); +} + +template +consteval auto cast_floating_literal(long double value) -> T { + if (!(value < 0 || value >= 0)) { + throw std::out_of_range{"floating literal must be finite"}; + } + + auto const lowest = static_cast(std::numeric_limits::lowest()); + if (auto const max = static_cast(std::numeric_limits::max()); + value < lowest || value > max) { + throw std::out_of_range{"floating literal is out of range for target underlying type"}; + } + + return static_cast(value); +} + +} // namespace mcpplibs::primitives::underlying::details + +export namespace mcpplibs::primitives::literals { + +consteval auto operator""_uchar(const char value) -> unsigned char { + return static_cast(value); +} + +consteval auto operator""_char8(const char8_t value) -> char8_t { return value; } + +consteval auto operator""_char16(const char16_t value) -> char16_t { return value; } + +consteval auto operator""_char32(const char32_t value) -> char32_t { return value; } + +consteval auto operator""_wchar(const wchar_t value) -> wchar_t { return value; } + +consteval auto operator""_u8(const unsigned long long value) -> std::uint8_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_u16(const unsigned long long value) -> std::uint16_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_u32(const unsigned long long value) -> std::uint32_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_u64(const unsigned long long value) -> std::uint64_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_i8(const unsigned long long value) -> std::int8_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_i16(const unsigned long long value) -> std::int16_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_i32(const unsigned long long value) -> std::int32_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_i64(const unsigned long long value) -> std::int64_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_f32(const unsigned long long value) -> float { + return underlying::details::cast_floating_literal( + static_cast(value)); +} + +consteval auto operator""_f32(const long double value) -> float { + return underlying::details::cast_floating_literal(value); +} + +consteval auto operator""_f64(const unsigned long long value) -> double { + return underlying::details::cast_floating_literal( + static_cast(value)); +} + +consteval auto operator""_f64(const long double value) -> double { + return underlying::details::cast_floating_literal(value); +} + +consteval auto operator""_f80(const unsigned long long value) -> long double { + return underlying::details::cast_floating_literal( + static_cast(value)); +} + +consteval auto operator""_f80(const long double value) -> long double { + return underlying::details::cast_floating_literal(value); +} + +} // namespace mcpplibs::primitives::literals From 67670f13eb57b102c57d4fb71a57193dc56d4714 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 10:59:29 +0800 Subject: [PATCH 02/14] feat: Add with function for creating primitive types with policies Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/primitive/impl.cppm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 4b17d15..31fe31d 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -234,6 +234,13 @@ private: value_type value_; }; +template +constexpr auto with(T value) noexcept( + noexcept(primitive, Policies...>{value})) + -> primitive, Policies...> { + return primitive, Policies...>{value}; +} + namespace types { template using Bool = primitive; From d7598f261b5748dcd341bef3541dabeda38d13b2 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 10:59:38 +0800 Subject: [PATCH 03/14] feat: Add unit tests for primitive factory and underlying literals functionality Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- .../primitive/factory/test_make_primitive.cpp | 49 +++++++++++++++++ .../underlying/literals/test_literals.cpp | 54 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 tests/basic/primitive/factory/test_make_primitive.cpp create mode 100644 tests/basic/underlying/literals/test_literals.cpp diff --git a/tests/basic/primitive/factory/test_make_primitive.cpp b/tests/basic/primitive/factory/test_make_primitive.cpp new file mode 100644 index 0000000..c318258 --- /dev/null +++ b/tests/basic/primitive/factory/test_make_primitive.cpp @@ -0,0 +1,49 @@ +#include +#include + +import mcpplibs.primitives; + +#include "../../support/underlying_custom_types.hpp" + +using namespace mcpplibs::primitives; +using namespace mcpplibs::primitives::literals; +using namespace mcpplibs::primitives::test_support::underlying; + +TEST(PrimitiveFactoryTest, MakesPrimitiveFromDeducedStdUnderlying) { + using expected_t = + types::I32; + + auto value = with(42_i32); + + static_assert(std::same_as); + EXPECT_EQ(value.load(), 42); +} + +TEST(PrimitiveFactoryTest, UsesDefaultPoliciesWhenNoPolicyIsSpecified) { + using expected_t = types::I32<>; + using meta_t = meta::traits; + + auto value = with(42_i32); + + static_assert(std::same_as); + static_assert(std::same_as); + static_assert( + std::same_as); + static_assert( + std::same_as); + static_assert(std::same_as); + EXPECT_EQ(value.load(), 42); +} + +TEST(PrimitiveFactoryTest, MakesPrimitiveFromDeducedCustomUnderlying) { + using expected_t = + primitive; + + auto value = + with(UserInteger{7}); + + static_assert(std::same_as); + EXPECT_EQ(value.load().value, 7); +} diff --git a/tests/basic/underlying/literals/test_literals.cpp b/tests/basic/underlying/literals/test_literals.cpp new file mode 100644 index 0000000..7d1278b --- /dev/null +++ b/tests/basic/underlying/literals/test_literals.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +import mcpplibs.primitives.underlying; + +using namespace mcpplibs::primitives::literals; + +TEST(UnderlyingLiteralsTest, IntegerLiteralsReturnExpectedUnderlyingTypes) { + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + + EXPECT_EQ(42_u8, static_cast(42)); + EXPECT_EQ(42_u16, static_cast(42)); + EXPECT_EQ(42_u32, static_cast(42)); + EXPECT_EQ(42_u64, static_cast(42)); + EXPECT_EQ(42_i8, static_cast(42)); + EXPECT_EQ(42_i16, static_cast(42)); + EXPECT_EQ(42_i32, static_cast(42)); + EXPECT_EQ(42_i64, static_cast(42)); +} + +TEST(UnderlyingLiteralsTest, FloatingLiteralsReturnExpectedUnderlyingTypes) { + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + + EXPECT_FLOAT_EQ(1.25_f32, 1.25f); + EXPECT_DOUBLE_EQ(1.25_f64, 1.25); + EXPECT_EQ(1.25_f80, static_cast(1.25)); + EXPECT_FLOAT_EQ(2_f32, 2.0f); + EXPECT_DOUBLE_EQ(2_f64, 2.0); + EXPECT_EQ(2_f80, static_cast(2.0)); +} + +TEST(UnderlyingLiteralsTest, CharacterLiteralsReturnExpectedUnderlyingTypes) { + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + + EXPECT_EQ('A'_uchar, static_cast('A')); + EXPECT_EQ(u8'A'_char8, u8'A'); + EXPECT_EQ(u'A'_char16, u'A'); + EXPECT_EQ(U'A'_char32, U'A'); + EXPECT_EQ(L'A'_wchar, L'A'); +} From 95584325ba52b322e679d15b43058b734fcadbd3 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 11:03:34 +0800 Subject: [PATCH 04/14] feat: Add literal operators for size_t and ptrdiff_t with constexpr support Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/underlying/impl.cppm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/underlying/impl.cppm b/src/underlying/impl.cppm index 4a966d0..d51d8dc 100644 --- a/src/underlying/impl.cppm +++ b/src/underlying/impl.cppm @@ -1,5 +1,6 @@ module; #include +#include #include #include #include @@ -96,6 +97,15 @@ consteval auto operator""_u64(const unsigned long long value) -> std::uint64_t { return underlying::details::cast_integer_literal(value); } +consteval auto operator""_size(const unsigned long long value) -> std::size_t { + return underlying::details::cast_integer_literal(value); +} + +consteval auto operator""_diff(const unsigned long long value) + -> std::ptrdiff_t { + return underlying::details::cast_integer_literal(value); +} + consteval auto operator""_i8(const unsigned long long value) -> std::int8_t { return underlying::details::cast_integer_literal(value); } @@ -140,4 +150,3 @@ consteval auto operator""_f80(const long double value) -> long double { } } // namespace mcpplibs::primitives::literals - From e48b259c6d06eb793f618bb458978ab1b50a6932 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 11:03:40 +0800 Subject: [PATCH 05/14] feat: Add primitive type aliases for size_t and ptrdiff_t Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/primitive/impl.cppm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 31fe31d..40e4840 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -1,4 +1,5 @@ module; +#include #include #include #include @@ -265,6 +266,10 @@ using U32 = primitive; template using U64 = primitive; template +using Size = primitive; +template +using Diff = primitive; +template using I8 = primitive; template using I16 = primitive; From b0399c814d10ff0c390de9b3cf22221cdd88ad8a Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 11:05:10 +0800 Subject: [PATCH 06/14] test: Add tests for size_t and ptrdiff_t literal operators and primitive aliases Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- tests/basic/primitive/factory/test_make_primitive.cpp | 10 ++++++++++ tests/basic/underlying/literals/test_literals.cpp | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/tests/basic/primitive/factory/test_make_primitive.cpp b/tests/basic/primitive/factory/test_make_primitive.cpp index c318258..26c9cf8 100644 --- a/tests/basic/primitive/factory/test_make_primitive.cpp +++ b/tests/basic/primitive/factory/test_make_primitive.cpp @@ -37,6 +37,16 @@ TEST(PrimitiveFactoryTest, UsesDefaultPoliciesWhenNoPolicyIsSpecified) { EXPECT_EQ(value.load(), 42); } +TEST(PrimitiveFactoryTest, DeducesSizeAndDiffPrimitiveAliases) { + auto sizeValue = with(42_size); + auto diffValue = with(42_diff); + + static_assert(std::same_as>); + static_assert(std::same_as>); + EXPECT_EQ(sizeValue.load(), static_cast(42)); + EXPECT_EQ(diffValue.load(), static_cast(42)); +} + TEST(PrimitiveFactoryTest, MakesPrimitiveFromDeducedCustomUnderlying) { using expected_t = primitive; diff --git a/tests/basic/underlying/literals/test_literals.cpp b/tests/basic/underlying/literals/test_literals.cpp index 7d1278b..27aefde 100644 --- a/tests/basic/underlying/literals/test_literals.cpp +++ b/tests/basic/underlying/literals/test_literals.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -11,6 +12,8 @@ TEST(UnderlyingLiteralsTest, IntegerLiteralsReturnExpectedUnderlyingTypes) { static_assert(std::same_as); static_assert(std::same_as); static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); static_assert(std::same_as); static_assert(std::same_as); static_assert(std::same_as); @@ -20,6 +23,8 @@ TEST(UnderlyingLiteralsTest, IntegerLiteralsReturnExpectedUnderlyingTypes) { EXPECT_EQ(42_u16, static_cast(42)); EXPECT_EQ(42_u32, static_cast(42)); EXPECT_EQ(42_u64, static_cast(42)); + EXPECT_EQ(42_size, static_cast(42)); + EXPECT_EQ(42_diff, static_cast(42)); EXPECT_EQ(42_i8, static_cast(42)); EXPECT_EQ(42_i16, static_cast(42)); EXPECT_EQ(42_i32, static_cast(42)); From 4943abc0e4c31798a663448ab3b4341cedc762a8 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 11:23:05 +0800 Subject: [PATCH 07/14] refactor: Move make_primitive implementation to details namespace Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/primitive/traits.cppm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/primitive/traits.cppm b/src/primitive/traits.cppm index bc3af30..0168c7d 100644 --- a/src/primitive/traits.cppm +++ b/src/primitive/traits.cppm @@ -13,6 +13,8 @@ namespace mcpplibs::primitives::meta::details { template struct primitive_traits_impl; +template struct make_primitive; + template struct primitive_traits_impl> { using value_type = T; @@ -27,21 +29,19 @@ struct primitive_traits_impl> { policy::resolve_policy_t; }; +template +struct make_primitive> { + using type = primitive; +}; + } // namespace mcpplibs::primitives::meta::details // Public API exported from this module. export namespace mcpplibs::primitives::meta { using policy_category = policy::category; -template struct make_primitive; - -template -struct make_primitive> { - using type = primitive; -}; - template -using make_primitive_t = make_primitive::type; +using make_primitive_t = details::make_primitive::type; using default_policies = std::tuple, From 7f5a6b0b2f457e941f0efc6e4e9ce4a966622999 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 11:23:14 +0800 Subject: [PATCH 08/14] feat: Add with function for creating primitive types with policies Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/primitive/impl.cppm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 40e4840..0f1ad91 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -242,6 +242,13 @@ constexpr auto with(T value) noexcept( return primitive, Policies...>{value}; } +template +constexpr auto with(std::tuple, T value) noexcept( + noexcept(primitive, Policies...>{value})) + -> primitive, Policies...> { + return primitive, Policies...>{value}; +} + namespace types { template using Bool = primitive; From 11beedecd3d35923bd4b2d5a709cb4336a9c6616 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 11:23:24 +0800 Subject: [PATCH 09/14] test: Add test for policies tuple input in PrimitiveFactory Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- .../primitive/factory/test_make_primitive.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/basic/primitive/factory/test_make_primitive.cpp b/tests/basic/primitive/factory/test_make_primitive.cpp index 26c9cf8..54d75ae 100644 --- a/tests/basic/primitive/factory/test_make_primitive.cpp +++ b/tests/basic/primitive/factory/test_make_primitive.cpp @@ -47,6 +47,21 @@ TEST(PrimitiveFactoryTest, DeducesSizeAndDiffPrimitiveAliases) { EXPECT_EQ(diffValue.load(), static_cast(42)); } +TEST(PrimitiveFactoryTest, AcceptsPoliciesTupleInput) { + using policies_t = + meta::traits>::policies; + using expected_t = meta::make_primitive_t; + + auto value1 = with(policies_t{}, 42_i32); + auto value2 = with(meta::traits::policies{}, 42_i32); + + static_assert(std::same_as); + static_assert(std::same_as); + EXPECT_EQ(value1.load(), 42); + EXPECT_EQ(value2.load(), 42_i32); +} + TEST(PrimitiveFactoryTest, MakesPrimitiveFromDeducedCustomUnderlying) { using expected_t = primitive; From d6a50d837c60109b788d159ec2a90df136be378e Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 12:09:34 +0800 Subject: [PATCH 10/14] refactor: Separate literal operators and validation logic into new literals module Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/underlying/impl.cppm | 117 ----------------- src/underlying/literals.cppm | 236 +++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 117 deletions(-) create mode 100644 src/underlying/literals.cppm diff --git a/src/underlying/impl.cppm b/src/underlying/impl.cppm index d51d8dc..6c39c3a 100644 --- a/src/underlying/impl.cppm +++ b/src/underlying/impl.cppm @@ -1,9 +1,4 @@ module; -#include -#include -#include -#include -#include #include export module mcpplibs.primitives.underlying.impl; @@ -38,115 +33,3 @@ struct mcpplibs::primitives::underlying::traits { static constexpr bool is_valid_rep(rep_type) noexcept { return true; } }; - -namespace mcpplibs::primitives::underlying::details { - -template -consteval auto cast_integer_literal(unsigned long long value) -> T { - if (value > static_cast(std::numeric_limits::max())) { - throw std::out_of_range{"integer literal is out of range for target underlying type"}; - } - - return static_cast(value); -} - -template -consteval auto cast_floating_literal(long double value) -> T { - if (!(value < 0 || value >= 0)) { - throw std::out_of_range{"floating literal must be finite"}; - } - - auto const lowest = static_cast(std::numeric_limits::lowest()); - if (auto const max = static_cast(std::numeric_limits::max()); - value < lowest || value > max) { - throw std::out_of_range{"floating literal is out of range for target underlying type"}; - } - - return static_cast(value); -} - -} // namespace mcpplibs::primitives::underlying::details - -export namespace mcpplibs::primitives::literals { - -consteval auto operator""_uchar(const char value) -> unsigned char { - return static_cast(value); -} - -consteval auto operator""_char8(const char8_t value) -> char8_t { return value; } - -consteval auto operator""_char16(const char16_t value) -> char16_t { return value; } - -consteval auto operator""_char32(const char32_t value) -> char32_t { return value; } - -consteval auto operator""_wchar(const wchar_t value) -> wchar_t { return value; } - -consteval auto operator""_u8(const unsigned long long value) -> std::uint8_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_u16(const unsigned long long value) -> std::uint16_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_u32(const unsigned long long value) -> std::uint32_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_u64(const unsigned long long value) -> std::uint64_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_size(const unsigned long long value) -> std::size_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_diff(const unsigned long long value) - -> std::ptrdiff_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_i8(const unsigned long long value) -> std::int8_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_i16(const unsigned long long value) -> std::int16_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_i32(const unsigned long long value) -> std::int32_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_i64(const unsigned long long value) -> std::int64_t { - return underlying::details::cast_integer_literal(value); -} - -consteval auto operator""_f32(const unsigned long long value) -> float { - return underlying::details::cast_floating_literal( - static_cast(value)); -} - -consteval auto operator""_f32(const long double value) -> float { - return underlying::details::cast_floating_literal(value); -} - -consteval auto operator""_f64(const unsigned long long value) -> double { - return underlying::details::cast_floating_literal( - static_cast(value)); -} - -consteval auto operator""_f64(const long double value) -> double { - return underlying::details::cast_floating_literal(value); -} - -consteval auto operator""_f80(const unsigned long long value) -> long double { - return underlying::details::cast_floating_literal( - static_cast(value)); -} - -consteval auto operator""_f80(const long double value) -> long double { - return underlying::details::cast_floating_literal(value); -} - -} // namespace mcpplibs::primitives::literals diff --git a/src/underlying/literals.cppm b/src/underlying/literals.cppm new file mode 100644 index 0000000..62482b6 --- /dev/null +++ b/src/underlying/literals.cppm @@ -0,0 +1,236 @@ +module; + +#include +#include // NOLINT +#include +#include +#include +#include + +export module mcpplibs.primitives.underlying.literals; + +import mcpplibs.primitives.algorithms.limits; +import mcpplibs.primitives.conversion.traits; +import mcpplibs.primitives.underlying; + +namespace mcpplibs::primitives::underlying::details { + +template +consteval auto throw_literal_risk() -> void { + if constexpr (Kind == conversion::risk::kind::overflow || + Kind == conversion::risk::kind::underflow) { + throw std::out_of_range{ + "numeric literal is out of range for target underlying type"}; + } else if constexpr (Kind == conversion::risk::kind::precision_loss) { + throw std::invalid_argument{ + "numeric literal loses precision for target underlying type"}; + } else if constexpr (Kind == conversion::risk::kind::domain_error) { + throw std::invalid_argument{ + "numeric literal must be finite for target underlying type"}; + } else { + throw std::invalid_argument{ + "numeric literal is not representable for target underlying type"}; + } +} + +template +consteval auto ordered(T value) -> bool { + return (value < static_cast(0)) || (value >= static_cast(0)); +} + +template +consteval auto finite(T value) -> bool { + return ordered(value) && + value >= algorithms::lowest_value() && + value <= algorithms::max_value(); +} + +template +consteval auto checked_integral_literal(unsigned long long value) -> To { + using value_type = std::remove_cv_t; + constexpr auto max_value = + static_cast(algorithms::max_value()); + + if (value > max_value) { + throw_literal_risk(); + } + + return static_cast(value); +} + +template +consteval auto checked_floating_literal(From value) -> To { + using source_type = std::remove_cv_t; + using value_type = std::remove_cv_t; + + if constexpr (std_floating) { + if (!ordered(value)) { + throw_literal_risk(); + } + } + + auto const normalized = static_cast(value); + auto const lowest = static_cast(algorithms::lowest_value()); + auto const max = static_cast(algorithms::max_value()); + + if (normalized < lowest) { + throw_literal_risk(); + } + if (normalized > max) { + throw_literal_risk(); + } + + auto const converted = static_cast(value); + + if constexpr (std_floating) { + if (!finite(converted)) { + if constexpr (std::signed_integral || std_floating) { + if (value < static_cast(0)) { + throw_literal_risk(); + } + } + throw_literal_risk(); + } + } + + if constexpr (std::integral) { + if (static_cast(converted) != value) { + throw_literal_risk(); + } + } else { + auto const roundtrip = static_cast(converted); + if (!ordered(roundtrip)) { + throw_literal_risk(); + } + if (roundtrip != value) { + if (converted == static_cast(0) && + value != static_cast(0)) { + throw_literal_risk(); + } + throw_literal_risk(); + } + } + + return converted; +} + +template +consteval auto parse_unsigned_decimal_literal() -> unsigned long long { + constexpr char input[] {Cs..., '\0'}; + constexpr auto max_value = std::numeric_limits::max(); + + unsigned long long value {}; + for (std::size_t i = 0; i < sizeof...(Cs); ++i) { + auto const ch = input[i]; + if (ch < '0' || ch > '9') { + throw std::invalid_argument{"invalid integer literal"}; + } + + auto const digit = static_cast(ch - '0'); + if (value > max_value / 10ULL || + (value == max_value / 10ULL && digit > max_value % 10ULL)) { + throw std::out_of_range{"integer literal is out of range"}; + } + + value = value * 10ULL + digit; + } + + return value; +} + +template +consteval auto literal_integral() -> To { + return checked_integral_literal(parse_unsigned_decimal_literal()); +} + +} // namespace mcpplibs::primitives::underlying::details + +export namespace mcpplibs::primitives::literals { + +consteval auto operator""_uchar(const char value) -> unsigned char { + return static_cast(value); +} + +consteval auto operator""_char8(const char8_t value) -> char8_t { return value; } + +consteval auto operator""_char16(const char16_t value) -> char16_t { return value; } + +consteval auto operator""_char32(const char32_t value) -> char32_t { return value; } + +consteval auto operator""_wchar(const wchar_t value) -> wchar_t { return value; } + +template +consteval auto operator""_u8() -> std::uint8_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_u16() -> std::uint16_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_u32() -> std::uint32_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_u64() -> std::uint64_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_size() -> std::size_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_diff() -> std::ptrdiff_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_i8() -> std::int8_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_i16() -> std::int16_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_i32() -> std::int32_t { + return underlying::details::literal_integral(); +} + +template +consteval auto operator""_i64() -> std::int64_t { + return underlying::details::literal_integral(); +} + +consteval auto operator""_f32(const unsigned long long value) -> float { + return underlying::details::checked_floating_literal(value); +} + +consteval auto operator""_f32(const long double value) -> float { + return underlying::details::checked_floating_literal(value); +} + +consteval auto operator""_f64(const unsigned long long value) -> double { + return underlying::details::checked_floating_literal(value); +} + +consteval auto operator""_f64(const long double value) -> double { + return underlying::details::checked_floating_literal(value); +} + +consteval auto operator""_f80(const unsigned long long value) -> long double { + return underlying::details::checked_floating_literal(value); +} + +consteval auto operator""_f80(const long double value) -> long double { + return underlying::details::checked_floating_literal(value); +} + +} // namespace mcpplibs::primitives::literals From faa87cd44722ee28f6c4fbda31cbcb00ddd6b5a5 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 12:09:40 +0800 Subject: [PATCH 11/14] refactor: Import literals module into primitives for better organization Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/primitives.cppm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/primitives.cppm b/src/primitives.cppm index cfa671f..86ee0e2 100644 --- a/src/primitives.cppm +++ b/src/primitives.cppm @@ -8,3 +8,4 @@ export import mcpplibs.primitives.primitive; export import mcpplibs.primitives.operations; export import mcpplibs.primitives.algorithms; export import mcpplibs.primitives.conversion; +export import mcpplibs.primitives.underlying.literals; From 3c9841d3b11fed0a24816b0ffa35858e6113e435 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 12:09:47 +0800 Subject: [PATCH 12/14] test: Add static assertions for literal overflow and precision loss probes Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- .../underlying/literals/test_literals.cpp | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/tests/basic/underlying/literals/test_literals.cpp b/tests/basic/underlying/literals/test_literals.cpp index 27aefde..dc5da63 100644 --- a/tests/basic/underlying/literals/test_literals.cpp +++ b/tests/basic/underlying/literals/test_literals.cpp @@ -3,10 +3,50 @@ #include #include -import mcpplibs.primitives.underlying; +import mcpplibs.primitives.underlying.literals; using namespace mcpplibs::primitives::literals; +namespace { + +template struct literal_value_tag; + +template +concept literal_available = requires { typename literal_value_tag; }; + +struct U8OverflowProbe { + static consteval auto value() { return 256_u8; } +}; + +struct I8OverflowProbe { + static consteval auto value() { return 128_i8; } +}; + +struct F32PrecisionLossProbe { + static consteval auto value() { return 16777217_f32; } +}; + +struct F64PrecisionLossProbe { + static consteval auto value() { return 9007199254740993_f64; } +}; + +struct F32OverflowProbe { + static consteval auto value() { return 1.0e39_f32; } +}; + +struct F32UnderflowProbe { + static consteval auto value() { return 1.0e-50_f32; } +}; + +static_assert(!literal_available); +static_assert(!literal_available); +static_assert(!literal_available); +static_assert(!literal_available); +static_assert(!literal_available); +static_assert(!literal_available); + +} // namespace + TEST(UnderlyingLiteralsTest, IntegerLiteralsReturnExpectedUnderlyingTypes) { static_assert(std::same_as); static_assert(std::same_as); @@ -35,10 +75,14 @@ TEST(UnderlyingLiteralsTest, FloatingLiteralsReturnExpectedUnderlyingTypes) { static_assert(std::same_as); static_assert(std::same_as); static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); EXPECT_FLOAT_EQ(1.25_f32, 1.25f); EXPECT_DOUBLE_EQ(1.25_f64, 1.25); EXPECT_EQ(1.25_f80, static_cast(1.25)); + EXPECT_FLOAT_EQ(16777216_f32, 16777216.0f); + EXPECT_DOUBLE_EQ(9007199254740992_f64, 9007199254740992.0); EXPECT_FLOAT_EQ(2_f32, 2.0f); EXPECT_DOUBLE_EQ(2_f64, 2.0); EXPECT_EQ(2_f80, static_cast(2.0)); From 2b08bda7e788b58907d1f894762ab959951a66e2 Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 12:21:21 +0800 Subject: [PATCH 13/14] refactor: Enhance floating-point literal handling with range checks and precision validation Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- src/underlying/literals.cppm | 58 +++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/underlying/literals.cppm b/src/underlying/literals.cppm index 62482b6..b104166 100644 --- a/src/underlying/literals.cppm +++ b/src/underlying/literals.cppm @@ -45,6 +45,16 @@ consteval auto finite(T value) -> bool { value <= algorithms::max_value(); } +template +consteval auto out_of_floating_range(From value) -> bool { + using value_type = std::remove_cv_t; + auto const normalized = static_cast(value); + auto const lowest = + static_cast(algorithms::lowest_value()); + auto const max = static_cast(algorithms::max_value()); + return normalized < lowest || normalized > max; +} + template consteval auto checked_integral_literal(unsigned long long value) -> To { using value_type = std::remove_cv_t; @@ -69,14 +79,12 @@ consteval auto checked_floating_literal(From value) -> To { } } - auto const normalized = static_cast(value); - auto const lowest = static_cast(algorithms::lowest_value()); - auto const max = static_cast(algorithms::max_value()); - - if (normalized < lowest) { - throw_literal_risk(); - } - if (normalized > max) { + if (out_of_floating_range(value)) { + if constexpr (std::signed_integral || std_floating) { + if (value < static_cast(0)) { + throw_literal_risk(); + } + } throw_literal_risk(); } @@ -93,6 +101,16 @@ consteval auto checked_floating_literal(From value) -> To { } } + return converted; +} + +template +consteval auto exact_floating_literal(From value) -> To { + using source_type = std::remove_cv_t; + using value_type = std::remove_cv_t; + + auto const converted = checked_floating_literal(value); + if constexpr (std::integral) { if (static_cast(converted) != value) { throw_literal_risk(); @@ -217,6 +235,14 @@ consteval auto operator""_f32(const long double value) -> float { return underlying::details::checked_floating_literal(value); } +consteval auto operator""_f32e(const unsigned long long value) -> float { + return underlying::details::exact_floating_literal(value); +} + +consteval auto operator""_f32e(const long double value) -> float { + return underlying::details::exact_floating_literal(value); +} + consteval auto operator""_f64(const unsigned long long value) -> double { return underlying::details::checked_floating_literal(value); } @@ -225,6 +251,14 @@ consteval auto operator""_f64(const long double value) -> double { return underlying::details::checked_floating_literal(value); } +consteval auto operator""_f64e(const unsigned long long value) -> double { + return underlying::details::exact_floating_literal(value); +} + +consteval auto operator""_f64e(const long double value) -> double { + return underlying::details::exact_floating_literal(value); +} + consteval auto operator""_f80(const unsigned long long value) -> long double { return underlying::details::checked_floating_literal(value); } @@ -233,4 +267,12 @@ consteval auto operator""_f80(const long double value) -> long double { return underlying::details::checked_floating_literal(value); } +consteval auto operator""_f80e(const unsigned long long value) -> long double { + return underlying::details::exact_floating_literal(value); +} + +consteval auto operator""_f80e(const long double value) -> long double { + return underlying::details::exact_floating_literal(value); +} + } // namespace mcpplibs::primitives::literals From 0041343757828210fcdf96790c2bf9f6925790cb Mon Sep 17 00:00:00 2001 From: FrozenlemonTee <1115306170@qq.com> Date: Fri, 27 Mar 2026 12:21:31 +0800 Subject: [PATCH 14/14] test: Rename precision loss and underflow probes for clarity and consistency Signed-off-by: FrozenlemonTee <1115306170@qq.com> --- .../underlying/literals/test_literals.cpp | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/tests/basic/underlying/literals/test_literals.cpp b/tests/basic/underlying/literals/test_literals.cpp index dc5da63..970f0da 100644 --- a/tests/basic/underlying/literals/test_literals.cpp +++ b/tests/basic/underlying/literals/test_literals.cpp @@ -22,28 +22,33 @@ struct I8OverflowProbe { static consteval auto value() { return 128_i8; } }; -struct F32PrecisionLossProbe { - static consteval auto value() { return 16777217_f32; } +struct F32ExactPrecisionLossProbe { + static consteval auto value() { return 16777217_f32e; } }; -struct F64PrecisionLossProbe { - static consteval auto value() { return 9007199254740993_f64; } +struct F64ExactPrecisionLossProbe { + static consteval auto value() { return 9007199254740993_f64e; } }; struct F32OverflowProbe { static consteval auto value() { return 1.0e39_f32; } }; -struct F32UnderflowProbe { - static consteval auto value() { return 1.0e-50_f32; } +struct F32ExactOverflowProbe { + static consteval auto value() { return 1.0e39_f32e; } +}; + +struct F32ExactUnderflowProbe { + static consteval auto value() { return 1.0e-50_f32e; } }; static_assert(!literal_available); static_assert(!literal_available); -static_assert(!literal_available); -static_assert(!literal_available); +static_assert(!literal_available); +static_assert(!literal_available); static_assert(!literal_available); -static_assert(!literal_available); +static_assert(!literal_available); +static_assert(!literal_available); } // namespace @@ -73,19 +78,40 @@ TEST(UnderlyingLiteralsTest, IntegerLiteralsReturnExpectedUnderlyingTypes) { TEST(UnderlyingLiteralsTest, FloatingLiteralsReturnExpectedUnderlyingTypes) { static_assert(std::same_as); + static_assert(std::same_as); static_assert(std::same_as); + static_assert(std::same_as); static_assert(std::same_as); + static_assert(std::same_as); static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); EXPECT_FLOAT_EQ(1.25_f32, 1.25f); + EXPECT_FLOAT_EQ(1.25_f32e, 1.25f); EXPECT_DOUBLE_EQ(1.25_f64, 1.25); + EXPECT_DOUBLE_EQ(1.25_f64e, 1.25); EXPECT_EQ(1.25_f80, static_cast(1.25)); + EXPECT_EQ(1.25_f80e, static_cast(1.25)); EXPECT_FLOAT_EQ(16777216_f32, 16777216.0f); + EXPECT_FLOAT_EQ(16777216_f32e, 16777216.0f); + EXPECT_FLOAT_EQ(16777217_f32, static_cast(16777217.0L)); EXPECT_DOUBLE_EQ(9007199254740992_f64, 9007199254740992.0); + EXPECT_DOUBLE_EQ(9007199254740992_f64e, 9007199254740992.0); + EXPECT_DOUBLE_EQ(9007199254740993_f64, static_cast(9007199254740993.0L)); + EXPECT_FLOAT_EQ(0.1_f32, static_cast(0.1L)); EXPECT_FLOAT_EQ(2_f32, 2.0f); + EXPECT_FLOAT_EQ(2_f32e, 2.0f); + EXPECT_DOUBLE_EQ(0.1_f64, static_cast(0.1L)); EXPECT_DOUBLE_EQ(2_f64, 2.0); + EXPECT_DOUBLE_EQ(2_f64e, 2.0); EXPECT_EQ(2_f80, static_cast(2.0)); + EXPECT_EQ(2_f80e, static_cast(2.0)); } TEST(UnderlyingLiteralsTest, CharacterLiteralsReturnExpectedUnderlyingTypes) {