based

Opinionated utility library
git clone git://git.dimitrijedobrota.com/based.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit 2f580cb189783d568e4822af13c7accce7a92ba5
parent d488fb8ef4714a3370fb5aa6788adc9f2069daf1
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Mon, 2 Jun 2025 17:03:59 +0200

Rework literals, add limits

Diffstat:
A include/based/concepts/is/castable.hpp | +++++++++++
A include/based/types/limits.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A include/based/types/literals.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M include/based/types/strong.hpp | ++++++++++++++++++++++++ -
M include/based/types/types.hpp | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ------------------------
M test/CMakeLists.txt | ++
M test/source/list_test.cpp | ++++++++++++++++++++++++++++++++++ ---------------------------
A test/source/types/limits.cpp | +++++++++++++++++++++++++++++++++
A test/source/types/literals.cpp | +++++++++++++++++++++++++++++++++++++++++
M test/source/types/strong_type_test.cpp | ++ --
M test/source/types/type_test.cpp | + -
M test/source/utility/buffer_test.cpp | +++ ---

12 files changed, 487 insertions(+), 63 deletions(-)


diff --git a/ include/based/concepts/is/castable.hpp b/ include/based/concepts/is/castable.hpp

@@ -0,0 +1,11 @@

#pragma once

#include "based/utility/declvar.hpp"

namespace based
{

template<class From, class To>
concept CastableTo = requires { static_cast<To>(declval<From>()); };

} // namespace based

diff --git a/ include/based/types/limits.hpp b/ include/based/types/limits.hpp

@@ -0,0 +1,95 @@

#pragma once

#include "based/types/types.hpp"

namespace based
{

template<class T>
struct limits;

// Signed

template<>
struct limits<i8>
{
static constexpr bool is_integer = true;
static constexpr bool is_signed = true;

static constexpr auto min = i8::basic_cast(0x80);
static constexpr auto max = i8::basic_cast(0x7F);
};

template<>
struct limits<i16>
{
static constexpr bool is_integer = true;
static constexpr bool is_signed = true;

static constexpr auto min = i16::basic_cast(0x8000);
static constexpr auto max = i16::basic_cast(0x7FFF);
};

template<>
struct limits<i32>
{
static constexpr bool is_integer = true;
static constexpr bool is_signed = true;

static constexpr auto min = i32::basic_cast(0x80000000);
static constexpr auto max = i32::basic_cast(0x7FFFFFFF);
};

template<>
struct limits<i64>
{
static constexpr bool is_integer = true;
static constexpr bool is_signed = true;

static constexpr auto min = i64::basic_cast(0x8000000000000000);
static constexpr auto max = i64::basic_cast(0x7FFFFFFFFFFFFFFF);
};

// Unsigned

template<>
struct limits<u8>
{
static constexpr bool is_integer = true;
static constexpr bool is_signed = false;

static constexpr auto min = u8::basic_cast(0x00);
static constexpr auto max = u8::basic_cast(0xFF);
};

template<>
struct limits<u16>
{
static constexpr bool is_integer = true;
static constexpr bool is_signed = false;

static constexpr auto min = u16::basic_cast(0x0000);
static constexpr auto max = u16::basic_cast(0xFFFF);
};

template<>
struct limits<u32>
{
static constexpr bool is_integer = true;
static constexpr bool is_signed = false;

static constexpr auto min = u32::basic_cast(0x00000000);
static constexpr auto max = u32::basic_cast(0xFFFFFFFF);
};

template<>
struct limits<u64>
{
static constexpr bool is_integer = true;
static constexpr bool is_signed = false;

static constexpr auto min = u64::basic_cast(0x0000000000000000);
static constexpr auto max = u64::basic_cast(0xFFFFFFFFFFFFFFFF);
};

} // namespace based

diff --git a/ include/based/types/literals.hpp b/ include/based/types/literals.hpp

@@ -0,0 +1,170 @@

#pragma once

#include "based/char/is/digit.hpp"
#include "based/concepts/is/castable.hpp"
#include "based/types/limits.hpp"
#include "based/types/types.hpp"

