Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/primitive/impl.cppm
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module;
#include <cstddef>
#include <cstdint>
#include <tuple>
#include <type_traits>
Expand Down Expand Up @@ -234,6 +235,20 @@ private:
value_type value_;
};

template <policy::policy_type... Policies, underlying_type T>
constexpr auto with(T value) noexcept(
noexcept(primitive<std::remove_cv_t<T>, Policies...>{value}))
-> primitive<std::remove_cv_t<T>, Policies...> {
return primitive<std::remove_cv_t<T>, Policies...>{value};
}

template <policy::policy_type... Policies, underlying_type T>
constexpr auto with(std::tuple<Policies...>, T value) noexcept(
noexcept(primitive<std::remove_cv_t<T>, Policies...>{value}))
-> primitive<std::remove_cv_t<T>, Policies...> {
return primitive<std::remove_cv_t<T>, Policies...>{value};
}

namespace types {
template <policy::policy_type... Policies>
using Bool = primitive<bool, Policies...>;
Expand All @@ -258,6 +273,10 @@ using U32 = primitive<std::uint32_t, Policies...>;
template <policy::policy_type... Policies>
using U64 = primitive<std::uint64_t, Policies...>;
template <policy::policy_type... Policies>
using Size = primitive<std::size_t, Policies...>;
template <policy::policy_type... Policies>
using Diff = primitive<std::ptrdiff_t, Policies...>;
template <policy::policy_type... Policies>
using I8 = primitive<std::int8_t, Policies...>;
template <policy::policy_type... Policies>
using I16 = primitive<std::int16_t, Policies...>;
Expand Down
16 changes: 8 additions & 8 deletions src/primitive/traits.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace mcpplibs::primitives::meta::details {

template <typename T> struct primitive_traits_impl;

template <typename T, typename PoliciesTuple> struct make_primitive;

template <underlying_type T, policy::policy_type... Policies>
struct primitive_traits_impl<primitive<T, Policies...>> {
using value_type = T;
Expand All @@ -27,21 +29,19 @@ struct primitive_traits_impl<primitive<T, Policies...>> {
policy::resolve_policy_t<policy::category::concurrency, Policies...>;
};

template <underlying_type T, policy::policy_type... Policies>
struct make_primitive<T, std::tuple<Policies...>> {
using type = primitive<T, Policies...>;
};

} // namespace mcpplibs::primitives::meta::details

// Public API exported from this module.
export namespace mcpplibs::primitives::meta {
using policy_category = policy::category;

template <typename T, typename PoliciesTuple> struct make_primitive;

template <underlying_type T, policy::policy_type... Policies>
struct make_primitive<T, std::tuple<Policies...>> {
using type = primitive<T, Policies...>;
};

template <underlying_type T, typename PoliciesTuple>
using make_primitive_t = make_primitive<T, PoliciesTuple>::type;
using make_primitive_t = details::make_primitive<T, PoliciesTuple>::type;
Comment thread
FrozenLemonTee marked this conversation as resolved.

using default_policies =
std::tuple<policy::resolve_policy_t<policy::category::value>,
Expand Down
115 changes: 115 additions & 0 deletions src/underlying/impl.cppm
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
module;
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <limits>
#include <type_traits>

export module mcpplibs.primitives.underlying.impl;
Expand Down Expand Up @@ -34,4 +39,114 @@ struct mcpplibs::primitives::underlying::traits<T> {
static constexpr bool is_valid_rep(rep_type) noexcept { return true; }
};

namespace mcpplibs::primitives::underlying::details {

template <std::integral T>
consteval auto cast_integer_literal(unsigned long long value) -> T {
if (value > static_cast<unsigned long long>(std::numeric_limits<T>::max())) {
throw std::out_of_range{"integer literal is out of range for target underlying type"};
}

return static_cast<T>(value);
}

template <std::floating_point T>
consteval auto cast_floating_literal(long double value) -> T {
if (!(value < 0 || value >= 0)) {
Comment thread
FrozenLemonTee marked this conversation as resolved.
Outdated
throw std::out_of_range{"floating literal must be finite"};
}

auto const lowest = static_cast<long double>(std::numeric_limits<T>::lowest());
if (auto const max = static_cast<long double>(std::numeric_limits<T>::max());
value < lowest || value > max) {
throw std::out_of_range{"floating literal is out of range for target underlying type"};
}

return static_cast<T>(value);
}

} // namespace mcpplibs::primitives::underlying::details

export namespace mcpplibs::primitives::literals {

consteval auto operator""_uchar(const char value) -> unsigned char {
return static_cast<unsigned char>(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<std::uint8_t>(value);
}

consteval auto operator""_u16(const unsigned long long value) -> std::uint16_t {
return underlying::details::cast_integer_literal<std::uint16_t>(value);
}

consteval auto operator""_u32(const unsigned long long value) -> std::uint32_t {
return underlying::details::cast_integer_literal<std::uint32_t>(value);
}

consteval auto operator""_u64(const unsigned long long value) -> std::uint64_t {
return underlying::details::cast_integer_literal<std::uint64_t>(value);
}

consteval auto operator""_size(const unsigned long long value) -> std::size_t {
return underlying::details::cast_integer_literal<std::size_t>(value);
}

consteval auto operator""_diff(const unsigned long long value)
-> std::ptrdiff_t {
return underlying::details::cast_integer_literal<std::ptrdiff_t>(value);
}

consteval auto operator""_i8(const unsigned long long value) -> std::int8_t {
return underlying::details::cast_integer_literal<std::int8_t>(value);
}

consteval auto operator""_i16(const unsigned long long value) -> std::int16_t {
return underlying::details::cast_integer_literal<std::int16_t>(value);
}

consteval auto operator""_i32(const unsigned long long value) -> std::int32_t {
return underlying::details::cast_integer_literal<std::int32_t>(value);
}

consteval auto operator""_i64(const unsigned long long value) -> std::int64_t {
return underlying::details::cast_integer_literal<std::int64_t>(value);
}

consteval auto operator""_f32(const unsigned long long value) -> float {
return underlying::details::cast_floating_literal<float>(
static_cast<long double>(value));
}

consteval auto operator""_f32(const long double value) -> float {
return underlying::details::cast_floating_literal<float>(value);
}

consteval auto operator""_f64(const unsigned long long value) -> double {
return underlying::details::cast_floating_literal<double>(
static_cast<long double>(value));
}

consteval auto operator""_f64(const long double value) -> double {
return underlying::details::cast_floating_literal<double>(value);
}

consteval auto operator""_f80(const unsigned long long value) -> long double {
return underlying::details::cast_floating_literal<long double>(
static_cast<long double>(value));
}

consteval auto operator""_f80(const long double value) -> long double {
return underlying::details::cast_floating_literal<long double>(value);
}

} // namespace mcpplibs::primitives::literals
74 changes: 74 additions & 0 deletions tests/basic/primitive/factory/test_make_primitive.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include <gtest/gtest.h>
#include <type_traits>

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<policy::value::checked, policy::error::expected>;

auto value = with<policy::value::checked, policy::error::expected>(42_i32);

static_assert(std::same_as<decltype(value), expected_t>);
EXPECT_EQ(value.load(), 42);
}

TEST(PrimitiveFactoryTest, UsesDefaultPoliciesWhenNoPolicyIsSpecified) {
using expected_t = types::I32<>;
using meta_t = meta::traits<expected_t>;

auto value = with(42_i32);

static_assert(std::same_as<decltype(value), expected_t>);
static_assert(std::same_as<meta_t::value_policy,
policy::defaults::value>);
static_assert(
std::same_as<meta_t::type_policy, policy::defaults::type>);
static_assert(
std::same_as<meta_t::error_policy, policy::defaults::error>);
static_assert(std::same_as<meta_t::concurrency_policy,
policy::defaults::concurrency>);
EXPECT_EQ(value.load(), 42);
}

TEST(PrimitiveFactoryTest, DeducesSizeAndDiffPrimitiveAliases) {
auto sizeValue = with(42_size);
auto diffValue = with(42_diff);

static_assert(std::same_as<decltype(sizeValue), types::Size<>>);
static_assert(std::same_as<decltype(diffValue), types::Diff<>>);
EXPECT_EQ(sizeValue.load(), static_cast<std::size_t>(42));
EXPECT_EQ(diffValue.load(), static_cast<std::ptrdiff_t>(42));
}

TEST(PrimitiveFactoryTest, AcceptsPoliciesTupleInput) {
using policies_t =
meta::traits<types::I32<policy::value::checked,
policy::error::expected>>::policies;
using expected_t = meta::make_primitive_t<std::int32_t, policies_t>;

auto value1 = with(policies_t{}, 42_i32);
auto value2 = with(meta::traits<decltype(value1)>::policies{}, 42_i32);

static_assert(std::same_as<decltype(value1), expected_t>);
static_assert(std::same_as<decltype(value2), expected_t>);
EXPECT_EQ(value1.load(), 42);
EXPECT_EQ(value2.load(), 42_i32);
}

TEST(PrimitiveFactoryTest, MakesPrimitiveFromDeducedCustomUnderlying) {
using expected_t =
primitive<UserInteger, policy::value::checked, policy::type::compatible>;

auto value =
with<policy::value::checked, policy::type::compatible>(UserInteger{7});

static_assert(std::same_as<decltype(value), expected_t>);
EXPECT_EQ(value.load().value, 7);
}
59 changes: 59 additions & 0 deletions tests/basic/underlying/literals/test_literals.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include <cstddef>
#include <cstdint>
#include <gtest/gtest.h>
#include <type_traits>

import mcpplibs.primitives.underlying;

using namespace mcpplibs::primitives::literals;

TEST(UnderlyingLiteralsTest, IntegerLiteralsReturnExpectedUnderlyingTypes) {
static_assert(std::same_as<decltype(42_u8), std::uint8_t>);
static_assert(std::same_as<decltype(42_u16), std::uint16_t>);
static_assert(std::same_as<decltype(42_u32), std::uint32_t>);
static_assert(std::same_as<decltype(42_u64), std::uint64_t>);
static_assert(std::same_as<decltype(42_size), std::size_t>);
static_assert(std::same_as<decltype(42_diff), std::ptrdiff_t>);
static_assert(std::same_as<decltype(42_i8), std::int8_t>);
static_assert(std::same_as<decltype(42_i16), std::int16_t>);
static_assert(std::same_as<decltype(42_i32), std::int32_t>);
static_assert(std::same_as<decltype(42_i64), std::int64_t>);

EXPECT_EQ(42_u8, static_cast<std::uint8_t>(42));
EXPECT_EQ(42_u16, static_cast<std::uint16_t>(42));
EXPECT_EQ(42_u32, static_cast<std::uint32_t>(42));
EXPECT_EQ(42_u64, static_cast<std::uint64_t>(42));
EXPECT_EQ(42_size, static_cast<std::size_t>(42));
EXPECT_EQ(42_diff, static_cast<std::ptrdiff_t>(42));
EXPECT_EQ(42_i8, static_cast<std::int8_t>(42));
EXPECT_EQ(42_i16, static_cast<std::int16_t>(42));
EXPECT_EQ(42_i32, static_cast<std::int32_t>(42));
EXPECT_EQ(42_i64, static_cast<std::int64_t>(42));
}

TEST(UnderlyingLiteralsTest, FloatingLiteralsReturnExpectedUnderlyingTypes) {
static_assert(std::same_as<decltype(1.25_f32), float>);
static_assert(std::same_as<decltype(1.25_f64), double>);
static_assert(std::same_as<decltype(1.25_f80), long double>);

EXPECT_FLOAT_EQ(1.25_f32, 1.25f);
EXPECT_DOUBLE_EQ(1.25_f64, 1.25);
EXPECT_EQ(1.25_f80, static_cast<long double>(1.25));
EXPECT_FLOAT_EQ(2_f32, 2.0f);
EXPECT_DOUBLE_EQ(2_f64, 2.0);
EXPECT_EQ(2_f80, static_cast<long double>(2.0));
}

TEST(UnderlyingLiteralsTest, CharacterLiteralsReturnExpectedUnderlyingTypes) {
static_assert(std::same_as<decltype('A'_uchar), unsigned char>);
static_assert(std::same_as<decltype(u8'A'_char8), char8_t>);
static_assert(std::same_as<decltype(u'A'_char16), char16_t>);
static_assert(std::same_as<decltype(U'A'_char32), char32_t>);
static_assert(std::same_as<decltype(L'A'_wchar), wchar_t>);

EXPECT_EQ('A'_uchar, static_cast<unsigned char>('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');
}
Loading