diff --git a/src/primitive/impl.cppm b/src/primitive/impl.cppm index 4b17d15..0f1ad91 100644 --- a/src/primitive/impl.cppm +++ b/src/primitive/impl.cppm @@ -1,4 +1,5 @@ module; +#include #include #include #include @@ -234,6 +235,20 @@ private: value_type value_; }; +template +constexpr auto with(T value) noexcept( + noexcept(primitive, Policies...>{value})) + -> primitive, Policies...> { + 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; @@ -258,6 +273,10 @@ using U32 = primitive; template using U64 = primitive; template +using Size = primitive; +template +using Diff = primitive; +template using I8 = primitive; template using I16 = primitive; 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, 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; diff --git a/src/underlying/impl.cppm b/src/underlying/impl.cppm index dbfccd7..6c39c3a 100644 --- a/src/underlying/impl.cppm +++ b/src/underlying/impl.cppm @@ -33,5 +33,3 @@ struct mcpplibs::primitives::underlying::traits { static constexpr bool is_valid_rep(rep_type) noexcept { return true; } }; - - diff --git a/src/underlying/literals.cppm b/src/underlying/literals.cppm new file mode 100644 index 0000000..b104166 --- /dev/null +++ b/src/underlying/literals.cppm @@ -0,0 +1,278 @@ +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 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; + 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(); + } + } + + if (out_of_floating_range(value)) { + if constexpr (std::signed_integral || std_floating) { + if (value < static_cast(0)) { + throw_literal_risk(); + } + } + 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(); + } + } + + 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(); + } + } 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""_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); +} + +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); +} + +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 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..54d75ae --- /dev/null +++ b/tests/basic/primitive/factory/test_make_primitive.cpp @@ -0,0 +1,74 @@ +#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, 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, 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; + + 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..970f0da --- /dev/null +++ b/tests/basic/underlying/literals/test_literals.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include + +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 F32ExactPrecisionLossProbe { + static consteval auto value() { return 16777217_f32e; } +}; + +struct F64ExactPrecisionLossProbe { + static consteval auto value() { return 9007199254740993_f64e; } +}; + +struct F32OverflowProbe { + static consteval auto value() { return 1.0e39_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); + +} // namespace + +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); + 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_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)); + 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); + 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) { + 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'); +}