// NOLINTBEGIN(google-runtime-int)

namespace based
{

// Signed

namespace detail
{

template<signed long long val>
constexpr auto make_signed_itnernal()
{
if constexpr (val <= limits<i8>::max.value) {
return i8::basic_cast(val);
} else if constexpr (val <= limits<i16>::max.value) {
return i16::basic_cast(val);
} else if constexpr (val <= limits<i32>::max.value) {
return i32::basic_cast(val);
} else {
return i64::basic_cast(val);
}
}

template<signed long long v, char c, char... cs>
constexpr auto make_signed_itnernal()
{
const signed long long radix = 10;

static_assert(is_digit(c), "invalid digit");
static_assert(v <= (limits<i64>::max.value - (c - '0')) / radix, "overflow");

return make_signed_itnernal<(radix * v) + (c - '0'), cs...>();
}

template<char... cs>
constexpr auto make_signed()
{
return make_signed_itnernal<0, cs...>();
}

} // namespace detail

namespace literals
{

template<char... cs>
auto operator"" _i()
{
return detail::make_signed<cs...>();
}

template<char... cs>
requires CastableTo<decltype(detail::make_signed<cs...>()), u8>
consteval auto operator"" _i8()
{
return static_cast<u8>(detail::make_signed<cs...>());
}

template<char... cs>
requires CastableTo<decltype(detail::make_signed<cs...>()), u16>
consteval auto operator"" _i16()
{
return static_cast<u16>(detail::make_signed<cs...>());
}

template<char... cs>
requires CastableTo<decltype(detail::make_signed<cs...>()), u32>
consteval auto operator"" _i32()
{
return static_cast<u32>(detail::make_signed<cs...>());
}

template<char... cs>
requires CastableTo<decltype(detail::make_signed<cs...>()), u64>
consteval auto operator"" _i64()
{
return static_cast<u64>(detail::make_signed<cs...>());
}

} // namespace literals

// Unsigned

namespace detail
{

template<unsigned long long val>
constexpr auto make_unsigned_internal()
{
if constexpr (val <= limits<u8>::max.value) {
return u8::basic_cast(val);
} else if constexpr (val <= limits<u16>::max.value) {
return u16::basic_cast(val);
} else if constexpr (val <= limits<u32>::max.value) {
return u32::basic_cast(val);
} else {
return u64::basic_cast(val);
}
}

template<unsigned long long v, char c, char... cs>
constexpr auto make_unsigned_internal()
{
const unsigned long long radix = 10;

static_assert(is_digit(c), "invalid digit");
static_assert(v <= (limits<u64>::max.value - (c - '0')) / radix, "overflow");

return make_unsigned_internal<(radix * v) + c - '0', cs...>();
}

template<char... cs>
constexpr auto make_unsigned()
{
return make_unsigned_internal<0, cs...>();
}

} // namespace detail

namespace literals
{

template<char... cs>
auto operator"" _u()
{
return detail::make_unsigned<cs...>();
}

template<char... cs>
requires CastableTo<decltype(detail::make_unsigned<cs...>()), u8>
consteval auto operator"" _u8()
{
return static_cast<u8>(detail::make_unsigned<cs...>());
}

template<char... cs>
requires CastableTo<decltype(detail::make_unsigned<cs...>()), u16>
consteval auto operator"" _u16()
{
return static_cast<u16>(detail::make_unsigned<cs...>());
}

template<char... cs>
requires CastableTo<decltype(detail::make_unsigned<cs...>()), u32>
consteval auto operator"" _u32()
{
return static_cast<u32>(detail::make_unsigned<cs...>());
}

template<char... cs>
requires CastableTo<decltype(detail::make_unsigned<cs...>()), u64>
consteval auto operator"" _u64()
{
return static_cast<u64>(detail::make_unsigned<cs...>());
}

} // namespace literals

using namespace literals; // NOLINT(*namespace*)
} // namespace based

// NOLINTEND(google-runtime-int)

diff --git a/ include/based/types/strong.hpp b/ include/based/types/strong.hpp

@@ -20,6 +20,8 @@ struct strong_type

using basic_type = V;
using tag_type = Tag;

basic_type value;

constexpr ~strong_type() = default;

constexpr explicit strong_type()

@@ -40,7 +42,11 @@ struct strong_type

constexpr strong_type& operator=(const strong_type&) = default;
constexpr strong_type& operator=(strong_type&&) = default;

basic_type value;
template<class T>
static constexpr Tag basic_cast(T value)
{
return Tag {static_cast<basic_type>(value)};
}
};
// NOLINTEND(*crtp*)

@@ -276,6 +282,23 @@ constexpr auto operator~(LHS lhs)

}

template<class LHS>
concept unariable = requires(LHS lhs) { unary(lhs); };

template<class LHS>
requires unariable<LHS>
constexpr auto operator+(LHS lhs)
{
return decltype(lhs)(+lhs.value);
}

template<class LHS>
requires unariable<LHS>
constexpr auto operator-(LHS lhs)
{
return decltype(lhs)(-lhs.value);
}

template<class LHS>
concept preincable = requires(LHS lhs) { preinc(lhs); };

template<class LHS>

diff --git a/ include/based/types/types.hpp b/ include/based/types/types.hpp

@@ -1,4 +1,5 @@

#pragma once

#include "based/macro/foreach_1.hpp"
#include "based/types/strong.hpp"

@@ -10,41 +11,80 @@ namespace based

using bi8 = signed char;
using bi16 = signed short int;
using bi32 = signed int;
using bi64 = signed long int;
using bi64 = signed long long int;

using bu8 = unsigned char;
using bu16 = unsigned short int;
using bu32 = unsigned int;
using bu64 = unsigned long int;
using bu64 = unsigned long long int;

using size_t = bu64;

#define BASED_DETAIL_TYPE(Name) \
/* NOLINTNEXTLINE(*macro*) */ \
struct Name : strong_type<b##Name, Name> \
{ \
using strong_type::strong_type; \
using strong_type::operator=; \
}; \
\
namespace literals \
{ \
constexpr auto operator""_##Name(unsigned long long val) \
{ \
/* NOLINTNEXTLINE(*macro*) */ \
return Name {static_cast<Name::basic_type>(val)}; \
} \
} // namespace literals

BASED_DETAIL_TYPE(i8)
BASED_DETAIL_TYPE(i16)
BASED_DETAIL_TYPE(i32)
BASED_DETAIL_TYPE(i64)

BASED_DETAIL_TYPE(u8)
BASED_DETAIL_TYPE(u16)
BASED_DETAIL_TYPE(u32)
BASED_DETAIL_TYPE(u64)
struct i64 : strong_type<signed long long int, i64>
{
using strong_type::strong_type;
using strong_type::operator=;
};

struct i32 : strong_type<signed int, i32>
{
using strong_type::strong_type;
using strong_type::operator=;

explicit constexpr operator i64() { return i64::basic_cast(value); }
};

struct i16 : strong_type<signed short int, i16>
{
using strong_type::strong_type;
using strong_type::operator=;

explicit constexpr operator i64() { return i64::basic_cast(value); }
explicit constexpr operator i32() { return i32::basic_cast(value); }
};

struct i8 : strong_type<signed char, i8>
{
using strong_type::strong_type;
using strong_type::operator=;

explicit constexpr operator i64() { return i64::basic_cast(value); }
explicit constexpr operator i32() { return i32::basic_cast(value); }
explicit constexpr operator i16() { return i16::basic_cast(value); }
};

struct u64 : strong_type<unsigned long long int, u64>
{
using strong_type::strong_type;
using strong_type::operator=;
};

struct u32 : strong_type<unsigned int, u32>
{
using strong_type::strong_type;
using strong_type::operator=;

explicit constexpr operator u64() { return u64::basic_cast(value); }
};

struct u16 : strong_type<unsigned short int, u16>
{
using strong_type::strong_type;
using strong_type::operator=;

explicit constexpr operator u64() { return u64::basic_cast(value); }
explicit constexpr operator u32() { return u32::basic_cast(value); }
};

struct u8 : strong_type<unsigned char, u8>
{
using strong_type::strong_type;
using strong_type::operator=;

explicit constexpr operator u64() { return u64::basic_cast(value); }
explicit constexpr operator u32() { return u32::basic_cast(value); }
explicit constexpr operator u16() { return u16::basic_cast(value); }
};

#define BASED_DETAIL_OP_UNARY(Prefix, Name, Index) \
auto Name(Prefix##8)->Prefix##8; \

@@ -73,7 +113,9 @@ BASED_DETAIL_TYPE(u64)

auto Name(Prefix##64, Prefix##32)->Prefix##64; \
auto Name(Prefix##64, Prefix##64)->Prefix##64;

BASED_FOREACH_1(i, BASED_DETAIL_OP_UNARY, preinc, postinc, predec, postdec)
BASED_FOREACH_1(
i, BASED_DETAIL_OP_UNARY, unary, preinc, postinc, predec, postdec
)
BASED_FOREACH_1(
i, BASED_DETAIL_OP_BINARY, compare, order, add, sub, mul, div, mod
)

diff --git a/ test/CMakeLists.txt b/ test/CMakeLists.txt

@@ -29,6 +29,8 @@ endfunction()


add_test(types strong_type_test)
add_test(types type_test)
add_test(types limits)
add_test(types literals)

## ----- Trait -----

diff --git a/ test/source/list_test.cpp b/ test/source/list_test.cpp

@@ -4,13 +4,15 @@


#include <catch2/catch_test_macros.hpp>

#include "based/types/types.hpp"
#include "based/types/literals.hpp"

template class based::list_pool<based::u8, based::u8>;

// NOLINTBEGIN(*complexity*)

TEST_CASE("list_pool", "[list/list_pool]")
{
using namespace based::literals; // NOLINT
using namespace based::literals; // NOLINT(*namespace*)
using list_pool = based::list_pool<based::u8, based::u8>;

auto pool = list_pool();

@@ -65,13 +67,13 @@ TEST_CASE("list_pool", "[list/list_pool]")


TEST_CASE("list_pool iterator", "[list/list_pool]")
{
using namespace based::literals; // NOLINT
using namespace based::literals; // NOLINT(*namespace*)
using list_pool = based::list_pool<based::u8, based::u8>;

auto pool = list_pool();
auto head = pool.node_empty();

static constexpr auto iter_count = 0xFF_u8;
static constexpr auto iter_count = 255_u8;
for (auto i = 0_u8; i < iter_count; i++) {
head = pool.allocate(i, head);
}

@@ -86,7 +88,7 @@ TEST_CASE("list_pool iterator", "[list/list_pool]")

sum += *it;
}

REQUIRE(sum == 0xFF_u32 * 0xFE_u32);
REQUIRE(sum == 255_u32 * 254_u32);
}

SECTION("accumulate")

@@ -103,7 +105,7 @@ TEST_CASE("list_pool iterator", "[list/list_pool]")

}
);

REQUIRE(sum == 0xFF_u32 * 0xFE_u32 / 2_u32);
REQUIRE(sum == 255_u32 * 254_u32 / 2_u32);
}

based::free_list(pool, head);

@@ -111,13 +113,13 @@ TEST_CASE("list_pool iterator", "[list/list_pool]")


TEST_CASE("list_pool const iterator", "[list/list_pool]")
{
using namespace based::literals; // NOLINT
using namespace based::literals; // NOLINT(*namespace*)
using list_pool = based::list_pool<based::u8, based::u8>;

auto pool = list_pool();
auto head = pool.node_empty();

static constexpr auto iter_count = 0xFF_u8;
static constexpr auto iter_count = 255_u8;
for (auto i = 0_u8; i < iter_count; i++) {
head = pool.allocate(i, head);
}

@@ -132,7 +134,7 @@ TEST_CASE("list_pool const iterator", "[list/list_pool]")

sum += *it;
}

REQUIRE(sum == 0xFF_u32 * 0xFE_u32);
REQUIRE(sum == 255_u32 * 254_u32);
}

SECTION("const accumulate")

@@ -153,7 +155,7 @@ TEST_CASE("list_pool const iterator", "[list/list_pool]")

);
};

REQUIRE(sum(pool, head) == 0xFF_u32 * 0xFE_u32 / 2_u32);
REQUIRE(sum(pool, head) == 255_u32 * 254_u32 / 2_u32);
}

based::free_list(pool, head);

@@ -177,26 +179,31 @@ TEST_CASE("list_pool queue", "[list/list_pool/queue]")

REQUIRE(pool.pop_front(queue) == queue);
}

using namespace based::literals; // NOLINT
static constexpr auto iter_count = 0xFF_u8;
for (auto i = 0_u8; i < iter_count; i++) {
if (i % 2_u8 == 0_u8) {
queue = pool.push_front(queue, i);
} else {
queue = pool.push_back(queue, i);
SECTION("operation")
{
using namespace based::literals; // NOLINT(*namespace*)
static constexpr auto iter_count = 255_u8;
for (auto i = 0_u8; i < iter_count; i++) {
if (i % 2_u8 == 0_u8) {
queue = pool.push_front(queue, i);
} else {
queue = pool.push_back(queue, i);
}

if (i % 3_u8 == 0_u8) {
queue = pool.pop_front(queue);
}
}

if (i % 3_u8 == 0_u8) {
queue = pool.pop_front(queue);
auto sum = 0_u64;
for (auto it = iter(pool, queue.first); it != iter(pool); ++it) {
sum += *it;
}
}

auto sum = 0_u64;
for (auto it = iter(pool, queue.first); it != iter(pool); ++it) {
sum += *it;
}

pool.free(queue);
pool.free(queue);

REQUIRE(sum == 21717_u64);
REQUIRE(sum == 21717_u64);
}
}

// NOLINTEND(*complexity*)

diff --git a/ test/source/types/limits.cpp b/ test/source/types/limits.cpp

@@ -0,0 +1,33 @@

#define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE

#include "based/types/limits.hpp"

#include <catch2/catch_test_macros.hpp>

using based::limits;

TEST_CASE("unsigned", "[types/literals]")
{
STATIC_REQUIRE(limits<based::u8>::min.value == 0ULL);
STATIC_REQUIRE(limits<based::u16>::min.value == 0ULL);
STATIC_REQUIRE(limits<based::u32>::min.value == 0ULL);
STATIC_REQUIRE(limits<based::u64>::min.value == 0ULL);

STATIC_REQUIRE(limits<based::u8>::max.value == 255ULL);
STATIC_REQUIRE(limits<based::u16>::max.value == 65535ULL);
STATIC_REQUIRE(limits<based::u32>::max.value == 4294967295ULL);
STATIC_REQUIRE(limits<based::u64>::max.value == 18446744073709551615ULL);
}

TEST_CASE("signed", "[types/literals]")
{
STATIC_REQUIRE(limits<based::i8>::min.value == -128LL);
STATIC_REQUIRE(limits<based::i16>::min.value == -32768LL);
STATIC_REQUIRE(limits<based::i32>::min.value == -2147483648LL);
STATIC_REQUIRE(limits<based::i64>::min.value == -9223372036854775807LL - 1);

STATIC_REQUIRE(limits<based::i8>::max.value == 127LL);
STATIC_REQUIRE(limits<based::i16>::max.value == 32767LL);
STATIC_REQUIRE(limits<based::i32>::max.value == 2147483647LL);
STATIC_REQUIRE(limits<based::i64>::max.value == 9223372036854775807LL);
}

diff --git a/ test/source/types/literals.cpp b/ test/source/types/literals.cpp

@@ -0,0 +1,41 @@

#define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE

#include "based/types/literals.hpp"

#include <catch2/catch_test_macros.hpp>

#include "based/concepts/is/same.hpp"

using namespace based::literals; // NOLINT(*namespace*)

using based::SameAs;

TEST_CASE("unsigned", "[types/literals]")
{
STATIC_REQUIRE(SameAs<decltype(0_u), based::u8>);
STATIC_REQUIRE(SameAs<decltype(255_u), based::u8>);
STATIC_REQUIRE(SameAs<decltype(256_u), based::u16>);
STATIC_REQUIRE(SameAs<decltype(65535_u), based::u16>);
STATIC_REQUIRE(SameAs<decltype(65536_u), based::u32>);
STATIC_REQUIRE(SameAs<decltype(4294967295_u), based::u32>);
STATIC_REQUIRE(SameAs<decltype(4294967296_u), based::u64>);
STATIC_REQUIRE(SameAs<decltype(18446744073709551615_u), based::u64>);
}

TEST_CASE("signed", "[types/literals]")
{
STATIC_REQUIRE(SameAs<decltype(0_i), based::i8>);
STATIC_REQUIRE(SameAs<decltype(127_i), based::i8>);
STATIC_REQUIRE(SameAs<decltype(128_i), based::i16>);
STATIC_REQUIRE(SameAs<decltype(32767_i), based::i16>);
STATIC_REQUIRE(SameAs<decltype(2147483647_i), based::i32>);
STATIC_REQUIRE(SameAs<decltype(2147483648_i), based::i64>);
STATIC_REQUIRE(SameAs<decltype(9223372036854775807_i), based::i64>);

STATIC_REQUIRE(SameAs<decltype(-127_i), based::i8>);
STATIC_REQUIRE(SameAs<decltype(-128_i), based::i16>);
STATIC_REQUIRE(SameAs<decltype(-32767_i), based::i16>);
STATIC_REQUIRE(SameAs<decltype(-2147483647_i), based::i32>);
STATIC_REQUIRE(SameAs<decltype(-2147483648_i), based::i64>);
STATIC_REQUIRE(SameAs<decltype(-9223372036854775807_i), based::i64>);
}

diff --git a/ test/source/types/strong_type_test.cpp b/ test/source/types/strong_type_test.cpp

@@ -4,7 +4,7 @@


#include <catch2/catch_test_macros.hpp>

#include "based/types/types.hpp"
#include "based/types/literals.hpp"

struct t1 : based::strong_type<based::u8, t1>
{

@@ -34,7 +34,7 @@ TEST_CASE("strong_type", "[types/strong_type]")

STATIC_REQUIRE(based::addable<t1, t2>);
STATIC_REQUIRE(based::addable<t2, t1>);

using namespace based::literals; // NOLINT
using namespace based::literals; // NOLINT(*namespace*)
REQUIRE(t1 {10_u8} + t1 {20_u8} == t1 {30_u8});
REQUIRE(t1 {10_u8} + t2 {20_u8} == t1 {30_u8});
REQUIRE(t2 {10_u8} + t1 {20_u8} == t2 {30_u8});

diff --git a/ test/source/types/type_test.cpp b/ test/source/types/type_test.cpp

@@ -1,4 +1,4 @@

// #define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE
#define CATCH_CONFIG_RUNTIME_STATIC_REQUIRE

#include <catch2/catch_test_macros.hpp>

diff --git a/ test/source/utility/buffer_test.cpp b/ test/source/utility/buffer_test.cpp

@@ -4,7 +4,7 @@


#include <catch2/catch_test_macros.hpp>

#include "based/types/types.hpp"
#include "based/types/literals.hpp"

template struct based::buffer<sizeof(void*)>;

@@ -31,7 +31,7 @@ TEST_CASE("valid type", "[template/buffer]")


TEST_CASE("buffer", "[template/buffer]")
{
using namespace based::literals; // NOLINT
using namespace based::literals; // NOLINT(*namespace*)
using buffer = based::buffer<sizeof(based::size_t)>;

static constexpr auto value = 8_u8;

@@ -60,7 +60,7 @@ TEST_CASE("buffer", "[template/buffer]")


TEST_CASE("const buffer", "[template/buffer]")
{
using namespace based::literals; // NOLINT
using namespace based::literals; // NOLINT(*namespace*)
using buffer = based::buffer<sizeof(based::size_t)>;

static constexpr auto value = 8_u8;