From 232c948674cff1b4b72c2d6b3acfa7342d096dd1 Mon Sep 17 00:00:00 2001 From: Hossein <37206671+HosseinSagha@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:22:39 +1000 Subject: [PATCH 01/13] Exclude operator <=> from chrono when STL not available (#1602) * Exclude operator <=> from chrono when STL not available * apply the fix to tests * chore: rerun pipeline --- include/etl/private/chrono/day.h | 2 +- include/etl/private/chrono/duration.h | 2 +- include/etl/private/chrono/month.h | 2 +- include/etl/private/chrono/month_day.h | 4 ++-- include/etl/private/chrono/time_point.h | 2 +- include/etl/private/chrono/year.h | 2 +- include/etl/private/chrono/year_month.h | 2 +- include/etl/private/chrono/year_month_day.h | 4 ++-- test/test_chrono_day.cpp | 2 +- test/test_chrono_duration.cpp | 2 +- test/test_chrono_month.cpp | 2 +- test/test_chrono_month_day.cpp | 2 +- test/test_chrono_time_point.cpp | 2 +- test/test_chrono_year.cpp | 2 +- test/test_chrono_year_month.cpp | 2 +- test/test_chrono_year_month_day.cpp | 2 +- 16 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/etl/private/chrono/day.h b/include/etl/private/chrono/day.h index a929c601..1f903067 100644 --- a/include/etl/private/chrono/day.h +++ b/include/etl/private/chrono/day.h @@ -211,7 +211,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::day& d1, const etl::chrono::day& d2) ETL_NOEXCEPT { diff --git a/include/etl/private/chrono/duration.h b/include/etl/private/chrono/duration.h index bc8d6ba5..cf48e170 100644 --- a/include/etl/private/chrono/duration.h +++ b/include/etl/private/chrono/duration.h @@ -482,7 +482,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL template [[nodiscard]] constexpr auto operator<=>(const etl::chrono::duration& lhs, const etl::chrono::duration& rhs) ETL_NOEXCEPT diff --git a/include/etl/private/chrono/month.h b/include/etl/private/chrono/month.h index 662bb632..40c2e594 100644 --- a/include/etl/private/chrono/month.h +++ b/include/etl/private/chrono/month.h @@ -224,7 +224,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month& d1, const etl::chrono::month& d2) ETL_NOEXCEPT { diff --git a/include/etl/private/chrono/month_day.h b/include/etl/private/chrono/month_day.h index dcefbff6..f4f77628 100644 --- a/include/etl/private/chrono/month_day.h +++ b/include/etl/private/chrono/month_day.h @@ -178,7 +178,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month_day& lhs, const etl::chrono::month_day& rhs) ETL_NOEXCEPT { @@ -298,7 +298,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month_day_last& mdl1, const etl::chrono::month_day_last& mdl2) ETL_NOEXCEPT { diff --git a/include/etl/private/chrono/time_point.h b/include/etl/private/chrono/time_point.h index f90f1c61..a0e79bb1 100644 --- a/include/etl/private/chrono/time_point.h +++ b/include/etl/private/chrono/time_point.h @@ -353,7 +353,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL template [[nodiscard]] constexpr auto operator<=>(const etl::chrono::time_point& lhs, const etl::chrono::time_point& rhs) diff --git a/include/etl/private/chrono/year.h b/include/etl/private/chrono/year.h index 49ad3530..e6cb3416 100644 --- a/include/etl/private/chrono/year.h +++ b/include/etl/private/chrono/year.h @@ -240,7 +240,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year& y1, const etl::chrono::year& y2) ETL_NOEXCEPT { diff --git a/include/etl/private/chrono/year_month.h b/include/etl/private/chrono/year_month.h index 16b3a431..44bb9fd1 100644 --- a/include/etl/private/chrono/year_month.h +++ b/include/etl/private/chrono/year_month.h @@ -234,7 +234,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month& lhs, const etl::chrono::year_month& rhs) ETL_NOEXCEPT { diff --git a/include/etl/private/chrono/year_month_day.h b/include/etl/private/chrono/year_month_day.h index a0f42474..91088c3b 100644 --- a/include/etl/private/chrono/year_month_day.h +++ b/include/etl/private/chrono/year_month_day.h @@ -426,7 +426,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month_day& lhs, const etl::chrono::year_month_day& rhs) ETL_NOEXCEPT { @@ -722,7 +722,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month_day_last& lhs, const etl::chrono::year_month_day_last& rhs) ETL_NOEXCEPT { diff --git a/test/test_chrono_day.cpp b/test/test_chrono_day.cpp index 706a3de9..47ab3fc8 100644 --- a/test/test_chrono_day.cpp +++ b/test/test_chrono_day.cpp @@ -278,7 +278,7 @@ namespace CHECK_TRUE(day10 >= day10); CHECK_TRUE(day20 >= day10); -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL CHECK_TRUE((day10 <=> day10) == 0); CHECK_TRUE((day10 <=> day20) < 0); CHECK_TRUE((day20 <=> day10) > 0); diff --git a/test/test_chrono_duration.cpp b/test/test_chrono_duration.cpp index 81ef8a0b..4122cdd5 100644 --- a/test/test_chrono_duration.cpp +++ b/test/test_chrono_duration.cpp @@ -1045,7 +1045,7 @@ namespace CHECK_EQUAL(1, s1.count()); // 10 seconds % (-3) seconds = 1 second } -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL //************************************************************************* TEST(test_duration_spaceship_operator) { diff --git a/test/test_chrono_month.cpp b/test/test_chrono_month.cpp index 46522d4f..a4e61491 100644 --- a/test/test_chrono_month.cpp +++ b/test/test_chrono_month.cpp @@ -303,7 +303,7 @@ namespace CHECK_TRUE(month1 >= month1); CHECK_TRUE(month2 >= month1); -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL CHECK_TRUE((month1 <=> month1) == 0); CHECK_TRUE((month1 <=> month2) < 0); CHECK_TRUE((month2 <=> month1) > 0); diff --git a/test/test_chrono_month_day.cpp b/test/test_chrono_month_day.cpp index fd6ea597..7e7bf9b5 100644 --- a/test/test_chrono_month_day.cpp +++ b/test/test_chrono_month_day.cpp @@ -89,7 +89,7 @@ namespace CHECK_FALSE(md.ok()); // Invalid month_day } -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL //************************************************************************* TEST(test_month_day_spaceship_operator) { diff --git a/test/test_chrono_time_point.cpp b/test/test_chrono_time_point.cpp index 0bbea37f..a4866f0c 100644 --- a/test/test_chrono_time_point.cpp +++ b/test/test_chrono_time_point.cpp @@ -365,7 +365,7 @@ namespace CHECK_FALSE(tp10d >= tp20h); CHECK_FALSE(tp10h >= tp20d); -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL CHECK_TRUE((tp10h <=> tp10h) == 0); CHECK_TRUE((tp10h <=> tp20h) < 0); CHECK_TRUE((tp20h <=> tp10h) > 0); diff --git a/test/test_chrono_year.cpp b/test/test_chrono_year.cpp index 8e3c9f1c..2b8dad29 100644 --- a/test/test_chrono_year.cpp +++ b/test/test_chrono_year.cpp @@ -284,7 +284,7 @@ namespace CHECK_FALSE(year10 == year20); CHECK_TRUE(year10 != year20); -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL CHECK_TRUE((year10 <=> year10) == 0); CHECK_TRUE((year10 <=> year20) < 0); CHECK_TRUE((year20 <=> year10) > 0); diff --git a/test/test_chrono_year_month.cpp b/test/test_chrono_year_month.cpp index e23bdf6d..73065121 100644 --- a/test/test_chrono_year_month.cpp +++ b/test/test_chrono_year_month.cpp @@ -89,7 +89,7 @@ namespace CHECK_FALSE(ym.ok()); // Invalid year_month } -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL //************************************************************************* TEST(test_year_month_spaceship_operator) { diff --git a/test/test_chrono_year_month_day.cpp b/test/test_chrono_year_month_day.cpp index 8f57f822..e44ee967 100644 --- a/test/test_chrono_year_month_day.cpp +++ b/test/test_chrono_year_month_day.cpp @@ -98,7 +98,7 @@ namespace CHECK_FALSE(ymd.ok()); // Invalid year_month_day } -#if ETL_USING_CPP20 +#if ETL_USING_CPP20 && ETL_USING_STL //************************************************************************* TEST(test_year_month_day_spaceship_operator) { From eb793d2b8231cb554d651b11efd7fbf9a50260f7 Mon Sep 17 00:00:00 2001 From: Marco Nilsson Date: Sun, 13 Sep 2026 11:33:42 +0200 Subject: [PATCH 02/13] Help the compiler optimize unaligned_type. (#1598) * Help the compiler optimize unaligned_type. This fixes a regression in 266b0f99 when the type was made constexpr. When switching to a loop to construct the value the compiler was unable to detect what was going on. We now unroll the loop at compile time into what's essentially a hand rolled shift and OR operation which the compiler detects and replaces with a byte swap. Tested on gcc (x86+arm) and clang (x86). Introduce a static value_from which can be used to convert a value from an existing buffer. There were previously three options for this, all bad or awkward in different ways: - val = etl::be_uint32_t(address) Copies to temporary storage. - val = *reinterpret_cast(address); Unreadable. - etl::be_uint32_t::copy_store_to_value(address, output); Requires an output parameter. The new addition is: - val = etl::be_uint32_t::value_from(address); Additionally, update the documentation with value_from and the missing, existing constructors. * Please clang-format. --------- Co-authored-by: John Wellbelove --- docs/types/unaligned-type.md | 66 +++++++++++++++-- include/etl/unaligned_type.h | 135 +++++++++++++++++++++++------------ test/test_unaligned_type.cpp | 105 +++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 50 deletions(-) diff --git a/docs/types/unaligned-type.md b/docs/types/unaligned-type.md index 0f6689a9..28c9f85f 100644 --- a/docs/types/unaligned-type.md +++ b/docs/types/unaligned-type.md @@ -166,6 +166,50 @@ Constructs with the supplied value. --- +```cpp +unaligned_type(const void* address) +``` +**Description** +Constructs from the `Size` bytes at the address. +From: `20.39.5` + +--- + +```cpp +unaligned_type(const void* address, size_t buffer_size) +``` +**Description** +Constructs from the `Size` bytes at the address. +`buffer_size` must be greater than or equal to `Size`. This is a precondition, +checked by `ETL_ASSERT`. If checks are disabled then a smaller buffer size +results in a read beyond the end of the buffer. +From: `20.39.5` + +--- + +```cpp +ETL_CONSTEXPR14 unaligned_type(const unsigned char* address) +``` +**Description** +Constructs from the `Size` bytes at the address. +Unlike the `const void*` overload, this requires no `reinterpret_cast` and so +may be used in a constexpr context. +From: `20.48.1` + +--- + +```cpp +ETL_CONSTEXPR14 unaligned_type(const unsigned char* address, size_t buffer_size) +``` +**Description** +Constructs from the `Size` bytes at the address. +`buffer_size` must be greater than or equal to `Size`. This is a precondition, +checked by `ETL_ASSERT`. If checks are disabled then a smaller buffer size +results in a read beyond the end of the buffer. +From: `20.48.1` + +--- + ```cpp template unaligned_type(const unaligned_type& other) @@ -176,10 +220,11 @@ The endianness is converted, if necessary. ## Member types ```cpp -using pointer = char*; -using const_pointer = const char*; -using iterator = char*; -using const_iterator = const char*; +using storage_type = unsigned char; +using pointer = unsigned char*; +using const_pointer = const unsigned char*; +using iterator = unsigned char*; +using const_iterator = const unsigned char*; using reverse_iterator = etl::reverse_iterator; using const_reverse_iterator = etl::reverse_iterator; ``` @@ -306,7 +351,7 @@ Const reverse iterator to the end of the storage. --- ```cpp -char& operator[](int i) +storage_type& operator[](int i) ``` **Description** Index operator. @@ -314,7 +359,7 @@ Index operator. --- ```cpp -ETL_CONSTEXPR14 const char& operator[](int i) const +ETL_CONSTEXPR14 const storage_type& operator[](int i) const ``` **Description** Const index operator. @@ -345,6 +390,15 @@ Gets the value. --- +```cpp +static ETL_CONSTEXPR14 T value_from(const_pointer address) +``` +**Description** +Gets the value of the `Size` bytes at the address. +Unlike construction from an address, the bytes are not copied to storage first. + +--- + ```cpp template ETL_CONSTEXPR14 unaligned_type& operator =(const unaligned_type& other) diff --git a/include/etl/unaligned_type.h b/include/etl/unaligned_type.h index cba1874f..6d8ef74c 100644 --- a/include/etl/unaligned_type.h +++ b/include/etl/unaligned_type.h @@ -335,6 +335,48 @@ namespace etl return ((Endian == ETL_ENDIAN_LITTLE) ? index : (Size - 1U - index)) * 8U; } + //************************************************************************* + /// Gathers the bytes of a store into a value, and scatters a value back out. + /// Unrolled at source level, so byte swap recognition sees no loop. + //************************************************************************* + template + struct gather_bytes + { + static ETL_CONSTEXPR14 TValue from(const unsigned char* store) + { + return static_cast(gather_bytes::from(store) + | (static_cast(store[Index]) << byte_shift(Index))); + } + }; + + template + struct gather_bytes + { + static ETL_CONSTEXPR14 TValue from(const unsigned char* store) + { + return static_cast(static_cast(store[0]) << byte_shift(0)); + } + }; + + template + struct scatter_bytes + { + static ETL_CONSTEXPR14 void into(TValue value, unsigned char* store) + { + scatter_bytes::into(value, store); + store[Index] = static_cast(value >> byte_shift(Index)); + } + }; + + template + struct scatter_bytes + { + static ETL_CONSTEXPR14 void into(TValue value, unsigned char* store) + { + store[0] = static_cast(value >> byte_shift(0)); + } + }; + #if ETL_USING_BUILTIN_BIT_CAST //************************************************************************* /// Unsigned integer type of a given byte size, used as a constexpr-capable @@ -402,28 +444,23 @@ namespace etl // latter is not defined for 'bool'. typedef typename etl::unsigned_type::type unsigned_t; - unsigned_t uvalue = static_cast(value); + private_unaligned_type::scatter_bytes::into(static_cast(value), store); + } - for (size_t i = 0UL; i < Size_; ++i) - { - store[i] = static_cast(uvalue >> private_unaligned_type::byte_shift(i)); - } + //******************************* + template + static ETL_CONSTEXPR14 T value_from(const_pointer store) + { + typedef typename etl::unsigned_type::type unsigned_t; + + return static_cast(private_unaligned_type::gather_bytes::from(store)); } //******************************* template static ETL_CONSTEXPR14 void copy_store_to_value(const_pointer store, T & value) { - typedef typename etl::unsigned_type::type unsigned_t; - - unsigned_t uvalue = unsigned_t(0); - - for (size_t i = 0UL; i < Size_; ++i) - { - uvalue = static_cast(uvalue | (static_cast(store[i]) << private_unaligned_type::byte_shift(i))); - } - - value = static_cast(uvalue); + value = value_from(store); } //******************************* @@ -462,12 +499,9 @@ namespace etl { typedef typename private_unaligned_type::uint_of_size::type uint_t; - uint_t uvalue = etl::bit_cast(value); + const uint_t uvalue = etl::bit_cast(value); - for (size_t i = 0UL; i < Size_; ++i) - { - store[i] = static_cast(uvalue >> private_unaligned_type::byte_shift(i)); - } + private_unaligned_type::scatter_bytes::into(uvalue, store); } #endif @@ -510,12 +544,7 @@ namespace etl { typedef typename private_unaligned_type::uint_of_size::type uint_t; - uint_t uvalue = uint_t(0); - - for (size_t i = 0UL; i < Size_; ++i) - { - uvalue = static_cast(uvalue | (static_cast(store[i]) << private_unaligned_type::byte_shift(i))); - } + const uint_t uvalue = private_unaligned_type::gather_bytes::from(store); value = etl::bit_cast(uvalue); } @@ -553,6 +582,22 @@ namespace etl do_copy_store_to_value(store, value, use_bit_cast_t()); } + //******************************* + template + static +#if ETL_USING_BUILTIN_BIT_CAST + ETL_CONSTEXPR14 +#endif + T + value_from(const_pointer store) + { + T value = T(); + + copy_store_to_value(store, value); + + return value; + } + //******************************* // This is pure byte manipulation (copy + optional reversal), with no // floating point arithmetic involved, so it is always constexpr-capable @@ -714,11 +759,7 @@ namespace etl //************************************************************************* ETL_CONSTEXPR14 operator T() const { - T value = T(); - - unaligned_copy::copy_store_to_value(this->storage, value); - - return value; + return value_from(this->storage); } //************************************************************************* @@ -726,11 +767,16 @@ namespace etl //************************************************************************* ETL_CONSTEXPR14 T value() const { - T value = T(); + return value_from(this->storage); + } - unaligned_copy::copy_store_to_value(this->storage, value); - - return value; + //************************************************************************* + /// Get the value directly from a byte buffer. + /// Unlike construction from an address, the bytes are not copied to storage. + //************************************************************************* + static ETL_CONSTEXPR14 T value_from(const_pointer address) + { + return unaligned_copy::template value_from(address); } }; ETL_END_PACKED @@ -898,11 +944,7 @@ namespace etl //************************************************************************* operator T() const { - T value = T(); - - unaligned_copy::copy_store_to_value(this->storage, value); - - return value; + return value_from(this->storage); } //************************************************************************* @@ -910,11 +952,16 @@ namespace etl //************************************************************************* T value() const { - T value = T(); + return value_from(this->storage); + } - unaligned_copy::copy_store_to_value(this->storage, value); - - return value; + //************************************************************************* + /// Get the value directly from a byte buffer. + /// Unlike construction from an address, the bytes are not copied to storage. + //************************************************************************* + static T value_from(const_pointer address) + { + return unaligned_copy::template value_from(address); } //************************************************************************* diff --git a/test/test_unaligned_type.cpp b/test/test_unaligned_type.cpp index ad1e50c9..578d0cfb 100644 --- a/test/test_unaligned_type.cpp +++ b/test/test_unaligned_type.cpp @@ -282,6 +282,92 @@ namespace CHECK_THROW(etl::be_uint8_t be_byte_empty(buffer.data(), 0U), etl::unaligned_type_buffer_size); } + //************************************************************************* + // 'value_from' reads a value straight out of a byte buffer, without first + // copying the bytes into an unaligned_type's own storage. + //************************************************************************* + TEST(test_value_from_integral) + { + const std::array buffer = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + CHECK_EQUAL(uint16_t(0x1234), etl::be_uint16_t::value_from(buffer.data())); + CHECK_EQUAL(uint16_t(0x3412), etl::le_uint16_t::value_from(buffer.data())); + + CHECK_EQUAL(uint32_t(0x12345678), etl::be_uint32_t::value_from(buffer.data())); + CHECK_EQUAL(uint32_t(0x78563412), etl::le_uint32_t::value_from(buffer.data())); + + CHECK_EQUAL(uint64_t(0x123456789ABCDEF0), etl::be_uint64_t::value_from(buffer.data())); + CHECK_EQUAL(uint64_t(0xF0DEBC9A78563412), etl::le_uint64_t::value_from(buffer.data())); + } + + //************************************************************************* + /// The address need not be aligned for the value's type. + //************************************************************************* + TEST(test_value_from_unaligned_address) + { + const std::array buffer = {0xFF, 0x12, 0x34, 0x56, 0x78, 0xFF}; + + CHECK_EQUAL(uint32_t(0x12345678), etl::be_uint32_t::value_from(buffer.data() + 1)); + CHECK_EQUAL(uint32_t(0x78563412), etl::le_uint32_t::value_from(buffer.data() + 1)); + } + + //************************************************************************* + /// 'value_from' must agree with constructing from the same address and + /// then reading the value back out. + //************************************************************************* + TEST(test_value_from_matches_construction) + { + const std::array buffer = {0xEE, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + + CHECK_EQUAL(etl::be_uint16_t(buffer.data()).value(), etl::be_uint16_t::value_from(buffer.data())); + CHECK_EQUAL(etl::le_uint16_t(buffer.data()).value(), etl::le_uint16_t::value_from(buffer.data())); + CHECK_EQUAL(etl::be_uint32_t(buffer.data()).value(), etl::be_uint32_t::value_from(buffer.data())); + CHECK_EQUAL(etl::le_uint32_t(buffer.data()).value(), etl::le_uint32_t::value_from(buffer.data())); + CHECK_EQUAL(etl::be_uint64_t(buffer.data()).value(), etl::be_uint64_t::value_from(buffer.data())); + CHECK_EQUAL(etl::le_uint64_t(buffer.data()).value(), etl::le_uint64_t::value_from(buffer.data())); + } + + //************************************************************************* + /// Boundary values must survive the round trip through the store. + //************************************************************************* + TEST(test_value_from_boundary_values) + { + const std::array zeroes = {0x00, 0x00, 0x00, 0x00}; + const std::array ones = {0xFF, 0xFF, 0xFF, 0xFF}; + + CHECK_EQUAL(uint32_t(0), etl::be_uint32_t::value_from(zeroes.data())); + CHECK_EQUAL(uint32_t(0), etl::le_uint32_t::value_from(zeroes.data())); + + CHECK_EQUAL(etl::integral_limits::max, etl::be_uint32_t::value_from(ones.data())); + CHECK_EQUAL(etl::integral_limits::max, etl::le_uint32_t::value_from(ones.data())); + } + + //************************************************************************* + /// Signed types are reconstructed through their unsigned representation. + //************************************************************************* + TEST(test_value_from_negative_values) + { + const std::array buffer = {0xFF, 0xFF, 0xFF, 0xFE}; + + CHECK_EQUAL(int32_t(-2), etl::be_int32_t::value_from(buffer.data())); + CHECK_EQUAL(int16_t(-1), etl::be_int16_t::value_from(buffer.data())); + } + + //************************************************************************* + /// The floating point specialisation provides 'value_from' too. + //************************************************************************* + TEST(test_value_from_float) + { + const float f = 3.1415927f; + const double d = 2.718281828459045; + + etl::be_float_t be_f(f); + etl::le_double_t le_d(d); + + CHECK_EQUAL(f, etl::be_float_t::value_from(be_f.data())); + CHECK_EQUAL(d, etl::le_double_t::value_from(le_d.data())); + } + //************************************************************************* // The following tests demonstrate the 'decode' direction: given a raw byte // buffer (e.g. as received from a file, network socket or memory-mapped @@ -625,6 +711,25 @@ namespace CHECK_EQUAL(0x12345678U, be_v.value()); } + //************************************************************************* + /// 'value_from' is usable at compile time for the same reason: it reads + /// the buffer through the 'const unsigned char*' path. + //************************************************************************* + TEST(test_constexpr_value_from_integral) + { + static ETL_CONSTANT unsigned char le_buffer[4] = {0x78, 0x56, 0x34, 0x12}; + static ETL_CONSTANT unsigned char be_buffer[4] = {0x12, 0x34, 0x56, 0x78}; + + constexpr uint32_t le_v = etl::le_uint32_t::value_from(le_buffer); + constexpr uint32_t be_v = etl::be_uint32_t::value_from(be_buffer); + + static_assert(le_v == 0x12345678U, "le_uint32_t constexpr value_from"); + static_assert(be_v == 0x12345678U, "be_uint32_t constexpr value_from"); + + CHECK_EQUAL(0x12345678U, le_v); + CHECK_EQUAL(0x12345678U, be_v); + } + #if ETL_USING_BUILTIN_BIT_CAST //************************************************************************* TEST(test_constexpr_decode_buffer_float) From 2e2c5d01999f2a9807f756fdf84b4a39fc492cc9 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Thu, 17 Sep 2026 12:38:18 +0200 Subject: [PATCH 03/13] Implement spaceship operator <=> for No-STL case (#1604) This was disabled explicitly in recent commit 232c948674cff1b4b72c2d6b3acfa7342d096dd1 to prevent using the header from STL. Now, re-enabling by implementing an ETL version of it in compare.h --- docs/utilities/compare.md | 103 ++++++- include/etl/chrono.h | 1 + include/etl/compare.h | 295 +++++++++++++++++++- include/etl/private/chrono/day.h | 4 +- include/etl/private/chrono/duration.h | 32 ++- include/etl/private/chrono/month.h | 4 +- include/etl/private/chrono/month_day.h | 6 +- include/etl/private/chrono/time_point.h | 2 +- include/etl/private/chrono/year.h | 4 +- include/etl/private/chrono/year_month.h | 2 +- include/etl/private/chrono/year_month_day.h | 4 +- test/test_chrono_day.cpp | 2 +- test/test_chrono_duration.cpp | 33 ++- test/test_chrono_month.cpp | 2 +- test/test_chrono_month_day.cpp | 8 +- test/test_chrono_month_day_last.cpp | 14 + test/test_chrono_time_point.cpp | 4 +- test/test_chrono_year.cpp | 2 +- test/test_chrono_year_month.cpp | 12 +- test/test_chrono_year_month_day.cpp | 16 +- test/test_chrono_year_month_day_last.cpp | 17 ++ test/test_compare.cpp | 148 ++++++++++ 22 files changed, 667 insertions(+), 48 deletions(-) diff --git a/docs/utilities/compare.md b/docs/utilities/compare.md index f6062c73..54d904e5 100644 --- a/docs/utilities/compare.md +++ b/docs/utilities/compare.md @@ -7,23 +7,32 @@ title: "compare" Since: `TBC` {{< /callout >}} -A helper class that defines `<=`, `>`, `>=` in terms of `etl::less` or a user supplied compare type. +A helper for deriving comparison operations from a strict weak ordering. +`etl::compare` exposes boolean predicates and a three-state comparison result. +`etl::three_way_compare` returns `-1`, `0`, or `1` using the same comparison policy. ```cpp template > struct compare ``` +```cpp +template > +ETL_CONSTEXPR14 int three_way_compare(const T& lhs, const T& rhs) +``` + ## Types ```cpp first_argument_type; second_argument_type; -result_type; +cmp_result; ``` +`cmp_result` has the values `Less`, `Equal`, and `Greater`. + ## Members ```cpp -static result_type lt(first_argument_type lhs, second_argument_type rhs) +static bool lt(first_argument_type lhs, second_argument_type rhs) ``` **Return** `true` if `lhs < rhs`, otherwise `false`. @@ -31,7 +40,7 @@ static result_type lt(first_argument_type lhs, second_argument_type rhs) --- ```cpp -static result_type lte(first_argument_type lhs, second_argument_type rhs) +static bool lte(first_argument_type lhs, second_argument_type rhs) ``` **Return** `true` if `lhs <= rhs`, otherwise `false`. @@ -39,7 +48,7 @@ static result_type lte(first_argument_type lhs, second_argument_type rhs) --- ```cpp -static result_type gt(first_argument_type lhs, second_argument_type rhs) +static bool gt(first_argument_type lhs, second_argument_type rhs) ``` **Return** `true` if `lhs > rhs`, otherwise `false`. @@ -47,14 +56,74 @@ static result_type gt(first_argument_type lhs, second_argument_type rhs) --- ```cpp -static result_type gte(first_argument_type lhs, second_argument_type rhs) +static bool gte(first_argument_type lhs, second_argument_type rhs) ``` **Return** `true` if `lhs >= rhs`, otherwise `false`. +--- + +```cpp +static bool eq(first_argument_type lhs, second_argument_type rhs) +``` +**Return** +`true` if `lhs == rhs`, otherwise `false`. + +--- + +```cpp +static bool ne(first_argument_type lhs, second_argument_type rhs) +``` +**Return** +`true` if `lhs != rhs`, otherwise `false`. + +--- + +```cpp +static cmp_result cmp(first_argument_type lhs, second_argument_type rhs) +``` +**Return** +`Less`, `Equal`, or `Greater`. + +## Three-way comparison + +`etl::three_way_compare(lhs, rhs)` returns the same ordering as `compare::cmp(lhs, rhs)`, +but as an `int` with the values `-1`, `0`, or `1`. + +## C++20 comparison categories + +When compiling as C++20, `compare.h` makes the comparison categories available in the `etl` +namespace, so ETL and user code can name them regardless of the STL configuration. + +```cpp +class strong_ordering; +class weak_ordering; +class partial_ordering; +``` + +* When the STL is available these are aliases for `std::strong_ordering`, `std::weak_ordering` + and `std::partial_ordering`. +* When the STL is not available, `compare.h` provides its own minimal implementations that + follow the standard semantics: the named values `less`, `equivalent`, `greater` + (plus `equal` for `strong_ordering` and `unordered` for `partial_ordering`), comparison + against the literal `0` only, and the implicit conversions + `strong_ordering` → `weak_ordering` → `partial_ordering`. + +An `unordered` result compares `false` against `0` for all of `<`, `<=`, `==`, `>=` and `>`. + +```cpp +ETL_CONSTEXPR strong_ordering make_strong_ordering(int value) ETL_NOEXCEPT; +ETL_CONSTEXPR weak_ordering make_weak_ordering(int value) ETL_NOEXCEPT; +ETL_CONSTEXPR partial_ordering make_partial_ordering(int value) ETL_NOEXCEPT; +``` + +These helpers convert a three-way comparison result, such as the one returned by +`etl::three_way_compare`, into a comparison category. Negative values map to `less`, positive +values to `greater`, and zero to `equal`/`equivalent`. `unordered` cannot be expressed as an +integer result, so use `etl::partial_ordering::unordered` directly for that case. + ## Example 1 ```cpp -using std::less struct Test { int a; @@ -84,6 +153,11 @@ bool operator >=(const Test& lhs, const Test& rhs) { return Compare::gte(lhs, rhs); } + +int operator <=>(const Test& lhs, const Test& rhs) +{ + return etl::three_way_compare(lhs, rhs); +} ``` ## Example 2 @@ -124,6 +198,11 @@ bool operator >=(const Test& lhs, const Test& rhs) { return Compare::gte(lhs, rhs); } + +int operator <=>(const Test& lhs, const Test& rhs) +{ + return etl::three_way_compare(lhs, rhs); +} ``` ## Example 3 @@ -158,3 +237,13 @@ bool operator >=(const Test& lhs, const Test& rhs) return Test::gte(lhs, rhs); } ``` + +## Example 4 +C++20, with or without the STL. + +```cpp +etl::strong_ordering operator <=>(const Test& lhs, const Test& rhs) +{ + return etl::make_strong_ordering(etl::three_way_compare(lhs, rhs)); +} +``` diff --git a/include/etl/chrono.h b/include/etl/chrono.h index b452ca4e..ab360108 100644 --- a/include/etl/chrono.h +++ b/include/etl/chrono.h @@ -37,6 +37,7 @@ SOFTWARE. #if ETL_USING_CPP11 + #include "compare.h" #include "hash.h" #include "integral_limits.h" #include "type_traits.h" diff --git a/include/etl/compare.h b/include/etl/compare.h index 918465bd..f909291c 100644 --- a/include/etl/compare.h +++ b/include/etl/compare.h @@ -35,6 +35,10 @@ SOFTWARE. #include "functional.h" #include "parameter_type.h" +#if ETL_USING_CPP20 && ETL_USING_STL + #include +#endif + //***************************************************************************** ///\defgroup compare compare /// Comparisons only using less than operator @@ -42,6 +46,295 @@ SOFTWARE. //***************************************************************************** namespace etl { +#if ETL_USING_CPP20 + #if ETL_USING_STL + //*************************************************************************** + /// The STL supplies the C++20 comparison categories, so simply expose them + /// through the etl namespace. This allows ETL code and user code to refer to + /// etl::strong_ordering (etc.) regardless of the STL configuration. + //*************************************************************************** + using std::partial_ordering; + using std::strong_ordering; + using std::weak_ordering; + #else + namespace private_compare + { + //************************************************************************* + /// A type that can only be constructed from the literal 0. + /// Used to constrain the comparison-category operators to comparisons + /// against the literal 0, as required by the standard. + //************************************************************************* + struct only_literal_zero + { + ETL_CONSTEXPR only_literal_zero(only_literal_zero*) ETL_NOEXCEPT {} + }; + + typedef signed char ordering_value_type; + + inline constexpr ordering_value_type ordering_less = -1; + inline constexpr ordering_value_type ordering_equivalent = 0; + inline constexpr ordering_value_type ordering_greater = 1; + inline constexpr ordering_value_type ordering_unordered = 2; + } // namespace private_compare + + //*************************************************************************** + /// Minimal partial-ordering category for C++20 three-way comparisons when the + /// STL is unavailable. + /// Unlike the other categories this one has an 'unordered' state, for which + /// every comparison against 0 is false. + //*************************************************************************** + class partial_ordering + { + public: + + static const partial_ordering less; + static const partial_ordering equivalent; + static const partial_ordering greater; + static const partial_ordering unordered; + + friend ETL_CONSTEXPR bool operator==(partial_ordering lhs, partial_ordering rhs) ETL_NOEXCEPT = default; + + friend ETL_CONSTEXPR bool operator==(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_less; + } + friend ETL_CONSTEXPR bool operator>(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_greater; + } + friend ETL_CONSTEXPR bool operator<=(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return (v.value == private_compare::ordering_less) || (v.value == private_compare::ordering_equivalent); + } + friend ETL_CONSTEXPR bool operator>=(partial_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return (v.value == private_compare::ordering_greater) || (v.value == private_compare::ordering_equivalent); + } + + friend ETL_CONSTEXPR bool operator<(private_compare::only_literal_zero, partial_ordering v) ETL_NOEXCEPT + { + return v > 0; + } + friend ETL_CONSTEXPR bool operator>(private_compare::only_literal_zero, partial_ordering v) ETL_NOEXCEPT + { + return v < 0; + } + friend ETL_CONSTEXPR bool operator<=(private_compare::only_literal_zero, partial_ordering v) ETL_NOEXCEPT + { + return v >= 0; + } + friend ETL_CONSTEXPR bool operator>=(private_compare::only_literal_zero, partial_ordering v) ETL_NOEXCEPT + { + return v <= 0; + } + + private: + + explicit ETL_CONSTEXPR partial_ordering(private_compare::ordering_value_type value_) ETL_NOEXCEPT + : value(value_) + { + } + + friend class weak_ordering; + friend class strong_ordering; + + private_compare::ordering_value_type value; + }; + + inline constexpr partial_ordering partial_ordering::less(private_compare::ordering_less); + inline constexpr partial_ordering partial_ordering::equivalent(private_compare::ordering_equivalent); + inline constexpr partial_ordering partial_ordering::greater(private_compare::ordering_greater); + inline constexpr partial_ordering partial_ordering::unordered(private_compare::ordering_unordered); + + //*************************************************************************** + /// Minimal weak-ordering category for C++20 three-way comparisons when the + /// STL is unavailable. + //*************************************************************************** + class weak_ordering + { + public: + + static const weak_ordering less; + static const weak_ordering equivalent; + static const weak_ordering greater; + + ETL_CONSTEXPR operator partial_ordering() const ETL_NOEXCEPT + { + return partial_ordering(value); + } + + friend ETL_CONSTEXPR bool operator==(weak_ordering lhs, weak_ordering rhs) ETL_NOEXCEPT = default; + + friend ETL_CONSTEXPR bool operator==(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value < private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator>(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value > private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<=(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value <= private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator>=(weak_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value >= private_compare::ordering_equivalent; + } + + friend ETL_CONSTEXPR bool operator<(private_compare::only_literal_zero, weak_ordering v) ETL_NOEXCEPT + { + return v > 0; + } + friend ETL_CONSTEXPR bool operator>(private_compare::only_literal_zero, weak_ordering v) ETL_NOEXCEPT + { + return v < 0; + } + friend ETL_CONSTEXPR bool operator<=(private_compare::only_literal_zero, weak_ordering v) ETL_NOEXCEPT + { + return v >= 0; + } + friend ETL_CONSTEXPR bool operator>=(private_compare::only_literal_zero, weak_ordering v) ETL_NOEXCEPT + { + return v <= 0; + } + + private: + + explicit ETL_CONSTEXPR weak_ordering(private_compare::ordering_value_type value_) ETL_NOEXCEPT + : value(value_) + { + } + + friend class strong_ordering; + + private_compare::ordering_value_type value; + }; + + inline constexpr weak_ordering weak_ordering::less(private_compare::ordering_less); + inline constexpr weak_ordering weak_ordering::equivalent(private_compare::ordering_equivalent); + inline constexpr weak_ordering weak_ordering::greater(private_compare::ordering_greater); + + //*************************************************************************** + /// Minimal strong-ordering category for C++20 three-way comparisons when the + /// STL is unavailable. + //*************************************************************************** + class strong_ordering + { + public: + + static const strong_ordering less; + static const strong_ordering equal; + static const strong_ordering equivalent; + static const strong_ordering greater; + + ETL_CONSTEXPR operator partial_ordering() const ETL_NOEXCEPT + { + return partial_ordering(value); + } + + ETL_CONSTEXPR operator weak_ordering() const ETL_NOEXCEPT + { + return weak_ordering(value); + } + + friend ETL_CONSTEXPR bool operator==(strong_ordering lhs, strong_ordering rhs) ETL_NOEXCEPT = default; + + friend ETL_CONSTEXPR bool operator==(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value == private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value < private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator>(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value > private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator<=(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value <= private_compare::ordering_equivalent; + } + friend ETL_CONSTEXPR bool operator>=(strong_ordering v, private_compare::only_literal_zero) ETL_NOEXCEPT + { + return v.value >= private_compare::ordering_equivalent; + } + + friend ETL_CONSTEXPR bool operator<(private_compare::only_literal_zero, strong_ordering v) ETL_NOEXCEPT + { + return v > 0; + } + friend ETL_CONSTEXPR bool operator>(private_compare::only_literal_zero, strong_ordering v) ETL_NOEXCEPT + { + return v < 0; + } + friend ETL_CONSTEXPR bool operator<=(private_compare::only_literal_zero, strong_ordering v) ETL_NOEXCEPT + { + return v >= 0; + } + friend ETL_CONSTEXPR bool operator>=(private_compare::only_literal_zero, strong_ordering v) ETL_NOEXCEPT + { + return v <= 0; + } + + private: + + explicit ETL_CONSTEXPR strong_ordering(private_compare::ordering_value_type value_) ETL_NOEXCEPT + : value(value_) + { + } + + private_compare::ordering_value_type value; + }; + + inline constexpr strong_ordering strong_ordering::less(private_compare::ordering_less); + inline constexpr strong_ordering strong_ordering::equal(private_compare::ordering_equivalent); + inline constexpr strong_ordering strong_ordering::equivalent(private_compare::ordering_equivalent); + inline constexpr strong_ordering strong_ordering::greater(private_compare::ordering_greater); + #endif + + //*************************************************************************** + /// Converts a three-way comparison result to an etl::strong_ordering. + /// Negative values map to 'less', positive values to 'greater' and zero to + /// 'equal'. + //*************************************************************************** + ETL_NODISCARD ETL_CONSTEXPR inline etl::strong_ordering make_strong_ordering(int value) ETL_NOEXCEPT + { + return (value < 0) ? etl::strong_ordering::less : (value > 0) ? etl::strong_ordering::greater : etl::strong_ordering::equal; + } + + //*************************************************************************** + /// Converts a three-way comparison result to an etl::weak_ordering. + /// Negative values map to 'less', positive values to 'greater' and zero to + /// 'equivalent'. + //*************************************************************************** + ETL_NODISCARD ETL_CONSTEXPR inline etl::weak_ordering make_weak_ordering(int value) ETL_NOEXCEPT + { + return (value < 0) ? etl::weak_ordering::less : (value > 0) ? etl::weak_ordering::greater : etl::weak_ordering::equivalent; + } + + //*************************************************************************** + /// Converts a three-way comparison result to an etl::partial_ordering. + /// Negative values map to 'less', positive values to 'greater' and zero to + /// 'equivalent'. + /// Note that 'unordered' cannot be expressed by an integer comparison result, + /// so use etl::partial_ordering::unordered directly for that case. + //*************************************************************************** + ETL_NODISCARD ETL_CONSTEXPR inline etl::partial_ordering make_partial_ordering(int value) ETL_NOEXCEPT + { + return (value < 0) ? etl::partial_ordering::less : (value > 0) ? etl::partial_ordering::greater : etl::partial_ordering::equivalent; + } +#endif + //*************************************************************************** /// Defines <=, >, >=, ==, !=, <=> in terms of < /// Default implementation of TLess is etl::less @@ -105,7 +398,7 @@ namespace etl #endif ETL_CONSTEXPR14 int three_way_compare(const T& lhs, const T& rhs) { - return etl::compare::cmp(lhs, rhs); + return etl::compare::cmp(lhs, rhs); } } // namespace etl diff --git a/include/etl/private/chrono/day.h b/include/etl/private/chrono/day.h index 1f903067..bbca6a59 100644 --- a/include/etl/private/chrono/day.h +++ b/include/etl/private/chrono/day.h @@ -211,11 +211,11 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::day& d1, const etl::chrono::day& d2) ETL_NOEXCEPT { - return (static_cast(d1) <=> static_cast(d2)); + return etl::make_strong_ordering(d1.compare(d2)); } #endif diff --git a/include/etl/private/chrono/duration.h b/include/etl/private/chrono/duration.h index cf48e170..fc0b6cb3 100644 --- a/include/etl/private/chrono/duration.h +++ b/include/etl/private/chrono/duration.h @@ -482,7 +482,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 template [[nodiscard]] constexpr auto operator<=>(const etl::chrono::duration& lhs, const etl::chrono::duration& rhs) ETL_NOEXCEPT @@ -492,7 +492,37 @@ namespace etl common_t l = etl::chrono::duration_cast(lhs); common_t r = etl::chrono::duration_cast(rhs); + #if ETL_USING_STL return (l.count() <=> r.count()); + #else + // The STL returns the ordering category of the representation type. + // Mirror that: a floating point representation is only partially ordered. + if constexpr (etl::is_floating_point::value) + { + if (l.count() < r.count()) + { + return etl::partial_ordering::less; + } + else if (r.count() < l.count()) + { + return etl::partial_ordering::greater; + } + else if ((l.count() <= r.count()) && (r.count() <= l.count())) + { + // Equivalent. Expressed without '==' so that -Wfloat-equal is not triggered. + return etl::partial_ordering::equivalent; + } + else + { + // At least one of the values is NaN. + return etl::partial_ordering::unordered; + } + } + else + { + return etl::make_strong_ordering((l.count() < r.count()) ? -1 : (r.count() < l.count()) ? 1 : 0); + } + #endif } #endif diff --git a/include/etl/private/chrono/month.h b/include/etl/private/chrono/month.h index 40c2e594..b166f509 100644 --- a/include/etl/private/chrono/month.h +++ b/include/etl/private/chrono/month.h @@ -224,11 +224,11 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month& d1, const etl::chrono::month& d2) ETL_NOEXCEPT { - return (static_cast(d1) <=> static_cast(d2)); + return etl::make_strong_ordering(d1.compare(d2)); } #endif diff --git a/include/etl/private/chrono/month_day.h b/include/etl/private/chrono/month_day.h index f4f77628..e53f8ac8 100644 --- a/include/etl/private/chrono/month_day.h +++ b/include/etl/private/chrono/month_day.h @@ -178,7 +178,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month_day& lhs, const etl::chrono::month_day& rhs) ETL_NOEXCEPT { @@ -298,11 +298,11 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::month_day_last& mdl1, const etl::chrono::month_day_last& mdl2) ETL_NOEXCEPT { - return (static_cast(mdl1.month()) <=> static_cast(mdl2.month())); + return mdl1.month() <=> mdl2.month(); } #endif } // namespace chrono diff --git a/include/etl/private/chrono/time_point.h b/include/etl/private/chrono/time_point.h index a0e79bb1..f90f1c61 100644 --- a/include/etl/private/chrono/time_point.h +++ b/include/etl/private/chrono/time_point.h @@ -353,7 +353,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 template [[nodiscard]] constexpr auto operator<=>(const etl::chrono::time_point& lhs, const etl::chrono::time_point& rhs) diff --git a/include/etl/private/chrono/year.h b/include/etl/private/chrono/year.h index e6cb3416..35f67853 100644 --- a/include/etl/private/chrono/year.h +++ b/include/etl/private/chrono/year.h @@ -240,11 +240,11 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year& y1, const etl::chrono::year& y2) ETL_NOEXCEPT { - return (static_cast(y1) <=> static_cast(y2)); + return etl::make_strong_ordering(y1.compare(y2)); } #endif diff --git a/include/etl/private/chrono/year_month.h b/include/etl/private/chrono/year_month.h index 44bb9fd1..16b3a431 100644 --- a/include/etl/private/chrono/year_month.h +++ b/include/etl/private/chrono/year_month.h @@ -234,7 +234,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month& lhs, const etl::chrono::year_month& rhs) ETL_NOEXCEPT { diff --git a/include/etl/private/chrono/year_month_day.h b/include/etl/private/chrono/year_month_day.h index 91088c3b..a0f42474 100644 --- a/include/etl/private/chrono/year_month_day.h +++ b/include/etl/private/chrono/year_month_day.h @@ -426,7 +426,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month_day& lhs, const etl::chrono::year_month_day& rhs) ETL_NOEXCEPT { @@ -722,7 +722,7 @@ namespace etl //*********************************************************************** /// Spaceship operator //*********************************************************************** -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 [[nodiscard]] inline constexpr auto operator<=>(const etl::chrono::year_month_day_last& lhs, const etl::chrono::year_month_day_last& rhs) ETL_NOEXCEPT { diff --git a/test/test_chrono_day.cpp b/test/test_chrono_day.cpp index 47ab3fc8..706a3de9 100644 --- a/test/test_chrono_day.cpp +++ b/test/test_chrono_day.cpp @@ -278,7 +278,7 @@ namespace CHECK_TRUE(day10 >= day10); CHECK_TRUE(day20 >= day10); -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 CHECK_TRUE((day10 <=> day10) == 0); CHECK_TRUE((day10 <=> day20) < 0); CHECK_TRUE((day20 <=> day10) > 0); diff --git a/test/test_chrono_duration.cpp b/test/test_chrono_duration.cpp index 4122cdd5..1372d6f6 100644 --- a/test/test_chrono_duration.cpp +++ b/test/test_chrono_duration.cpp @@ -1045,16 +1045,41 @@ namespace CHECK_EQUAL(1, s1.count()); // 10 seconds % (-3) seconds = 1 second } -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 //************************************************************************* TEST(test_duration_spaceship_operator) { Chrono::seconds s1(2); // 2 seconds Chrono::milliseconds ms1(1500); // 1500 milliseconds - CHECK_TRUE(s1 <=> s1 == std::strong_ordering::equal); - CHECK_TRUE(ms1 <=> s1 == std::strong_ordering::less); - CHECK_TRUE(s1 <=> ms1 == std::strong_ordering::greater); + CHECK_TRUE(s1 <=> s1 == etl::strong_ordering::equal); + CHECK_TRUE(ms1 <=> s1 == etl::strong_ordering::less); + CHECK_TRUE(s1 <=> ms1 == etl::strong_ordering::greater); + + static_assert(etl::is_same ms1), etl::strong_ordering>::value, "Must return etl::strong_ordering"); + } + + //************************************************************************* + TEST(test_duration_spaceship_operator_floating_point_representation) + { + typedef Chrono::duration fp_seconds; + + fp_seconds fs1(1.5); + fp_seconds fs2(2.5); + + // A floating point representation is only partially ordered. + static_assert(etl::is_same fs2), etl::partial_ordering>::value, "Must return etl::partial_ordering"); + + CHECK_TRUE((fs1 <=> fs1) == etl::partial_ordering::equivalent); + CHECK_TRUE((fs1 <=> fs2) == etl::partial_ordering::less); + CHECK_TRUE((fs2 <=> fs1) == etl::partial_ordering::greater); + + fp_seconds nan_seconds(etl::numeric_limits::quiet_NaN()); + + CHECK_TRUE((nan_seconds <=> fs1) == etl::partial_ordering::unordered); + CHECK_FALSE((nan_seconds <=> fs1) < 0); + CHECK_FALSE((nan_seconds <=> fs1) == 0); + CHECK_FALSE((nan_seconds <=> fs1) > 0); } #endif diff --git a/test/test_chrono_month.cpp b/test/test_chrono_month.cpp index a4e61491..46522d4f 100644 --- a/test/test_chrono_month.cpp +++ b/test/test_chrono_month.cpp @@ -303,7 +303,7 @@ namespace CHECK_TRUE(month1 >= month1); CHECK_TRUE(month2 >= month1); -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 CHECK_TRUE((month1 <=> month1) == 0); CHECK_TRUE((month1 <=> month2) < 0); CHECK_TRUE((month2 <=> month1) > 0); diff --git a/test/test_chrono_month_day.cpp b/test/test_chrono_month_day.cpp index 7e7bf9b5..91a61482 100644 --- a/test/test_chrono_month_day.cpp +++ b/test/test_chrono_month_day.cpp @@ -89,7 +89,7 @@ namespace CHECK_FALSE(md.ok()); // Invalid month_day } -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 //************************************************************************* TEST(test_month_day_spaceship_operator) { @@ -97,9 +97,9 @@ namespace Chrono::month_day md2{Chrono::February, Chrono::day{10}}; Chrono::month_day md3{Chrono::January, Chrono::day{15}}; - CHECK_TRUE((md1 <=> md3) == std::strong_ordering::equal); // Same month and day - CHECK_TRUE((md1 <=> md2) == std::strong_ordering::less); // Different month and day - CHECK_TRUE((md2 <=> md1) == std::strong_ordering::greater); // Same month and day + CHECK_TRUE((md1 <=> md3) == etl::strong_ordering::equal); // Same month and day + CHECK_TRUE((md1 <=> md2) == etl::strong_ordering::less); // Different month and day + CHECK_TRUE((md2 <=> md1) == etl::strong_ordering::greater); // Same month and day } #endif diff --git a/test/test_chrono_month_day_last.cpp b/test/test_chrono_month_day_last.cpp index bd5bee7f..927a9369 100644 --- a/test/test_chrono_month_day_last.cpp +++ b/test/test_chrono_month_day_last.cpp @@ -100,6 +100,20 @@ namespace CHECK_TRUE(month_day_last1 != month_day_last3); } +#if ETL_USING_CPP20 + //************************************************************************* + TEST(test_month_day_last_spaceship_operator) + { + Chrono::month_day_last month_day_last1(Chrono::January); + Chrono::month_day_last month_day_last2(Chrono::February); + Chrono::month_day_last month_day_last3(Chrono::January); + + CHECK_TRUE((month_day_last1 <=> month_day_last3) == etl::strong_ordering::equal); + CHECK_TRUE((month_day_last1 <=> month_day_last2) == etl::strong_ordering::less); + CHECK_TRUE((month_day_last2 <=> month_day_last1) == etl::strong_ordering::greater); + } +#endif + #if ETL_USING_ETL_CHRONO //************************************************************************* TEST(test_month_day_last_hashes_are_unique) diff --git a/test/test_chrono_time_point.cpp b/test/test_chrono_time_point.cpp index a4866f0c..89a00227 100644 --- a/test/test_chrono_time_point.cpp +++ b/test/test_chrono_time_point.cpp @@ -365,7 +365,7 @@ namespace CHECK_FALSE(tp10d >= tp20h); CHECK_FALSE(tp10h >= tp20d); -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 CHECK_TRUE((tp10h <=> tp10h) == 0); CHECK_TRUE((tp10h <=> tp20h) < 0); CHECK_TRUE((tp20h <=> tp10h) > 0); @@ -377,6 +377,8 @@ namespace CHECK_TRUE((tp10d <=> tp10h) == 0); CHECK_TRUE((tp10h <=> tp20d) < 0); CHECK_TRUE((tp20d <=> tp10h) > 0); + + static_assert(etl::is_same tp20d), etl::strong_ordering>::value, "Must return etl::strong_ordering"); #endif } diff --git a/test/test_chrono_year.cpp b/test/test_chrono_year.cpp index 2b8dad29..8e3c9f1c 100644 --- a/test/test_chrono_year.cpp +++ b/test/test_chrono_year.cpp @@ -284,7 +284,7 @@ namespace CHECK_FALSE(year10 == year20); CHECK_TRUE(year10 != year20); -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 CHECK_TRUE((year10 <=> year10) == 0); CHECK_TRUE((year10 <=> year20) < 0); CHECK_TRUE((year20 <=> year10) > 0); diff --git a/test/test_chrono_year_month.cpp b/test/test_chrono_year_month.cpp index 73065121..ad051386 100644 --- a/test/test_chrono_year_month.cpp +++ b/test/test_chrono_year_month.cpp @@ -89,7 +89,7 @@ namespace CHECK_FALSE(ym.ok()); // Invalid year_month } -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 //************************************************************************* TEST(test_year_month_spaceship_operator) { @@ -97,11 +97,11 @@ namespace Chrono::year_month ym2{Chrono::year(2001), Chrono::January}; Chrono::year_month ym3{Chrono::year(2000), Chrono::February}; - CHECK_TRUE((ym1 <=> ym1) == std::strong_ordering::equal); - CHECK_TRUE((ym1 <=> ym2) == std::strong_ordering::less); - CHECK_TRUE((ym2 <=> ym1) == std::strong_ordering::greater); - CHECK_TRUE((ym1 <=> ym3) == std::strong_ordering::less); - CHECK_TRUE((ym3 <=> ym1) == std::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym1) == etl::strong_ordering::equal); + CHECK_TRUE((ym1 <=> ym2) == etl::strong_ordering::less); + CHECK_TRUE((ym2 <=> ym1) == etl::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym3) == etl::strong_ordering::less); + CHECK_TRUE((ym3 <=> ym1) == etl::strong_ordering::greater); } #endif diff --git a/test/test_chrono_year_month_day.cpp b/test/test_chrono_year_month_day.cpp index e44ee967..a83f7ebe 100644 --- a/test/test_chrono_year_month_day.cpp +++ b/test/test_chrono_year_month_day.cpp @@ -98,7 +98,7 @@ namespace CHECK_FALSE(ymd.ok()); // Invalid year_month_day } -#if ETL_USING_CPP20 && ETL_USING_STL +#if ETL_USING_CPP20 //************************************************************************* TEST(test_year_month_day_spaceship_operator) { @@ -107,13 +107,13 @@ namespace Chrono::year_month_day ym3{Chrono::year(2000), Chrono::February, Chrono::day(1)}; Chrono::year_month_day ym4{Chrono::year(2000), Chrono::January, Chrono::day(2)}; - CHECK_TRUE((ym1 <=> ym1) == std::strong_ordering::equal); - CHECK_TRUE((ym1 <=> ym2) == std::strong_ordering::less); - CHECK_TRUE((ym2 <=> ym1) == std::strong_ordering::greater); - CHECK_TRUE((ym1 <=> ym3) == std::strong_ordering::less); - CHECK_TRUE((ym3 <=> ym1) == std::strong_ordering::greater); - CHECK_TRUE((ym1 <=> ym4) == std::strong_ordering::less); - CHECK_TRUE((ym4 <=> ym1) == std::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym1) == etl::strong_ordering::equal); + CHECK_TRUE((ym1 <=> ym2) == etl::strong_ordering::less); + CHECK_TRUE((ym2 <=> ym1) == etl::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym3) == etl::strong_ordering::less); + CHECK_TRUE((ym3 <=> ym1) == etl::strong_ordering::greater); + CHECK_TRUE((ym1 <=> ym4) == etl::strong_ordering::less); + CHECK_TRUE((ym4 <=> ym1) == etl::strong_ordering::greater); } #endif diff --git a/test/test_chrono_year_month_day_last.cpp b/test/test_chrono_year_month_day_last.cpp index 53a556af..8edaca2f 100644 --- a/test/test_chrono_year_month_day_last.cpp +++ b/test/test_chrono_year_month_day_last.cpp @@ -160,6 +160,23 @@ namespace CHECK_TRUE(year_month_day_last1 != year_month_day_last3); } +#if ETL_USING_CPP20 + //************************************************************************* + TEST(test_year_month_day_last_spaceship_operator) + { + Chrono::year_month_day_last year_month_day_last1(Chrono::year(2000), Chrono::month_day_last(Chrono::January)); + Chrono::year_month_day_last year_month_day_last2(Chrono::year(2001), Chrono::month_day_last(Chrono::January)); + Chrono::year_month_day_last year_month_day_last3(Chrono::year(2000), Chrono::month_day_last(Chrono::February)); + Chrono::year_month_day_last year_month_day_last4(Chrono::year(2000), Chrono::month_day_last(Chrono::January)); + + CHECK_TRUE((year_month_day_last1 <=> year_month_day_last4) == etl::strong_ordering::equal); + CHECK_TRUE((year_month_day_last1 <=> year_month_day_last2) == etl::strong_ordering::less); + CHECK_TRUE((year_month_day_last2 <=> year_month_day_last1) == etl::strong_ordering::greater); + CHECK_TRUE((year_month_day_last1 <=> year_month_day_last3) == etl::strong_ordering::less); + CHECK_TRUE((year_month_day_last3 <=> year_month_day_last1) == etl::strong_ordering::greater); + } +#endif + #if ETL_USING_ETL_CHRONO //************************************************************************* TEST(test_year_month_day_last_hashes_are_unique) diff --git a/test/test_compare.cpp b/test/test_compare.cpp index 032cbd41..d0ec3b7c 100644 --- a/test/test_compare.cpp +++ b/test/test_compare.cpp @@ -91,6 +91,20 @@ namespace } }; + struct ComparatorOnlyObject + { + int a; + int b; + }; + + struct LessBySecond + { + bool constexpr operator()(const ComparatorOnlyObject& lhs, const ComparatorOnlyObject& rhs) const + { + return lhs.b < rhs.b; + } + }; + typedef etl::compare CompareTest; SUITE(test_compare) @@ -191,5 +205,139 @@ namespace CHECK_EQUAL(CompareInt::Greater, cmp_test_greater); CHECK_EQUAL(CompareInt::Equal, cmp_test_equal); } + +#if ETL_USING_CPP20 + //************************************************************************* + TEST(test_strong_ordering) + { + constexpr etl::strong_ordering ordering_less(etl::strong_ordering::less); + constexpr etl::strong_ordering ordering_equal(etl::strong_ordering::equal); + constexpr etl::strong_ordering ordering_greater(etl::strong_ordering::greater); + + CHECK(ordering_less < 0); + CHECK(ordering_less <= 0); + CHECK(!(ordering_less == 0)); + CHECK(!(ordering_less > 0)); + CHECK(ordering_equal == 0); + CHECK(ordering_equal <= 0); + CHECK(ordering_equal >= 0); + CHECK(ordering_greater > 0); + CHECK(ordering_greater >= 0); + CHECK(!(ordering_greater == 0)); + + // Reversed operands. + CHECK(0 > ordering_less); + CHECK(0 < ordering_greater); + + static_assert(etl::strong_ordering::equivalent == etl::strong_ordering::equal, "Equivalent must alias equal"); + static_assert(etl::is_same::value, + "Must convert to weak_ordering"); + CHECK(etl::weak_ordering(etl::strong_ordering::less) < 0); + CHECK(etl::partial_ordering(etl::strong_ordering::greater) > 0); + } + + //************************************************************************* + TEST(test_make_strong_ordering) + { + constexpr etl::strong_ordering ordering_less = etl::make_strong_ordering(-1); + constexpr etl::strong_ordering ordering_equal = etl::make_strong_ordering(0); + constexpr etl::strong_ordering ordering_greater = etl::make_strong_ordering(1); + + CHECK(ordering_less < 0); + CHECK(ordering_equal == 0); + CHECK(ordering_greater > 0); + + static_assert(etl::is_same::value, "Must return etl::strong_ordering"); + } + + //************************************************************************* + TEST(test_weak_ordering) + { + constexpr etl::weak_ordering ordering_less(etl::weak_ordering::less); + constexpr etl::weak_ordering ordering_equivalent(etl::weak_ordering::equivalent); + constexpr etl::weak_ordering ordering_greater(etl::weak_ordering::greater); + + CHECK(ordering_less < 0); + CHECK(ordering_equivalent == 0); + CHECK(ordering_greater > 0); + + CHECK(0 > ordering_less); + CHECK(0 < ordering_greater); + + static_assert(etl::weak_ordering::equivalent == 0, "Equivalent must be zero"); + CHECK(etl::partial_ordering(etl::weak_ordering::less) < 0); + } + + //************************************************************************* + TEST(test_make_weak_ordering) + { + constexpr etl::weak_ordering ordering_less = etl::make_weak_ordering(-1); + constexpr etl::weak_ordering ordering_equivalent = etl::make_weak_ordering(0); + constexpr etl::weak_ordering ordering_greater = etl::make_weak_ordering(1); + + CHECK(ordering_less < 0); + CHECK(ordering_equivalent == 0); + CHECK(ordering_greater > 0); + + static_assert(etl::is_same::value, "Must return etl::weak_ordering"); + } + + //************************************************************************* + TEST(test_partial_ordering) + { + constexpr etl::partial_ordering ordering_less(etl::partial_ordering::less); + constexpr etl::partial_ordering ordering_equivalent(etl::partial_ordering::equivalent); + constexpr etl::partial_ordering ordering_greater(etl::partial_ordering::greater); + constexpr etl::partial_ordering ordering_unordered(etl::partial_ordering::unordered); + + CHECK(ordering_less < 0); + CHECK(ordering_equivalent == 0); + CHECK(ordering_greater > 0); + + // An unordered result compares false against zero in every direction. + CHECK(!(ordering_unordered < 0)); + CHECK(!(ordering_unordered <= 0)); + CHECK(!(ordering_unordered == 0)); + CHECK(!(ordering_unordered >= 0)); + CHECK(!(ordering_unordered > 0)); + CHECK(ordering_unordered != 0); + + static_assert(etl::partial_ordering::equivalent == 0, "Equivalent must be zero"); + } + + //************************************************************************* + TEST(test_make_partial_ordering) + { + constexpr etl::partial_ordering ordering_less = etl::make_partial_ordering(-1); + constexpr etl::partial_ordering ordering_equivalent = etl::make_partial_ordering(0); + constexpr etl::partial_ordering ordering_greater = etl::make_partial_ordering(1); + + CHECK(ordering_less < 0); + CHECK(ordering_equivalent == 0); + CHECK(ordering_greater > 0); + + static_assert(etl::is_same::value, "Must return etl::partial_ordering"); + } + + //************************************************************************* + TEST(test_three_way_compare_custom_less) + { + constexpr ComparatorOnlyObject lhs = {5, 1}; + constexpr ComparatorOnlyObject rhs = {1, 2}; + constexpr ComparatorOnlyObject eq = {9, 1}; + + constexpr int cmp_less = etl::three_way_compare(lhs, rhs); + constexpr int cmp_greater = etl::three_way_compare(rhs, lhs); + constexpr int cmp_equal = etl::three_way_compare(lhs, eq); + + CHECK_EQUAL(-1, cmp_less); + CHECK_EQUAL(1, cmp_greater); + CHECK_EQUAL(0, cmp_equal); + + CHECK(etl::make_strong_ordering(cmp_less) < 0); + CHECK(etl::make_strong_ordering(cmp_greater) > 0); + CHECK(etl::make_strong_ordering(cmp_equal) == 0); + } +#endif } } // namespace From 525fd0304aabc8c61098a011d24483bb227269de Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Sun, 13 Sep 2026 10:50:31 +0100 Subject: [PATCH 04/13] Updated ignore for hard link for CLion --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b5cc3865..035a6dfa 100644 --- a/.gitignore +++ b/.gitignore @@ -423,3 +423,4 @@ include/etl/iterator.h~RFfd5eda.TMP test/vs2022/Build docs/Messaging test/vs2026/Build +test/include From 22bdef2ee41279a75d92c91a7c831b73084e4c1f Mon Sep 17 00:00:00 2001 From: Mike Bloom <91038685+mike919192@users.noreply.github.com> Date: Thu, 17 Sep 2026 07:57:10 -0400 Subject: [PATCH 05/13] Simplify noexcept expressions using ETL_NOT_USING_EXCEPTIONS (#1606) * Simplify noexcept containing ETL_NOT_USING_EXCEPTIONS * Revert some format --------- Co-authored-by: John Wellbelove Co-authored-by: Roland Reichwein --- include/etl/array.h | 4 ++-- include/etl/array_view.h | 4 ++-- include/etl/array_wrapper.h | 4 ++-- include/etl/span.h | 26 +++++++++++--------------- include/etl/string_view.h | 2 +- include/etl/vector.h | 8 +++----- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/include/etl/array.h b/include/etl/array.h index c1d7d6e4..a5f48e27 100644 --- a/include/etl/array.h +++ b/include/etl/array.h @@ -137,7 +137,7 @@ namespace etl /// Returns a reference to the value at index 'i'. ///\param i The index of the element to access. //************************************************************************* - ETL_NODISCARD ETL_CONSTEXPR14 reference operator[](size_t i) ETL_NOEXCEPT_EXPR(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_INDEX_OPERATOR) + ETL_NODISCARD ETL_CONSTEXPR14 reference operator[](size_t i) ETL_NOEXCEPT_EXPR(ETL_NOT_CHECKING_INDEX_OPERATOR) { ETL_ASSERT_CHECK_INDEX_OPERATOR(i < SIZE, ETL_ERROR(array_out_of_range)); @@ -149,7 +149,7 @@ namespace etl /// Returns a const reference to the value at index 'i'. ///\param i The index of the element to access. //************************************************************************* - ETL_NODISCARD ETL_CONSTEXPR const_reference operator[](size_t i) const ETL_NOEXCEPT_EXPR(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_INDEX_OPERATOR) + ETL_NODISCARD ETL_CONSTEXPR const_reference operator[](size_t i) const ETL_NOEXCEPT_EXPR(ETL_NOT_CHECKING_INDEX_OPERATOR) { // Throwing from c++11 constexpr requires special syntax #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_INDEX_OPERATOR diff --git a/include/etl/array_view.h b/include/etl/array_view.h index bb25dac0..7d20c5a2 100644 --- a/include/etl/array_view.h +++ b/include/etl/array_view.h @@ -518,7 +518,7 @@ namespace etl /// If asserts or exceptions are enabled, throws an etl::array_view_bounds /// if the index is out of bounds. //************************************************************************* - reference operator[](const size_t i) ETL_NOEXCEPT_EXPR(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_INDEX_OPERATOR) + reference operator[](const size_t i) ETL_NOEXCEPT_EXPR(ETL_NOT_CHECKING_INDEX_OPERATOR) { ETL_ASSERT_CHECK_INDEX_OPERATOR(i < size(), ETL_ERROR(array_view_bounds)); @@ -531,7 +531,7 @@ namespace etl /// If asserts or exceptions are enabled, throws an etl::array_view_bounds /// if the index is out of bounds. //************************************************************************* - const_reference operator[](const size_t i) const ETL_NOEXCEPT_EXPR(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_INDEX_OPERATOR) + const_reference operator[](const size_t i) const ETL_NOEXCEPT_EXPR(ETL_NOT_CHECKING_INDEX_OPERATOR) { ETL_ASSERT_CHECK_INDEX_OPERATOR(i < size(), ETL_ERROR(array_view_bounds)); diff --git a/include/etl/array_wrapper.h b/include/etl/array_wrapper.h index 2c434a05..97e198ec 100644 --- a/include/etl/array_wrapper.h +++ b/include/etl/array_wrapper.h @@ -271,7 +271,7 @@ namespace etl /// If asserts or exceptions are enabled, throws an /// etl::array_wrapper_bounds if the index is out of bounds. //************************************************************************* - reference operator[](size_t i) ETL_NOEXCEPT_EXPR(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_INDEX_OPERATOR) + reference operator[](size_t i) ETL_NOEXCEPT_EXPR(ETL_NOT_CHECKING_INDEX_OPERATOR) { ETL_ASSERT_CHECK_INDEX_OPERATOR(i < SIZE, ETL_ERROR(etl::array_wrapper_bounds)); return ARRAY_[i]; @@ -282,7 +282,7 @@ namespace etl /// If asserts or exceptions are enabled, throws an /// etl::array_wrapper_bounds if the index is out of bounds. //************************************************************************* - ETL_CONSTEXPR const_reference operator[](size_t i) const ETL_NOEXCEPT_EXPR(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_INDEX_OPERATOR) + ETL_CONSTEXPR const_reference operator[](size_t i) const ETL_NOEXCEPT_EXPR(ETL_NOT_CHECKING_INDEX_OPERATOR) { // Throwing from c++11 constexpr requires special syntax #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_INDEX_OPERATOR diff --git a/include/etl/span.h b/include/etl/span.h index d948c355..71c7e907 100644 --- a/include/etl/span.h +++ b/include/etl/span.h @@ -644,8 +644,7 @@ namespace etl /// Obtains a span that is a view over the first count elements of this /// span. //************************************************************************* - ETL_NODISCARD ETL_CONSTEXPR etl::span first(size_t count) const - ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NODISCARD ETL_CONSTEXPR etl::span first(size_t count) const ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return count <= size() ? etl::span(pbegin, pbegin + count) : throw(ETL_ERROR(span_out_of_range)); @@ -672,8 +671,7 @@ namespace etl //************************************************************************* /// Obtains a span that is a view over the last count elements of this span. //************************************************************************* - ETL_NODISCARD ETL_CONSTEXPR etl::span last(size_t count) const - ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NODISCARD ETL_CONSTEXPR etl::span last(size_t count) const ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return count <= size() ? etl::span((pbegin + Extent) - count, (pbegin + Extent)) @@ -735,7 +733,7 @@ namespace etl /// elements of this span. //************************************************************************* ETL_NODISCARD ETL_CONSTEXPR etl::span subspan(size_t offset, size_t count = etl::dynamic_extent) const - ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return (offset <= size()) && (count != etl::dynamic_extent ? count <= (size() - offset) : true) @@ -1003,7 +1001,7 @@ namespace etl //************************************************************************* /// Returns a reference to the first element. //************************************************************************* - ETL_NODISCARD ETL_CONSTEXPR reference front() const ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NODISCARD ETL_CONSTEXPR reference front() const ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return size() > 0 ? *pbegin : throw(ETL_ERROR(span_out_of_range)); @@ -1017,7 +1015,7 @@ namespace etl //************************************************************************* /// Returns a reference to the last element. //************************************************************************* - ETL_NODISCARD ETL_CONSTEXPR reference back() const ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NODISCARD ETL_CONSTEXPR reference back() const ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return size() > 0 ? *(pend - 1) : throw(ETL_ERROR(span_out_of_range)); @@ -1197,7 +1195,7 @@ namespace etl /// span. //************************************************************************* template - ETL_NODISCARD ETL_CONSTEXPR etl::span first() const ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NODISCARD ETL_CONSTEXPR etl::span first() const ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return COUNT <= size() ? etl::span(pbegin, pbegin + COUNT) : throw(ETL_ERROR(span_out_of_range)); @@ -1212,8 +1210,7 @@ namespace etl /// Obtains a span that is a view over the first count elements of this /// span. //************************************************************************* - ETL_NODISCARD ETL_CONSTEXPR etl::span first(size_t count) const - ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NODISCARD ETL_CONSTEXPR etl::span first(size_t count) const ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return count <= size() ? etl::span(pbegin, pbegin + count) : throw(ETL_ERROR(span_out_of_range)); @@ -1228,7 +1225,7 @@ namespace etl /// Obtains a span that is a view over the last COUNT elements of this span. //************************************************************************* template - ETL_NODISCARD ETL_CONSTEXPR etl::span last() const ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NODISCARD ETL_CONSTEXPR etl::span last() const ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return COUNT <= size() ? etl::span(pend - COUNT, pend) : throw(ETL_ERROR(span_out_of_range)); @@ -1242,8 +1239,7 @@ namespace etl //************************************************************************* /// Obtains a span that is a view over the last count elements of this span. //************************************************************************* - ETL_NODISCARD ETL_CONSTEXPR etl::span last(size_t count) const - ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NODISCARD ETL_CONSTEXPR etl::span last(size_t count) const ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return count <= size() ? etl::span(pend - count, pend) : throw(ETL_ERROR(span_out_of_range)); @@ -1261,7 +1257,7 @@ namespace etl //************************************************************************* template ETL_NODISCARD ETL_CONSTEXPR etl::span< element_type, COUNT != etl::dynamic_extent ? COUNT : etl::dynamic_extent> subspan() const - ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_EXTRA return (OFFSET <= size()) && (COUNT != etl::dynamic_extent ? COUNT <= (size() - OFFSET) : true) @@ -1305,7 +1301,7 @@ namespace etl /// elements of this span. //************************************************************************* ETL_NODISCARD ETL_CONSTEXPR14 etl::span subspan(size_t offset, size_t count = etl::dynamic_extent) const - ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_EXTRA) + ETL_NOEXCEPT_IF(ETL_NOT_CHECKING_EXTRA) { ETL_ASSERT_CHECK_EXTRA(offset <= size(), ETL_ERROR(span_out_of_range)); ETL_ASSERT_CHECK_EXTRA(count != etl::dynamic_extent ? count <= (size() - offset) : true, ETL_ERROR(span_out_of_range)); diff --git a/include/etl/string_view.h b/include/etl/string_view.h index 2cb20e37..0827636c 100644 --- a/include/etl/string_view.h +++ b/include/etl/string_view.h @@ -355,7 +355,7 @@ namespace etl /// If asserts or exceptions are enabled, throws an etl::string_view_bounds /// if the index is out of bounds. //************************************************************************* - ETL_CONSTEXPR const_reference operator[](size_type i) const ETL_NOEXCEPT_EXPR(ETL_NOT_USING_EXCEPTIONS || ETL_NOT_CHECKING_INDEX_OPERATOR) + ETL_CONSTEXPR const_reference operator[](size_type i) const ETL_NOEXCEPT_EXPR(ETL_NOT_CHECKING_INDEX_OPERATOR) { #if ETL_USING_CPP11 && ETL_NOT_USING_CPP14 && ETL_USING_EXCEPTIONS && ETL_CHECKING_INDEX_OPERATOR return i < size() ? mbegin[i] : throw(ETL_ERROR(string_view_bounds)); diff --git a/include/etl/vector.h b/include/etl/vector.h index beac6cdc..61ee6c26 100644 --- a/include/etl/vector.h +++ b/include/etl/vector.h @@ -393,7 +393,7 @@ namespace etl typename etl::enable_if::value && etl::is_convertible::value_type, T>::value, typename etl::enable_if::value, void>::type>::type - assign(TIterator first, TIterator last) ETL_NOEXCEPT_IF((etl::is_nothrow_copy_constructible::value && ETL_NOT_USING_EXCEPTIONS)) + assign(TIterator first, TIterator last) { #if ETL_USING_CPP11 ETL_STATIC_ASSERT((etl::is_same::type, @@ -2082,7 +2082,6 @@ namespace etl /// Copy constructor. //************************************************************************* vector_ext(const vector_ext& other, void* buffer, size_t max_size) - ETL_NOEXCEPT_IF((etl::is_nothrow_copy_constructible::value && ETL_NOT_USING_EXCEPTIONS)) : etl::ivector(reinterpret_cast(buffer), max_size) { if (&other != this) @@ -2094,7 +2093,7 @@ namespace etl //************************************************************************* /// Assignment operator. //************************************************************************* - vector_ext& operator=(const vector_ext& rhs) ETL_NOEXCEPT_IF((etl::is_nothrow_copy_constructible::value && ETL_NOT_USING_EXCEPTIONS)) + vector_ext& operator=(const vector_ext& rhs) { if (&rhs != this) { @@ -2109,7 +2108,6 @@ namespace etl /// Move constructor. //************************************************************************* vector_ext(vector_ext&& other, void* buffer, size_t max_size) - ETL_NOEXCEPT_IF((etl::is_nothrow_move_constructible::value && ETL_NOT_USING_EXCEPTIONS)) : etl::ivector(reinterpret_cast(buffer), max_size) { if (&other != this) @@ -2123,7 +2121,7 @@ namespace etl //************************************************************************* /// Move assignment operator. //************************************************************************* - vector_ext& operator=(vector_ext&& rhs) ETL_NOEXCEPT_IF((etl::is_nothrow_move_constructible::value && ETL_NOT_USING_EXCEPTIONS)) + vector_ext& operator=(vector_ext&& rhs) { if (&rhs != this) { From d9a9488582305cbb1fb19ec314655570798d26df Mon Sep 17 00:00:00 2001 From: Drew Rife Date: Sat, 26 Sep 2026 05:45:48 -0400 Subject: [PATCH 06/13] Add referenced clamped values with shared ranges (#1597) * Support runtime floating-point clamped_value before C++20 * Add referenced clamped values with shared ranges --- docs/utilities/clamped-value.md | 176 ++++++++++- include/etl/clamped_value.h | 395 ++++++++++++++++++++++++ test/syntax_check/clamped_value.h.t.cpp | 10 + test/test_clamped_value.cpp | 152 +++++++++ 4 files changed, 732 insertions(+), 1 deletion(-) diff --git a/docs/utilities/clamped-value.md b/docs/utilities/clamped-value.md index 5bae0543..2396a96a 100644 --- a/docs/utilities/clamped-value.md +++ b/docs/utilities/clamped-value.md @@ -26,7 +26,8 @@ including the header to override automatic detection. When `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is `0`, `T` must be an integral type. When it is `1`, `float`, `double`, and `long double` may also be used with -runtime bounds. +runtime bounds. `clamped_value_range` and `referenced_clamped_value` are +available in C++11 and later. ```cpp // C++11 and later @@ -48,6 +49,20 @@ etl::clamped_value float_ct(0.5f); #endif ``` +In C++11 and later, multiple values can share one immutable runtime range: + +```cpp +const etl::clamped_value_range range(-1.5f, 2.5f); + +etl::referenced_clamped_value value1(range, 0.5f); +etl::referenced_clamped_value value2(range, 1.5f); +``` + +`referenced_clamped_value` stores an +`etl::reference_wrapper>`. The range must outlive +every value that refers to it. Construction from a temporary range is deleted. +Copying or swapping a referenced value also copies or swaps its range reference. + ## Types `difference_type` is the signed counterpart of `T` for integral values. For @@ -55,6 +70,9 @@ floating-point values, `difference_type` is `T`, allowing fractional steps. `limits_type` and `difference_limits_type` provide the corresponding `etl::numeric_limits` specializations. +`referenced_clamped_value` also defines `range_type` as +`clamped_value_range`. + ## Constructors ```cpp @@ -76,6 +94,23 @@ runtime-bound specialization. Supplying `Min` and `Max` selects fixed compile-time bounds. NaN bounds and initial values are rejected. Infinite bounds and values are permitted. +### Referenced ranges + +```cpp +clamped_value_range range(min, max); +referenced_clamped_value value(range); +referenced_clamped_value value(range, initial); +``` + +The one-argument value constructor initializes to `range.min()`. The +two-argument form clamps `initial` to the shared range. A range is immutable +after construction, so every referring value observes stable bounds. A +default-constructed range uses the full representable range of `T`. + +`referenced_clamped_value` is non-owning. The referenced range must outlive +the value and any copies of it. The constructors taking `range_type&&` are +deleted to prevent direct construction from a temporary range. + ## Modifiers ```cpp @@ -93,6 +128,10 @@ counterpart of `T` as its step type. For floating-point values, the step type is `T` and may be fractional. Steps saturate in constant time at the minimum or maximum rather than wrapping. NaN steps are rejected. +`referenced_clamped_value` supports `set(T)`, `to_min()`, `to_max()`, and +`advance(difference_type)`. It does not provide `set(min, max)` because its +range is immutable and shared. + Positive steps move toward `max`; negative steps move toward `min`. Infinite steps saturate at a finite bound. Infinite values and bounds remain valid. @@ -122,6 +161,14 @@ ETL_NODISCARD T max() const noexcept; Gets the current value and its bounds. Both compile-time and runtime specializations provide these accessors as const-qualified member functions. +`referenced_clamped_value` additionally provides: + +```cpp +ETL_NODISCARD const range_type& range() const noexcept; +``` + +This returns the exact `clamped_value_range` object referenced by the value. + ## Operators ```cpp @@ -143,6 +190,8 @@ floating-point values, assignment and compound-assignment reject NaN. Prefix increment and decrement return a reference to the updated object. Postfix increment and decrement return a copy of the value before it was updated. All four operations move by one and saturate at the applicable bound. +`referenced_clamped_value` provides the same conversion, assignment, +increment, decrement, and compound-assignment operators. ## Operations @@ -153,6 +202,10 @@ ETL_CONSTEXPR14 void swap(clamped_value& lhs, clamped_value& rhs); Swaps clamped values. Runtime values also swap their bounds. +Swapping two `referenced_clamped_value` objects swaps both their current +values and range references. Copy construction and copy assignment preserve +the source's range identity; they do not copy the range itself. + Equality, inequality, and relational comparisons are provided. Comparisons against arithmetic values use the implicit conversion to `T`. Equality compares the current values; runtime bounds do not affect equality. Runtime @@ -169,3 +222,124 @@ assignments, and steps also trigger an ETL assertion. Depending on the ETL error-handler configuration, an assertion may throw `etl::exception`, invoke a configured handler, or terminate. Operations that validate input are only `noexcept` when exceptions are disabled. + +The same validation applies when constructing `clamped_value_range` and +when assigning or advancing a `referenced_clamped_value`. + +## Shared Referenced Ranges + +`clamped_value_range` and `referenced_clamped_value` are available in +C++11 and later. They let independent values share immutable runtime bounds. + +### `clamped_value_range` + +```cpp +template +class clamped_value_range; +``` + +#### Constructors + +```cpp +clamped_value_range(); +clamped_value_range(T min, T max); +``` + +The default constructor uses the full representable range of `T`. The +two-argument constructor stores immutable bounds and rejects reversed or NaN +bounds. + +#### Accessors + +```cpp +ETL_NODISCARD T min() const noexcept; +ETL_NODISCARD T max() const noexcept; +``` + +### `referenced_clamped_value` + +```cpp +template +class referenced_clamped_value; +``` + +The class stores a current value and an +`etl::reference_wrapper>`. It does not own the +range, which must outlive the value and all copies of it. + +Member types are `range_type`, `difference_type`, `limits_type`, and +`difference_limits_type`, with the same numeric meanings as `clamped_value`. + +#### Constructors + +```cpp +explicit referenced_clamped_value(const range_type& range); +referenced_clamped_value(const range_type& range, T initial); +referenced_clamped_value(const referenced_clamped_value& other); + +referenced_clamped_value(range_type&&) = delete; +referenced_clamped_value(range_type&&, T) = delete; +``` + +The one-argument constructor initializes to `range.min()`. The two-argument +constructor clamps `initial`. Temporary ranges are rejected to prevent an +immediately dangling reference. Copy construction preserves the source value +and range identity. + +#### Modifiers + +```cpp +void set(T value); +void to_min() noexcept; +void to_max() noexcept; +void advance(difference_type n); +``` + +These operations mirror `clamped_value`. There is no `set(min, max)` because +the referenced range is immutable and shared. + +#### Accessors + +```cpp +ETL_NODISCARD T get() const noexcept; +ETL_NODISCARD T min() const noexcept; +ETL_NODISCARD T max() const noexcept; +ETL_NODISCARD const range_type& range() const noexcept; +ETL_NODISCARD operator T() const noexcept; +``` + +`range()` returns the exact range object held by reference. + +#### Operators + +```cpp +referenced_clamped_value& operator=(const referenced_clamped_value& other) & noexcept; +referenced_clamped_value& operator=(T value) &; +referenced_clamped_value& operator++() & noexcept; +referenced_clamped_value operator++(int) noexcept; +referenced_clamped_value& operator--() & noexcept; +referenced_clamped_value operator--(int) noexcept; +referenced_clamped_value& operator+=(difference_type n) &; +referenced_clamped_value& operator-=(difference_type n) &; +``` + +Copy assignment copies both the current value and range reference. Arithmetic +and value assignment clamp to the referenced bounds. Comparisons compare only +current values, not range identity or bounds. + +#### Swap + +```cpp +void swap(referenced_clamped_value& other) noexcept; +void swap(referenced_clamped_value& lhs, + referenced_clamped_value& rhs) noexcept; +``` + +Swap exchanges both current values and range references, keeping each value +associated with its original bounds. + +#### Errors + +Invalid or NaN range bounds trigger an ETL assertion. NaN initial values, +assignments, and steps are also rejected. Infinite values follow the same +behavior as `clamped_value`. diff --git a/include/etl/clamped_value.h b/include/etl/clamped_value.h index d7f3dcf9..37385139 100644 --- a/include/etl/clamped_value.h +++ b/include/etl/clamped_value.h @@ -35,6 +35,7 @@ SOFTWARE. #include "absolute.h" #include "algorithm.h" #include "error_handler.h" +#include "functional.h" #include "limits.h" #include "math.h" #include "static_assert.h" @@ -978,6 +979,400 @@ namespace etl T min_value; T max_value; }; + +#if ETL_USING_CPP11 + //*************************************************************************** + /// An immutable pair of runtime limits that may be shared by multiple + /// referenced_clamped_value objects. + ///@tparam T An integral type, or a floating-point type when + /// ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`. + ///\ingroup clamped_value + //*************************************************************************** + template + class clamped_value_range + { + public: + + typedef typename private_clamped_value::traits::limits_type limits_type; + + //************************************************************************* + /// Constructs the full representable range of T. + //************************************************************************* + ETL_CONSTEXPR14 clamped_value_range() + : min_value(limits_type::lowest()) + , max_value(limits_type::max()) + { + } + + //************************************************************************* + /// Constructs a range with the supplied limits. + /// Reversed bounds and NaN bounds are rejected. + ///\param min_ The minimum value. + ///\param max_ The maximum value. + //************************************************************************* + ETL_CONSTEXPR14 clamped_value_range(T min_, T max_) + : min_value(min_) + , max_value(max_) + { + private_clamped_value::validate(min_); + private_clamped_value::validate(max_); + ETL_ASSERT(min_ <= max_, ETL_ERROR_GENERIC("clamped_value_range: invalid range")); + } + + //************************************************************************* + /// Gets the minimum value. + //************************************************************************* + ETL_NODISCARD ETL_CONSTEXPR T min() const ETL_NOEXCEPT + { + return min_value; + } + + //************************************************************************* + /// Gets the maximum value. + //************************************************************************* + ETL_NODISCARD ETL_CONSTEXPR T max() const ETL_NOEXCEPT + { + return max_value; + } + + private: + + const T min_value; + const T max_value; + }; + + //*************************************************************************** + /// Provides a value clamped to a shared immutable runtime range. + /// The referenced range must outlive this object. + ///@tparam T An integral type, or a floating-point type when + /// ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`. + ///\ingroup clamped_value + //*************************************************************************** + template + class referenced_clamped_value + { + public: + + /// The referenced range type. + typedef clamped_value_range range_type; + + /// The type used to advance or subtract from the stored value. + typedef typename private_clamped_value::traits::difference_type difference_type; + + /// Numeric limits for the stored value type. + typedef typename private_clamped_value::traits::limits_type limits_type; + + /// Numeric limits for difference_type. + typedef typename private_clamped_value::traits::difference_limits_type difference_limits_type; + + //************************************************************************* + /// Constructs a value initialized to the minimum of the referenced range. + ///\param range_ The range to reference. It must outlive this object. + //************************************************************************* + explicit referenced_clamped_value(const range_type& range_) + : value(range_.min()) + , value_range(etl::cref(range_)) + { + } + + //************************************************************************* + /// Constructs a value clamped to the referenced range. + ///\param range_ The range to reference. It must outlive this object. + ///\param initial The initial value. + //************************************************************************* + referenced_clamped_value(const range_type& range_, T initial) + : value(private_clamped_value::validated_clamp(initial, range_.min(), range_.max())) + , value_range(etl::cref(range_)) + { + } + + //************************************************************************* + /// Prevents construction with a temporary range. + //************************************************************************* + referenced_clamped_value(range_type&&) = delete; + referenced_clamped_value(range_type&&, T) = delete; + + //************************************************************************* + /// Copies the value and its range reference. + ///\param other The value to copy. + //************************************************************************* + referenced_clamped_value(const referenced_clamped_value& other) ETL_NOEXCEPT + : value(other.value) + , value_range(other.value_range) + { + } + + //************************************************************************* + /// Copy assignment. Copies the value and its range reference. + ///\param other The value and range reference to copy. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator=(const referenced_clamped_value& other) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT + { + if (this != &other) + { + value = other.value; + value_range = other.value_range; + } + + return *this; + } + + //************************************************************************* + /// Sets the value and clamps it to the referenced range. + ///\param value_ The value to assign. + //************************************************************************* + void set(T value_) + { + value = private_clamped_value::validated_clamp(value_, min(), max()); + } + + //************************************************************************* + /// Resets the value to the minimum of the referenced range. + //************************************************************************* + void to_min() ETL_NOEXCEPT + { + value = min(); + } + + //************************************************************************* + /// Resets the value to the maximum of the referenced range. + //************************************************************************* + void to_max() ETL_NOEXCEPT + { + value = max(); + } + + //************************************************************************* + /// Advances the value and saturates it at the referenced bounds. + ///\param n The difference to add. + //************************************************************************* + void advance(difference_type n) + { + private_clamped_value::validate(n); + value = private_clamped_value::advance(value, min(), max(), n); + } + + //************************************************************************* + /// Converts to the underlying value type. + ///\return The current value. + //************************************************************************* + ETL_NODISCARD operator T() const ETL_NOEXCEPT + { + return value; + } + + //************************************************************************* + /// Advances by one and saturates at the maximum. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator++() ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT + { + value = private_clamped_value::advance(value, min(), max(), difference_type(1)); + return *this; + } + + //************************************************************************* + /// Advances by one and returns the previous value. + ///\return The value before incrementing. + //************************************************************************* + referenced_clamped_value operator++(int) ETL_NOEXCEPT + { + referenced_clamped_value temp(*this); + ++(*this); + return temp; + } + + //************************************************************************* + /// Subtracts one and saturates at the minimum. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator--() ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT + { + value = private_clamped_value::advance(value, min(), max(), difference_type(-1)); + return *this; + } + + //************************************************************************* + /// Subtracts one and returns the previous value. + ///\return The value before decrementing. + //************************************************************************* + referenced_clamped_value operator--(int) ETL_NOEXCEPT + { + referenced_clamped_value temp(*this); + --(*this); + return temp; + } + + //************************************************************************* + /// Assigns a value and clamps it to the referenced range. + ///\param value_ The value to assign. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator=(T value_) ETL_LVALUE_REF_QUALIFIER + { + set(value_); + return *this; + } + + //************************************************************************* + /// Adds a difference and clamps to the referenced range. + ///\param n The difference to add. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator+=(difference_type n) ETL_LVALUE_REF_QUALIFIER + { + advance(n); + return *this; + } + + //************************************************************************* + /// Subtracts a difference and clamps to the referenced range. + ///\param n The difference to subtract. + ///\return A reference to this value. + //************************************************************************* + referenced_clamped_value& operator-=(difference_type n) ETL_LVALUE_REF_QUALIFIER + { + private_clamped_value::validate(n); + value = private_clamped_value::subtract(value, min(), max(), n); + return *this; + } + + //************************************************************************* + /// Gets the current value. + ///\return The current value. + //************************************************************************* + ETL_NODISCARD + T get() const ETL_NOEXCEPT + { + return value; + } + + //************************************************************************* + /// Gets the minimum of the referenced range. + ///\return The minimum value. + //************************************************************************* + ETL_NODISCARD + T min() const ETL_NOEXCEPT + { + return value_range.get().min(); + } + + //************************************************************************* + /// Gets the maximum of the referenced range. + ///\return The maximum value. + //************************************************************************* + ETL_NODISCARD + T max() const ETL_NOEXCEPT + { + return value_range.get().max(); + } + + //************************************************************************* + /// Gets the referenced range. + ///\return The referenced range. + //************************************************************************* + ETL_NODISCARD + const range_type& range() const ETL_NOEXCEPT + { + return value_range.get(); + } + + //************************************************************************* + /// Swaps the values and range references. + ///\param other The value to swap with. + //************************************************************************* + void swap(referenced_clamped_value& other) ETL_NOEXCEPT + { + using ETL_OR_STD::swap; + swap(value, other.value); + swap(value_range, other.value_range); + } + + //************************************************************************* + /// Swaps the values and range references. + ///\param lhs The first value. + ///\param rhs The second value. + //************************************************************************* + friend void swap(referenced_clamped_value& lhs, referenced_clamped_value& rhs) ETL_NOEXCEPT + { + lhs.swap(rhs); + } + + //************************************************************************* + /// Compares current values for equality. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if the current values are equal. + //************************************************************************* + friend bool operator==(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + #include "private/diagnostic_float_equal_push.h" + return lhs.value == rhs.value; + #include "private/diagnostic_pop.h" + } + + //************************************************************************* + /// Compares current values for inequality. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if the current values differ. + //************************************************************************* + friend bool operator!=(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return !(lhs == rhs); + } + + //************************************************************************* + /// Compares current values using less-than. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if lhs is less than rhs. + //************************************************************************* + friend bool operator<(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return lhs.value < rhs.value; + } + + //************************************************************************* + /// Compares current values using less-than-or-equal. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if lhs is less than or equal to rhs. + //************************************************************************* + friend bool operator<=(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return !(rhs < lhs); + } + + //************************************************************************* + /// Compares current values using greater-than. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if lhs is greater than rhs. + //************************************************************************* + friend bool operator>(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return rhs < lhs; + } + + //************************************************************************* + /// Compares current values using greater-than-or-equal. + ///\param lhs The left-hand value. + ///\param rhs The right-hand value. + ///\return `true` if lhs is greater than or equal to rhs. + //************************************************************************* + friend bool operator>=(const referenced_clamped_value& lhs, const referenced_clamped_value& rhs) ETL_NOEXCEPT + { + return !(lhs < rhs); + } + + private: + + T value; + etl::reference_wrapper value_range; + }; +#endif } // namespace etl #endif diff --git a/test/syntax_check/clamped_value.h.t.cpp b/test/syntax_check/clamped_value.h.t.cpp index 7651ff8c..31f6d549 100644 --- a/test/syntax_check/clamped_value.h.t.cpp +++ b/test/syntax_check/clamped_value.h.t.cpp @@ -37,3 +37,13 @@ etl::clamped_value clamped_double_run_time; #if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE etl::clamped_value clamped_float_compile_time; #endif + +#if ETL_USING_CPP11 +const etl::clamped_value_range referenced_clamped_range(0, 10); +etl::referenced_clamped_value referenced_clamped_int(referenced_clamped_range); + + #if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE +const etl::clamped_value_range referenced_clamped_float_range(-1.0f, 1.0f); +etl::referenced_clamped_value referenced_clamped_float(referenced_clamped_float_range); + #endif +#endif diff --git a/test/test_clamped_value.cpp b/test/test_clamped_value.cpp index cdf81420..09a64781 100644 --- a/test/test_clamped_value.cpp +++ b/test/test_clamped_value.cpp @@ -531,5 +531,157 @@ namespace } #endif #endif + +#if ETL_USING_CPP11 + //************************************************************************* + TEST(test_referenced_clamped_value_uses_shared_range) + { + const etl::clamped_value_range range(2, 7); + etl::referenced_clamped_value value1(range, 1); + etl::referenced_clamped_value value2(range, 8); + + CHECK_EQUAL(2, value1.get()); + CHECK_EQUAL(7, value2.get()); + CHECK_EQUAL(2, value1.min()); + CHECK_EQUAL(7, value1.max()); + CHECK_EQUAL(&range, &value1.range()); + CHECK_EQUAL(&range, &value2.range()); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_arithmetic_saturates) + { + const etl::clamped_value_range range(-2, 3); + etl::referenced_clamped_value value(range, 0); + + value += 2; + CHECK_EQUAL(2, value.get()); + ++value; + ++value; + CHECK_EQUAL(3, value.get()); + value -= 4; + CHECK_EQUAL(-1, value.get()); + --value; + --value; + CHECK_EQUAL(-2, value.get()); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_modifiers_and_postfix_operators) + { + const etl::clamped_value_range range(2, 7); + etl::referenced_clamped_value value(range); + + CHECK_EQUAL(2, value.get()); + value.set(4); + CHECK_EQUAL(4, value.get()); + + etl::referenced_clamped_value previous = value++; + CHECK_EQUAL(4, previous.get()); + CHECK_EQUAL(5, value.get()); + + previous = value--; + CHECK_EQUAL(5, previous.get()); + CHECK_EQUAL(4, value.get()); + + value.to_max(); + CHECK_EQUAL(7, value.get()); + value.to_min(); + CHECK_EQUAL(2, value.get()); + + value = 100; + CHECK_EQUAL(7, value.get()); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_default_range_and_comparisons) + { + const etl::clamped_value_range default_range; + const etl::clamped_value_range range(0, 10); + etl::referenced_clamped_value value1(range, 3); + etl::referenced_clamped_value value2(range, 4); + const int converted = value1; + + CHECK_EQUAL(etl::numeric_limits::lowest(), default_range.min()); + CHECK_EQUAL(etl::numeric_limits::max(), default_range.max()); + CHECK_EQUAL(3, converted); + CHECK(value1 == value1); + CHECK(value1 != value2); + CHECK(value1 < value2); + CHECK(value1 <= value2); + CHECK(value2 > value1); + CHECK(value2 >= value1); + CHECK(value1 < 4); + CHECK(3 == value1); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_copy_assignment_and_swap_ranges) + { + const etl::clamped_value_range range1(0, 10); + const etl::clamped_value_range range2(20, 30); + etl::referenced_clamped_value value1(range1, 4); + etl::referenced_clamped_value value2(range2, 24); + + etl::referenced_clamped_value copy(value1); + CHECK_EQUAL(&range1, ©.range()); + CHECK_EQUAL(4, copy.get()); + + copy = value2; + CHECK_EQUAL(&range2, ©.range()); + CHECK_EQUAL(24, copy.get()); + + swap(value1, value2); + CHECK_EQUAL(&range2, &value1.range()); + CHECK_EQUAL(24, value1.get()); + CHECK_EQUAL(&range1, &value2.range()); + CHECK_EQUAL(4, value2.get()); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_rejects_invalid_range) + { + CHECK_THROW((etl::clamped_value_range(7, 2)), etl::exception); + } + + //************************************************************************* + TEST(test_referenced_clamped_value_rejects_temporary_range) + { + typedef etl::clamped_value_range range_type; + typedef etl::referenced_clamped_value value_type; + + CHECK_FALSE((etl::is_constructible::value)); + CHECK_FALSE((etl::is_constructible::value)); + } + + #if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE + //************************************************************************* + TEST(test_referenced_floating_clamped_value) + { + const etl::clamped_value_range range(-1.5F, 2.5F); + etl::referenced_clamped_value value(range, 0.5F); + + value += 1.25F; + CHECK_CLOSE(1.75F, value.get(), 0.0001F); + value.advance(10.0F); + CHECK_CLOSE(2.5F, value.get(), 0.0001F); + } + + //************************************************************************* + TEST(test_referenced_floating_clamped_value_rejects_nan) + { + const float nan = etl::numeric_limits::quiet_NaN(); + + CHECK_THROW((etl::clamped_value_range(nan, 1.0F)), etl::exception); + + const etl::clamped_value_range range(-1.0F, 1.0F); + CHECK_THROW((etl::referenced_clamped_value(range, nan)), etl::exception); + + etl::referenced_clamped_value value(range, 0.0F); + CHECK_THROW(value = nan, etl::exception); + CHECK_THROW(value.advance(nan), etl::exception); + } + #endif +#endif } } // namespace From c313e6b98ca2d75b68970cb0b078cc2a5ebebc7c Mon Sep 17 00:00:00 2001 From: Sergei Date: Sun, 27 Sep 2026 11:42:34 +0300 Subject: [PATCH 07/13] Add docs and tutorial for etl::intrusive_avl_tree (#1608) * Add docs and tutorial for etl::intrusive_avl_tree Documents the intrusive AVL tree added in PR #1425: a full API reference page under Containers/Trees, and a tutorial demonstrating ordered access to intrusively-linked objects with unpredictable lifetimes, including move support and destructor-driven automatic removal from the tree. Co-Authored-By: Claude Sonnet 5 * Add complexity shortcode and document its usage Introduces a {{< complexity >}} Hugo shortcode (with light/dark styling for constant/logarithmic/linear/linearithmic/polynomial/exponential classes) for rendering Big-O complexity inline, applies it throughout the intrusive_avl_tree API reference in place of raw "O(...)" text, and documents the Description/Complexity structure and supported classes in DOCUMENTATION.md. Co-Authored-By: Claude Sonnet 5 * Fix issues found by @CodeRabbit * Put complexity notes on their own lines in intrusive_avl_tree docs Move the complexity shortcode from the inline "**Complexity:**" label to its own line under a "**Complexity**" label. Notes that give several alternatives (e.g. amortized vs. worst-case) now show each one on a separate line. Update the guideline in DOCUMENTATION.md to match. * Update complexity examples in DOCUMENTATION.md Add the hard line break after the "**Complexity**" label in the example, and rewrite the list of supported complexity classes in the new two-line format, including an example with several alternatives. * Add logarithmic complexity example to DOCUMENTATION.md --------- Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove --- DOCUMENTATION.md | 32 + docs/containers/trees/_index.md | 6 + docs/containers/trees/intrusive-avl-tree.md | 560 ++++++++++++++++++ docs/tutorials/_index.md | 5 + docs/tutorials/intrusive-avl-tree-tutorial.md | 210 +++++++ docs/utilities/intrusive-links.md | 2 + hugo/assets/css/custom.css | 76 +++ hugo/layouts/shortcodes/complexity.html | 3 + 8 files changed, 894 insertions(+) create mode 100644 docs/containers/trees/_index.md create mode 100644 docs/containers/trees/intrusive-avl-tree.md create mode 100644 docs/tutorials/intrusive-avl-tree-tutorial.md create mode 100644 hugo/layouts/shortcodes/complexity.html diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 2da43c6b..3b2c2ca6 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -120,6 +120,38 @@ The documentation is written in [Goldmark](https://github.com/teekennedy/goldmar Hugo uses [Goldmark](https://github.com/teekennedy/goldmark-markdown) as its default Markdown processor for versions 0.60.0 and newer. It is built into Hugo, providing high-performance and fully [CommonMark](https://commonmark.org/)-compliant rendering, along with support for [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) Markdown. +### Documenting Algorithmic Complexity + +When documenting the complexity of an operation, use the `complexity` shortcode instead of writing raw Big-O text. +Place it on its own `**Complexity**` lines, right after the `**Description**` line/paragraph, for example: + +```markdown +**Description** +Returns the number of linked items. + +**Complexity** +{{< complexity "constant" "1" >}} +``` + +The shortcode takes two arguments: a complexity class (used to color and label it) and the expression to show +inside `O(...)`. The supported classes are: + +```markdown +**Complexity** +{{< complexity "constant" "1" >}} +**Complexity** +{{< complexity "linear" "N" >}}, where N is number of items in the tree, and assuming comparator is {{< complexity "constant" "1" >}}. +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where N is number of items in the tree. +**Complexity** +{{< complexity "linearithmic" "N log N" >}}, where N is number of items in the tree. +**Complexity** +{{< complexity "polynomial" "N²" >}}, where N is number of items in the tree. +**Complexity** +{{< complexity "constant" "1" >}} when something, or +{{< complexity "exponential" "2ᴺ" >}} some alternative, where N is number of items in the tree. +``` + ### Starting a New Page A template file is provided at `docs/page-template.md` to use as a starting point. Copy it to the diff --git a/docs/containers/trees/_index.md b/docs/containers/trees/_index.md new file mode 100644 index 00000000..c7bd6cc9 --- /dev/null +++ b/docs/containers/trees/_index.md @@ -0,0 +1,6 @@ +--- +title: "Trees" +weight: 100 +--- + +Tree like containers. diff --git a/docs/containers/trees/intrusive-avl-tree.md b/docs/containers/trees/intrusive-avl-tree.md new file mode 100644 index 00000000..ce52af92 --- /dev/null +++ b/docs/containers/trees/intrusive-avl-tree.md @@ -0,0 +1,560 @@ +--- +title: "intrusive_avl_tree" +weight: 1 +--- + +{{< callout type="info">}} + Header: `intrusive_avl_tree.h` + Similar to: `std::set` / `std::map` (ordering only, no ownership) +{{< /callout >}} + +An intrusive, self-balancing AVL tree. + +```cpp +template +etl::intrusive_avl_tree +``` + +`TValue` is the type that contains the actual values. It must derive from +`etl::intrusive_avl_tree_base::link_type`, exposed as the tree's own +`link_type`. Unlike `etl::intrusive_list`/`etl::intrusive_forward_list`, +which are templated directly on a link type and expect the node to derive +from it publicly, `intrusive_avl_tree` is templated only on `TValue` and an +`ID_`. Its `link_type` is a deliberately opaque wrapper around +`etl::tree_link` that exposes almost nothing as public (or even +protected) API, by design, so that inheriting from it imposes as little +"intrusive"-ness as possible, avoids name conflicts, and hides implementation +details. + +As with the other intrusive containers, an object may inherit multiple +`link_type`s with different `ID_` values to be a member of several trees (or +lists) at once: + +```cpp +typedef etl::intrusive_avl_tree_base<0>::link_type FirstLink; +typedef etl::intrusive_avl_tree_base<1>::link_type SecondLink; + +struct Item : public FirstLink, public SecondLink +{ + int value; +}; + +etl::intrusive_avl_tree tree0; +etl::intrusive_avl_tree tree1; +``` + +See also: +[Intrusive Links]({{% relref "../../utilities/intrusive-links.md" %}}) +[intrusive_list]({{% relref "../lists/intrusive-list.md" %}}) +[intrusive_forward_list]({{% relref "../lists/intrusive-forward-list.md" %}}) + +There is no class-level comparator template parameter. Every ordering +operation instead takes its own comparator functor at the call site. +Lookup functions (`find`, `lower_bound`, `upper_bound`, `find_or_insert`) +take a unary comparator accepting a single `const value_type&` and returning +an `int`: `>0` if the search target is "greater" than the argument, `0` if +found, `<0` if "smaller". The range constructor takes a binary comparator +`int(const value_type& lhs, const value_type& rhs)`; `etl::compare::cmp` +can be used directly if `value_type` defines `<`. + +## Member types + +```cpp +link_type (nested, inherited from etl::intrusive_avl_tree_base) +value_type TValue +pointer value_type* +const_pointer const value_type* +reference value_type& +const_reference const value_type& +size_type size_t +rvalue_reference value_type&& // C++11 +``` + +## The link type + +`TValue` must derive from `link_type`. It has its own set of special member +functions that a node author relies on. + +```cpp +link_type() +``` +**Description** +Default constructor. Leaves the item unlinked. + +--- + +```cpp +link_type(link_type&& other) +``` +**Description** +C++11. Constructs a new item by moving `other` into `this`. After +construction, `this` replaces `other` in the same tree position that +`other` occupied, so no tree balancing is needed. `other` becomes unlinked. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +link_type& operator=(link_type&& other) +``` +**Description** +C++11. Assigns `other` by moving it into `this`. Does nothing on +self-assignment. After assignment, `this` replaces `other` in the same tree +position that `other` occupied; `other` becomes unlinked. `this` might end +up in a different tree than it started in. + +**Complexity** +{{< complexity "logarithmic" "log N" >}} if `this` is already linked to a tree (it has to be erased first, with rebalancing), or +{{< complexity "constant" "1" >}} if `this` is not linked, where `N` is number of items in the tree. + +--- + +```cpp +link_type(const link_type&) = delete; +link_type& operator=(const link_type&) = delete; +``` +**Description** +C++11. Copy construction and assignment are disabled. + +--- + +```cpp +~link_type() +``` +**Description** +Might rotate the tree as necessary to keep it balanced. The tree is not the +real owner of its nodes' memory - the node's actual owner may destroy it at +any time, with or without a prior explicit `erase`. If the link is still +linked to a tree when it's destroyed, the destructor unlinks it +automatically; otherwise the node's former parent and children would keep +dangling pointers to it, breaking the tree and leading to undefined +behaviour. This is what makes it safe to embed a tree node in an object +whose lifetime is managed elsewhere. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +## Constructors + +```cpp +etl::intrusive_avl_tree() +``` +**Description** +Default constructor. Constructs an empty tree. + +--- + +```cpp +template +etl::intrusive_avl_tree(TIterator first, TIterator last, TBinaryCompare binary_comp) +``` +**Description** +Constructs the tree from the range [`first`, `last`) of items, using +`binary_comp` to order them. All items in the range must be unlinked +initially; if any item is already linked to some other tree, throws +`etl::intrusive_avl_tree_value_is_already_linked`, and any items already +inserted are unlinked again - it's all or nothing. +If duplicates should be kept, `binary_comp` should return a non-zero result +even for "equal" items - `<0` prepends a duplicate, `>0` appends one. +Returning `0` for equal items keeps only the first of each run of duplicates +in the range; the rest are left unlinked. `etl::compare::cmp` +can be used directly as `binary_comp`. + +**Complexity** +{{< complexity "linearithmic" "N log N" >}}, where `N` is number of items in the range. + +--- + +```cpp +intrusive_avl_tree(intrusive_avl_tree&&) = default; +``` +**Description** +C++11. Move constructor. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +intrusive_avl_tree(const intrusive_avl_tree&) = delete; +``` +**Description** +C++11. Copy construction is disabled. + +--- + +```cpp +~intrusive_avl_tree() +``` +**Description** +Every remaining node is unlinked; none of the value objects are destroyed, +since the tree never owns their memory. + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +## Assignment + +```cpp +intrusive_avl_tree& operator=(intrusive_avl_tree&&) = default; +``` +**Description** +C++11. Move assignment. Does nothing on self-assignment. All of `this` +tree's former items have to be unlinked. + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is the size of `this` tree before assignment. + +--- + +```cpp +intrusive_avl_tree& operator=(const intrusive_avl_tree&) = delete; +``` +**Description** +C++11. Copy assignment is disabled. + +## Capacity + +```cpp +bool empty() const +``` +**Description** +Returns `true` if the tree has no linked items. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +size_t size() const +``` +**Description** +Returns the number of linked items. + +**Complexity** +{{< complexity "constant" "1" >}} + +## Iterators + +Only bidirectional `iterator`/`const_iterator` are provided; there is no +`reverse_iterator` (use `visit_in_order` with `is_reverse = true` instead). +An iterator may be in one of three logical states: it references a real +node (`has_value() == true`), it is the terminal sentinel (`it == end()`), +or it is "valueless" (`!has_value() && it != end()` - e.g. a +default-constructed iterator, or the result of `get_parent()`/`get_child()` +when there is no such node). + +```cpp +iterator begin() +const_iterator begin() const +const_iterator cbegin() const +``` +**Description** +Returns an iterator to the smallest item. Returns `end()` if the tree is +empty. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +iterator end() +const_iterator end() const +const_iterator cend() const +``` +**Description** +Returns an iterator to the terminal sentinel. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +iterator min() +const_iterator min() const +``` +**Description** +Equivalent to `begin()`. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +iterator max() +const_iterator max() const +``` +**Description** +Equivalent to `--end()`. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +iterator& operator++() +iterator operator++(int) +``` +**Description** +Advances the iterator to the next ("greater") item. + +**Complexity** +{{< complexity "constant" "1" >}} amortized, +{{< complexity "logarithmic" "log N" >}} worst-case, where `N` is number of items in the tree. + +--- + +```cpp +iterator& operator--() +iterator operator--(int) +``` +**Description** +Moves the iterator to the previous ("smaller") item. + +**Complexity** +{{< complexity "constant" "1" >}} amortized, +{{< complexity "logarithmic" "log N" >}} worst-case, where `N` is number of items in the tree. + +--- + +```cpp +reference operator*() const +pointer operator->() const +pointer get() const +``` +**Description** +Dereferences an iterator that has a value. `operator*`/`operator->` throw +`etl::intrusive_avl_tree_iterator_exception` if `has_value()` is `false`. +`get()` returns `nullptr` instead of throwing. + +--- + +```cpp +bool has_value() const +explicit operator bool() const +``` +**Description** +Returns `true` if the iterator references a real node. + +## Advanced traversal + +These are normally only needed for advanced traversal. + +```cpp +iterator get_root() +const_iterator get_root() const +``` +**Description** +Returns an iterator to the root node. Valueless (`has_value() == false`) if +the tree is empty. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +int_fast8_t get_balance_factor() const +``` +**Description** +Returns the node's AVL balance factor: `-1`, `0` or `+1`. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +iterator get_parent() const +``` +**Description** +Returns an iterator to the node's parent. Valueless if there is no parent. + +**Complexity** +{{< complexity "constant" "1" >}} + +--- + +```cpp +iterator get_child(bool is_right) const +``` +**Description** +Returns an iterator to the node's left (`is_right == false`) or right +(`is_right == true`) child. Valueless if there is no such child. + +**Complexity** +{{< complexity "constant" "1" >}} + +## Lookup + +```cpp +template +iterator find(TCompare comp) +template +const_iterator find(TCompare comp) const +``` +**Description** +Finds an item using the unary comparator `comp`. Returns `end()` if there +is no matching item. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree, +and assuming `comp` is {{< complexity "constant" "1" >}}. + +--- + +```cpp +template +iterator lower_bound(TCompare comp) +template +const_iterator lower_bound(TCompare comp) const +``` +**Description** +Returns an iterator to the first item that compares as "not less" than the +target of `comp`. Returns `end()` if no such item exists. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree, +and assuming `comp` is {{< complexity "constant" "1" >}}. + +--- + +```cpp +template +iterator upper_bound(TCompare comp) +template +const_iterator upper_bound(TCompare comp) const +``` +**Description** +Returns an iterator to the first item that compares as "greater" than the +target of `comp`. Returns `end()` if no such item exists. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree, +and assuming `comp` is {{< complexity "constant" "1" >}}. + +## Modifiers + +```cpp +template +etl::pair find_or_insert(TCompare comp, TFactory factory) +``` +**Description** +Finds an existing item using the unary comparator `comp`; if not found, +calls `factory()` (no arguments) and inserts the item it returns at the +position where the search stopped. This operation never invalidates +existing iterators, though depending on where the new item was linked, +existing iterators may skip over it. +`factory` must return the address of the new value (castable to +`link_type*`). If it returns `nullptr`, the tree is left unmodified and the +result iterator is valueless. If the returned value is already linked to +any tree, throws `etl::intrusive_avl_tree_value_is_already_linked`. +Returns a pair of: an iterator to the found or inserted item (never `end()`, +but may be valueless), and a `bool` that is `true` if the tree was modified. +If duplicates are wanted, `comp` should return a non-zero result even for +"equal" items - `+1` appends after existing duplicates, `-1` prepends +before them. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree, +and assuming `comp` and `factory` are {{< complexity "constant" "1" >}}. + +--- + +```cpp +iterator erase(iterator position) +iterator erase(const_iterator position) +``` +**Description** +Erases the item at `position`, including tree rebalancing. `position` must +reference a real item (asserts/throws +`etl::intrusive_avl_tree_iterator_exception` otherwise) and must originate +from this tree instance. Invalidates any existing iterator to the erased +item, but no others. Returns an iterator to the next node. +Use `clear()` instead if every item needs to be erased - no rebalancing is +involved. + +**Complexity** +{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +void clear() +``` +**Description** +Unlinks every item, leaving the tree empty. Invalidates all existing +iterators. This is cheaper than calling `erase` on every item, which would +cost {{< complexity "linearithmic" "N log N" >}} once intermediate +rebalancing is taken into account. + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +void swap(intrusive_avl_tree& other) +friend void swap(intrusive_avl_tree& lhs, intrusive_avl_tree& rhs) +``` +**Description** +Swaps the contents of two trees. Does nothing on self-swap. + +**Complexity** +{{< complexity "constant" "1" >}} + +## Traversal + +Each of these visits every item exactly once, without recursion, and has a +`const` overload. The visitor must not modify the tree during visitation. + +```cpp +template +void visit_in_order(bool is_reverse, Visitor visitor) +``` +**Description** +Visits every item in sorted order. `is_reverse` selects ascending (`false`) +or descending (`true`) order. + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +template +void visit_pre_order(bool is_reverse, Visitor visitor) +``` +**Description** +Visits every item in pre-order (parent before children). + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +--- + +```cpp +template +void visit_post_order(bool is_reverse, Visitor visitor) +``` +**Description** +Visits every item in post-order (children before parent). + +**Complexity** +{{< complexity "linear" "N" >}}, where `N` is number of items in the tree. + +## Exceptions + +```cpp +intrusive_avl_tree_exception // base exception +intrusive_avl_tree_iterator_exception // invalid iterator use +intrusive_avl_tree_value_is_already_linked // item already linked to a tree +``` diff --git a/docs/tutorials/_index.md b/docs/tutorials/_index.md index 43e6530f..d41ac916 100644 --- a/docs/tutorials/_index.md +++ b/docs/tutorials/_index.md @@ -13,6 +13,11 @@ A quick introduction to containers. --- +[Intrusive AVL Tree](./intrusive-avl-tree-tutorial) +Ordering objects that already exist, without a second container. + +--- + [observer](./observer-tutorial) A templated set of classes to allow easier implementation of the observer pattern. diff --git a/docs/tutorials/intrusive-avl-tree-tutorial.md b/docs/tutorials/intrusive-avl-tree-tutorial.md new file mode 100644 index 00000000..033ff380 --- /dev/null +++ b/docs/tutorials/intrusive-avl-tree-tutorial.md @@ -0,0 +1,210 @@ +--- +title: "Intrusive AVL Tree" +--- + +A tutorial on how `etl::intrusive_avl_tree` can give ordered, +{{< complexity "logarithmic" "log N" >}} access to objects that already +exist elsewhere, without a second container to track them. + +## Overview + +Imagine a set of driver/sensor tasks, each owned by its own subsystem. They +are created and destroyed at unpredictable times as modules attach and +detach, so there is no compile-time upper bound on how many exist at once. +A scheduler needs to visit the tasks that are currently due, in ascending +deadline order, on every tick. + +A normal fixed-capacity ordered container (`etl::set`/`etl::map`) would need +a second, separately-sized place to store pointers to these tasks. Instead, +embedding an `etl::intrusive_avl_tree` link directly into each task object +gives the scheduler ordered, {{< complexity "logarithmic" "log N" >}} +access to exactly the tasks that exist right now - nothing more is +allocated, and nothing needs to be kept in sync by hand. That's the payoff of "intrusive": the tree links the +objects that already exist, rather than storing copies of them. + +The example below also demonstrates two things that fall out of the design +for free: +- **Move support.** When the `std::vector` that owns the tasks reallocates, + every task is moved into new storage - and each move takes over its + task's exact tree position, with no rebalancing and no code of its own. +- **Safe removal without an explicit `erase()` call.** Removing a task's + owning object removes it from the schedule too, with no dangling entry + left behind - whether that's because the object is destroyed while still + linked (`link_type`'s destructor unlinks it automatically), or, as in + this example, because `std::vector::erase` move-assigns another task over + it (`link_type`'s move assignment unlinks `this` before taking over the + moved-from tree position). + +## Example + +```cpp +#include "etl/intrusive_avl_tree.h" + +#include +#include +#include +#include + +// A driver/sensor task that already exists as a long-lived object owned by +// its own subsystem. Inheriting from the tree's link_type is all it takes +// to make it schedulable - no separate fixed-capacity container is needed +// to track which tasks exist. +class SensorTask : public etl::intrusive_avl_tree_base<0>::link_type +{ +public: + SensorTask(uint32_t id_, uint32_t deadline_, uint32_t period_) + : id(id_), deadline(deadline_), period(period_) + { + } + + // Orders primarily by deadline; ties are broken by id so two tasks due + // on the same tick still occupy distinct tree positions. + struct Locate + { + uint32_t target_deadline; + uint32_t target_id; + + int operator()(const SensorTask& node) const + { + if (target_deadline != node.deadline) + { + return (target_deadline < node.deadline) ? -1 : 1; + } + if (target_id != node.id) + { + return (target_id < node.id) ? -1 : 1; + } + return 0; + } + }; + + uint32_t id; + uint32_t deadline; // next tick at which this task must be serviced + uint32_t period; +}; + +typedef etl::intrusive_avl_tree Schedule; + +// The only place a task gets linked - called whenever a module attaches, +// or whenever a task is rescheduled after being serviced. +void schedule_task(Schedule& schedule, SensorTask& task) +{ + schedule.find_or_insert(SensorTask::Locate{ task.deadline, task.id }, + [&task] { return &task; }); +} + +// Visits every currently-due task in ascending deadline order, services it, +// then reschedules it at its next deadline. +void service_due_tasks(Schedule& schedule, uint32_t now) +{ + for (auto it = schedule.begin(); (it != schedule.end()) && (it->deadline <= now); ) + { + SensorTask& task = *it; + it = schedule.erase(it); // O(log N), rebalances + + std::cout << "servicing task " << task.id << " (was due at " << task.deadline << ")\n"; + + task.deadline = now + task.period; + schedule_task(schedule, task); // reschedule at the new deadline + } +} + +void print_schedule(const Schedule& schedule) +{ + for (auto it = schedule.begin(); it != schedule.end(); ++it) + { + std::cout << " task " << it->id << " due at " << it->deadline << "\n"; + } +} + +int main() +{ + Schedule schedule; + + // Driver registry: a plain std::vector that a subsystem uses to own its + // tasks for whatever reason it already needs to - the tree does not + // require this vector to exist, it's just a stand-in for "wherever the + // real owner happens to keep the object". + std::vector driver_registry; + driver_registry.reserve(2); + + driver_registry.emplace_back(1, 100, 1000); + schedule_task(schedule, driver_registry.back()); + + driver_registry.emplace_back(2, 300, 1000); + schedule_task(schedule, driver_registry.back()); + + std::cout << "Initial schedule (ascending deadline):\n"; + print_schedule(schedule); + + std::cout << "\nschedule.size() before growth: " << schedule.size() << "\n"; + + // driver_registry.reserve()'d capacity was 2; this emplace_back forces a + // reallocation. std::vector move-constructs every existing SensorTask + // into the new storage. Each move steals its task's exact tree position + // (O(1), no rebalancing) - the tree needs no code of its own to survive + // the underlying storage moving. + driver_registry.emplace_back(3, 200, 1100); + schedule_task(schedule, driver_registry.back()); + + std::cout << "schedule.size() after growth: " << schedule.size() << "\n\n"; + + std::cout << "Schedule after growth (still correctly ordered):\n"; + print_schedule(schedule); + + std::cout << "\nServicing due tasks at tick 250:\n"; + service_due_tasks(schedule, 250); + + std::cout << "\nSchedule after servicing:\n"; + print_schedule(schedule); + + // Unplugging a module removes its SensorTask from driver_registry. Task 2 + // isn't the last element, so std::vector::erase() doesn't just destroy it + // in place: it move-assigns the following element (task 3) into task 2's + // slot, then destroys the now-duplicate last slot. That move-assignment + // is SensorTask's (implicitly-generated) one, which calls + // link_type::operator=(link_type&&): it first unlinks *this* - task 2's + // slot, still linked at deadline 300 - from the tree, then takes over + // task 3's tree position. The destructor that runs afterwards, on the + // vacated last slot, fires on an already-unlinked, moved-from object, so + // it has nothing left to do - no explicit schedule.erase() call is made + // here, and no dangling entry is left behind. + std::cout << "\nschedule.size() before unplugging task 2: " << schedule.size() << "\n"; + + auto pos = std::find_if(driver_registry.begin(), driver_registry.end(), + [](const SensorTask& t) { return t.id == 2; }); + driver_registry.erase(pos); // move-assigns task 3 over task 2, unlinking task 2 + + std::cout << "schedule.size() after unplugging task 2: " << schedule.size() << "\n"; + + return 0; +} +``` + +## Program output + +``` +Initial schedule (ascending deadline): + task 1 due at 100 + task 2 due at 300 + +schedule.size() before growth: 2 +schedule.size() after growth: 3 + +Schedule after growth (still correctly ordered): + task 1 due at 100 + task 3 due at 200 + task 2 due at 300 + +Servicing due tasks at tick 250: +servicing task 1 (was due at 100) +servicing task 3 (was due at 200) + +Schedule after servicing: + task 2 due at 300 + task 1 due at 1250 + task 3 due at 1350 + +schedule.size() before unplugging task 2: 3 +schedule.size() after unplugging task 2: 2 +``` diff --git a/docs/utilities/intrusive-links.md b/docs/utilities/intrusive-links.md index 6ee73e3f..ac16e511 100644 --- a/docs/utilities/intrusive-links.md +++ b/docs/utilities/intrusive-links.md @@ -297,6 +297,8 @@ template struct tree_link ``` +`etl::intrusive_avl_tree` is built on top of `tree_link` - see [intrusive_avl_tree]({{% relref "../containers/trees/intrusive-avl-tree.md" %}}). + ### Template parameters ```cpp const size_t ID_ diff --git a/hugo/assets/css/custom.css b/hugo/assets/css/custom.css index e23c0ebf..ee5b7e09 100644 --- a/hugo/assets/css/custom.css +++ b/hugo/assets/css/custom.css @@ -420,4 +420,80 @@ tr td code{ font-size: 0.8rem !important; } +.complexity { + padding: 3px 6px; + border-radius: 999px; + font-weight: 600; + letter-spacing: 0.04em; + border: 1px solid transparent; +} +html.dark .complexity { + &.constant { + color: #7CFFB2; + background: rgba(0,255,120,0.08); + border-color: rgba(0,255,120,0.25); + } + &.logarithmic { + color: #7CE7FF; + background: rgba(0,200,255,0.08); + border-color: rgba(0,200,255,0.18); + } + &.linear { + color: #6AB7FF; + background: rgba(80,140,255,0.08); + border-color: rgba(80,140,255,0.18); + } + &.linearithmic { + color: #B78CFF; + background: rgba(140,80,255,0.08); + border-color: rgba(140,80,255,0.18); + } + &.polynomial { + color: #FFD36A; + background: rgba(255,190,0,0.08); + border-color: rgba(255,190,0,0.18); + } + &.exponential { + color: #ff7d9c; + background: rgba(255,0,80,0.08); + border-color: rgba(255,0,80,0.2); + } +} +html:not(.dark) .complexity { + &.constant { + color: #15803D; + background: rgba(34,197,94,0.10); + border-color: rgba(34,197,94,0.22); + } + + &.logarithmic { + color: #0E7490; + background: rgba(6,182,212,0.10); + border-color: rgba(6,182,212,0.22); + } + + &.linear { + color: #2563EB; + background: rgba(59,130,246,0.10); + border-color: rgba(59,130,246,0.22); + } + + &.linearithmic { + color: #7C3AED; + background: rgba(139,92,246,0.10); + border-color: rgba(139,92,246,0.22); + } + + &.polynomial { + color: #CA8A04; + background: rgba(234,179,8,0.12); + border-color: rgba(234,179,8,0.24); + } + + &.exponential { + color: #DC2626; + background: rgba(239,68,68,0.10); + border-color: rgba(239,68,68,0.22); + } +} diff --git a/hugo/layouts/shortcodes/complexity.html b/hugo/layouts/shortcodes/complexity.html new file mode 100644 index 00000000..644440ab --- /dev/null +++ b/hugo/layouts/shortcodes/complexity.html @@ -0,0 +1,3 @@ + + 𝐎({{ .Get 1 }}) + From f0d9d681a87dceb7161407d20d9fddb33e3081dd Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Mon, 28 Sep 2026 10:38:08 +0100 Subject: [PATCH 08/13] Updated sponsors pages --- docs/business-and-financial-case-for-commercial-sponsorship.md | 2 +- docs/sponsors.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/business-and-financial-case-for-commercial-sponsorship.md b/docs/business-and-financial-case-for-commercial-sponsorship.md index aa7683f4..956bbea3 100644 --- a/docs/business-and-financial-case-for-commercial-sponsorship.md +++ b/docs/business-and-financial-case-for-commercial-sponsorship.md @@ -2,7 +2,7 @@ title: "Why CFOs should care about Open Source" description: "The business and financial case for commercially sponsoring the Embedded Template Library (ETL)." date: 2026-09-12 -weight: 3003 +weight: 2 --- If your engineering team is using the **Embedded Template Library (ETL)** in commercial products, your company is benefiting from a critical piece of software infrastructure. diff --git a/docs/sponsors.md b/docs/sponsors.md index 25d7fb00..77be5bbc 100644 --- a/docs/sponsors.md +++ b/docs/sponsors.md @@ -1,6 +1,6 @@ --- title: "Sponsors of the ETL" -weight: 3002 +weight: 3 --- A special thank you to all of our past and current sponsors. Your generosity directly funds the continuous development and maintenance of the ETL. From 202f997e666f2815fd21f9be26571423a8700d9f Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Mon, 28 Sep 2026 09:57:31 +0100 Subject: [PATCH 09/13] Refactor the documentation css to group styles for light and dark themes --- hugo/assets/css/custom.css | 679 ++++++++++++++++++++----------------- 1 file changed, 373 insertions(+), 306 deletions(-) diff --git a/hugo/assets/css/custom.css b/hugo/assets/css/custom.css index ee5b7e09..bbddd9ab 100644 --- a/hugo/assets/css/custom.css +++ b/hugo/assets/css/custom.css @@ -1,222 +1,6 @@ -/* Definitions for light mode */ -html:not(.dark) .highlight .chroma .c1 -{ - color: forestgreen !important; -} - -html:not(.dark) .highlight .chroma .cp, -html:not(.dark) .highlight .chroma .cpf { - color: mediumslateblue !important; -} - -html:not(.dark) h1 { - color: white !important; - width: 100%; - background-color: coral !important; - border: width 0.15rem solid rgb(163, 81, 51) !important; - padding: 0.5rem 0.5rem !important; -} - -html:not(.dark) h2 { - color: royalblue !important; - border-bottom: 3px solid black !important; -} - -html:not(.dark) h3, -html:not(.dark) h4 -{ - color: darkgoldenrod !important; - border-bottom: 2px solid darkgrey !important; -} - -html:not(.dark) p { - color: black !important; -} - -html:not(.dark) dl, -html:not(.dark) dl dt code, -html:not(.dark) dl dd { - color: black !important; - background-color: #eeeeee !important; -} - -html:not(.dark) code, -html:not(.dark) pre, -html:not(.dark) pre code { - color: black !important; - background-color: #eeeeee !important; -} - -html:not(.dark) hr { - background-color: #eeeeee !important; -} - -html:not(.dark) .hx\:font-extrabold { - font-family: Roboto, sans-serif !important; - font-size: 1.6rem !important; - color: royalblue !important; -} - -/* inactive */ -html:not(.dark) .hx\:text-gray-500 { - color: black !important; -} - -/* active */ -html:not(.dark) .hextra-sidebar-active-item { - color: black !important; -} - -html:not(.dark) .content p.label-text { - color: darkgoldenrod !important; -} - -/* Regular item hover */ -html:not(.dark) .hextra-sidebar-item a:hover, -html:not(.dark) .hextra-sidebar-item a:hover span { - background-color: rgb(250, 195, 175) !important; - color: black !important; -} - -/* Active item default + hover - must come AFTER regular hover */ -html:not(.dark) .hextra-sidebar-item[data-active="true"] a, -html:not(.dark) .hextra-sidebar-item[data-active="true"] a span, -html:not(.dark) .hextra-sidebar-item[data-active="true"] a:hover, -html:not(.dark) .hextra-sidebar-item[data-active="true"] a:hover span { - background-color: coral !important; - color: white !important; -} - - -/* Definitions for dark mode */ -html.dark .highlight .chroma .c1 -{ - color: lightgreen !important; -} - -html.dark .highlight .chroma .cp, -html.dark.highlight .chroma .cpf { - color: lightsteelblue !important; -} - -html.dark body { - background-color: #1e1e1e !important; -} - -html.dark main { - background-color: #252525 !important; -} - -html.dark .hx\:dark\:shadow-\[0_-12px_16px_\#111\] { - box-shadow: 0 -12px 16px #1e1e1e !important; -} - -html.dark .hx\:dark\:bg-dark { - background-color: #1e1e1e !important; - border-color: grey !important; -} - -html.dark h1 -{ - color: white !important; - width: 100%; - background-color: rgb(177, 87, 54) !important; - padding: 0.5rem 0.5rem !important; -} - -html.dark h2 { - color: #7099c7 !important; - border-bottom: 2px solid #c0c0c0 !important; -} - -html.dark h3, -html.dark h4 { - color: lightgreen !important; - border-bottom: 2px solid #505050 !important; -} - -html.dark p { - color: #c0c0c0 !important; -} - -html.dark p strong { - color: #c4c3a6; -} - -html.dark dl, -html.dark dl dt code, -html.dark dl dd { - color: #c0c0c0 !important; - background-color: #444444 !important; -} - -html.dark code, -html.dark pre, -html.dark pre code { - color: #c0c0c0 !important; - background-color: #444444 !important; -} - -html.dark hr { - background-color: #505050 !important; -} - -html.dark .hx\:font-extrabold { - font-family: Roboto, sans-serif !important; - font-size: 1.6rem !important; - color: #7099c7 !important; -} - -/* inactive */ -html.dark .hx\:text-gray-500 { - color: #c0c0c0 !important; -} - -/* active */ -html.dark .hextra-sidebar-active-item { - color: #c0c0c0 !important; -} - -html.dark .hextra-code-block .highlight .chroma span.p { - color: #c0c0c0; -} - -html.dark .content p.label-text { - color: salmon !important; -} - -/* Regular item hover */ -html.dark .hextra-sidebar-item a:hover, -html.dark .hextra-sidebar-item a:hover span { - background-color: rgb(250, 195, 175) !important; - color: black !important; -} - -/* Active item default + hover - must come AFTER regular hover */ -html.dark .hextra-sidebar-item[data-active="true"] a, -html.dark .hextra-sidebar-item[data-active="true"] a span, -html.dark .hextra-sidebar-item[data-active="true"] a:hover, -html.dark .hextra-sidebar-item[data-active="true"] a:hover span { - background-color: rgb(177, 87, 54) !important; - color: #e0e0e0 !important; -} - -/* Common definitions */ - -@font-face { - font-family: Roboto; - src: url('/fonts/roboto-v51-latin-regular.woff2') format('woff2'); - font-weight:500; - font-style: normal; -} - -@font-face { - font-family: 'Roboto Mono'; - src: url('/fonts/roboto-mono-v31-latin-500.woff2') format('woff2'); - font-weight: nornal; - font-style: normal; -} - +/**********************/ +/* .highlight .chroma */ +/**********************/ .highlight .chroma .k, .highlight .chroma .kc, .highlight .chroma .kd, @@ -233,64 +17,21 @@ html.dark .hextra-sidebar-item[data-active="true"] a:hover span { font-style: normal !important; } -.hx\:mb-0 { - display: block; - width: 100%; +html.dark .highlight .chroma .cp, +html.dark.highlight .chroma .cpf { + color: lightgreen !important; + color: lightsteelblue !important; } -.hextra-code-block { - margin-top: 0.5rem !important; - -} - -/* .highlight .chroma .c1 -{ +html:not(.dark) .highlight .chroma .cp, +html:not(.dark) .highlight .chroma .cpf { color: forestgreen !important; -} - -.highlight .chroma .cp, -.highlight .chroma .cpf { color: mediumslateblue !important; -} */ - -html.dark .hextra-code-block .highlight .chroma .cs, -html.dark .hextra-code-block .highlight .chroma .cp, -html.dark .hextra-code-block .highlight .chroma .cpf { - font-weight: normal; -} - -.badges { - display: flex; - flex-wrap: wrap; - gap: 4px; - justify-content: flex-start; - margin-top: 1rem; -} - -.badges p { - display: contents; - margin: 0; -} - -.badges div { - width: 100%; - display: flex; - flex-wrap: wrap; - gap: 4px; -} - -.content ul li { - font-family: Roboto, sans-serif !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 1.1rem !important; -} - -.err { - background-color: transparent !important; - color: inherit !important; } +/***********************/ +/* h1 */ +/***********************/ h1 { font-family: Roboto, sans-serif !important; font-style: i !important; @@ -301,6 +42,25 @@ h1 { border-radius: 0.5rem !important; } +html.dark h1 +{ + color: white !important; + width: 100%; + background-color: rgb(177, 87, 54) !important; + padding: 0.5rem 0.5rem !important; +} + +html:not(.dark) h1 { + color: white !important; + width: 100%; + background-color: coral !important; + border: width 0.15rem solid rgb(163, 81, 51) !important; + padding: 0.5rem 0.5rem !important; +} + +/***********************/ +/* h2 */ +/***********************/ h2 { font-family: Roboto, sans-serif !important; font-style: normal !important; @@ -310,6 +70,19 @@ h2 { margin-bottom: 0.5em !important; } +html.dark h2 { + color: #7099c7 !important; + border-bottom: 2px solid #c0c0c0 !important; +} + +html:not(.dark) h2 { + color: royalblue !important; + border-bottom: 3px solid black !important; +} + +/***********************/ +/* h3 */ +/***********************/ h3 { font-family: Roboto, sans-serif !important; font-style: normal !important; @@ -321,6 +94,20 @@ h3 { #display: inline-block; } +html.dark h3 { + color: lightgreen !important; + border-bottom: 2px solid #505050 !important; +} + +html:not(.dark) h3 +{ + color: darkgoldenrod !important; + border-bottom: 2px solid darkgrey !important; +} + +/***********************/ +/* h4 */ +/***********************/ h4 { font-family: Roboto, sans-serif !important; font-style: normal !important; @@ -331,6 +118,43 @@ h4 { display: inline-block; } +html.dark h4 { + color: lightgreen !important; + border-bottom: 2px solid #505050 !important; +} + +html:not(.dark) h4 +{ + color: darkgoldenrod !important; + border-bottom: 2px solid darkgrey !important; +} + +/***********************/ +/* p */ +/***********************/ +p { + font-family: Roboto, sans-serif !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 1.1rem !important; + margin-top: 0.5em !important; +} + +html.dark p { + color: #c0c0c0 !important; +} + +html.dark p strong { + color: #c4c3a6; +} + +html:not(.dark) p { + color: black !important; +} + +/***********************/ +/* dl */ +/***********************/ dl { font-family: 'Roboto Mono', monospace !important; font-style: normal !important; @@ -352,27 +176,23 @@ dl dd { font-size: 1rem !important; } -ol { - font-family: 'Roboto Mono', monospace !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 1rem !important; +html.dark dl, +html.dark dl dt code, +html.dark dl dd { + color: #c0c0c0 !important; + background-color: #444444 !important; } -ol li { - font-family: Roboto, sans-serif !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 1rem !important; -} - -ol li code { - font-family: 'Roboto Mono', monospace !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 1rem !important; +html:not(.dark) dl, +html:not(.dark) dl dt code, +html:not(.dark) dl dd { + color: black !important; + background-color: #eeeeee !important; } +/***********************/ +/* pre & code */ +/***********************/ code { font-family: 'Roboto Mono', monospace !important; font-weight: normal !important; @@ -381,15 +201,6 @@ code { border: none !important; } -pre code { - font-family: 'Roboto Mono', monospace !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 0.9rem !important; - margin-left: 0 !important; - border: none !important; -} - pre { font-family: 'Roboto Mono', monospace !important; font-size: 0.9rem !important; @@ -400,6 +211,32 @@ pre { border: none !important; } +pre code { + font-family: 'Roboto Mono', monospace !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 0.9rem !important; + margin-left: 0 !important; + border: none !important; +} + +html.dark code, +html.dark pre, +html.dark pre code { + color: #c0c0c0 !important; + background-color: #444444 !important; +} + +html:not(.dark) code, +html:not(.dark) pre, +html:not(.dark) pre code { + color: black !important; + background-color: #eeeeee !important; +} + +/***********************/ +/* hr */ +/***********************/ hr { height: 2px !important; border: none !important; @@ -407,23 +244,154 @@ hr { margin-bottom: 1em !important; } -p { +html.dark hr { + background-color: #505050 !important; +} + +html:not(.dark) hr { + background-color: #eeeeee !important; +} + +/***********************/ +/* body */ +/***********************/ +html.dark body { + background-color: #1e1e1e !important; +} + +/***********************/ +/* .hx\:font-extrabold */ +/***********************/ +html.dark .hx\:font-extrabold { font-family: Roboto, sans-serif !important; - font-style: normal !important; - font-weight: normal !important; - font-size: 1.1rem !important; - margin-top: 0.5em !important; + font-size: 1.6rem !important; + color: #7099c7 !important; } -tr td code{ - font-family: 'Roboto Mono', monospace !important; - font-size: 0.8rem !important; +html:not(.dark) .hx\:font-extrabold { + font-family: Roboto, sans-serif !important; + font-size: 1.6rem !important; + color: royalblue !important; } +/***********************/ +/* .hx\:text-gray-500 */ +/* inactive */ +/***********************/ +html.dark .hx\:text-gray-500 { + color: #c0c0c0 !important; +} + +html:not(.dark) .hx\:text-gray-500 { + color: black !important; +} + +/*******************************/ +/* .hextra-sidebar-active-item */ +/* active */ +/*******************************/ +html.dark .hextra-sidebar-active-item { + color: #c0c0c0 !important; +} + +html:not(.dark) .hextra-sidebar-active-item { + color: black !important; +} + +/*************************/ +/* .content p.label-text */ +/*************************/ +html.dark .content p.label-text { + color: salmon !important; +} + +html:not(.dark) .content p.label-text { + color: darkgoldenrod !important; +} + +/********************************/ +/* .hextra-sidebar-item a:hover */ +/********************************/ +html.dark .hextra-sidebar-item a:hover, +html.dark .hextra-sidebar-item a:hover span { + background-color: rgb(250, 195, 175) !important; + color: black !important; +} + +html:not(.dark) .hextra-sidebar-item a:hover, +html:not(.dark) .hextra-sidebar-item a:hover span { + background-color: rgb(250, 195, 175) !important; + color: black !important; +} + +/*********************************/ +/* .hextra-sidebar-item */ +/* Active item default + hover */ +/* Must come AFTER regular hover */ +/*********************************/ +html.dark .hextra-sidebar-item[data-active="true"] a, +html.dark .hextra-sidebar-item[data-active="true"] a span, +html.dark .hextra-sidebar-item[data-active="true"] a:hover, +html.dark .hextra-sidebar-item[data-active="true"] a:hover span { + background-color: rgb(177, 87, 54) !important; + color: #e0e0e0 !important; +} + +html:not(.dark) .hextra-sidebar-item[data-active="true"] a, +html:not(.dark) .hextra-sidebar-item[data-active="true"] a span, +html:not(.dark) .hextra-sidebar-item[data-active="true"] a:hover, +html:not(.dark) .hextra-sidebar-item[data-active="true"] a:hover span { + background-color: coral !important; + color: white !important; +} + +/***********************/ +/* main */ +/***********************/ +html.dark main { + background-color: #252525 !important; +} + +/***********************/ +/* .hx\:dark\:shadow */ +/***********************/ +html.dark .hx\:dark\:shadow-\[0_-12px_16px_\#111\] { + box-shadow: 0 -12px 16px #1e1e1e !important; +} + +/***********************/ +/* .hx\:dark\:bg-dark */ +/***********************/ +html.dark .hx\:dark\:bg-dark { + background-color: #1e1e1e !important; + border-color: grey !important; +} + +/***********************/ +/* hextra-code-block */ +/***********************/ +.hextra-code-block { + margin-top: 0.5rem !important; +} + +html.dark .hextra-code-block .highlight .chroma span.p { + color: #c0c0c0; +} + +html.dark .hextra-code-block .highlight .chroma .cs, +html.dark .hextra-code-block .highlight .chroma .cp, +html.dark .hextra-code-block .highlight .chroma .cpf { + font-weight: normal; +} + +/***********************/ +/* complexity */ +/***********************/ .complexity { padding: 3px 6px; - border-radius: 999px; + border-radius: 0.5rem !important; font-weight: 600; + font-size: 0.9rem !important; letter-spacing: 0.04em; border: 1px solid transparent; } @@ -460,6 +428,7 @@ html.dark .complexity { border-color: rgba(255,0,80,0.2); } } + html:not(.dark) .complexity { &.constant { color: #15803D; @@ -497,3 +466,101 @@ html:not(.dark) .complexity { border-color: rgba(239,68,68,0.22); } } + +/***********************/ +/* font-face */ +/***********************/ +@font-face { + font-family: Roboto; + src: url('/fonts/roboto-v51-latin-regular.woff2') format('woff2'); + font-weight:500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto Mono'; + src: url('/fonts/roboto-mono-v31-latin-500.woff2') format('woff2'); + font-weight: nornal; + font-style: normal; +} + +/***********************/ +/* .hx\:mb */ +/***********************/ +.hx\:mb-0 { + display: block; + width: 100%; +} + +/***********************/ +/* .badges */ +/***********************/ +.badges { + display: flex; + flex-wrap: wrap; + gap: 4px; + justify-content: flex-start; + margin-top: 1rem; +} + +.badges p { + display: contents; + margin: 0; +} + +.badges div { + width: 100%; + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +/***********************/ +/* .content */ +/***********************/ +.content ul li { + font-family: Roboto, sans-serif !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 1.1rem !important; +} + +/***********************/ +/* .err */ +/***********************/ +.err { + background-color: transparent !important; + color: inherit !important; +} + +/***********************/ +/* ol */ +/***********************/ +ol { + font-family: 'Roboto Mono', monospace !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 1rem !important; +} + +ol li { + font-family: Roboto, sans-serif !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 1rem !important; +} + +ol li code { + font-family: 'Roboto Mono', monospace !important; + font-style: normal !important; + font-weight: normal !important; + font-size: 1rem !important; +} + +/***********************/ +/* tr */ +/***********************/ +tr td code{ + font-family: 'Roboto Mono', monospace !important; + font-size: 0.8rem !important; +} From 3e6b486786752b406b23c5d60fa0fb366c5fa80d Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Tue, 29 Sep 2026 12:37:27 +0100 Subject: [PATCH 10/13] Fix Codacy badge display --- docs/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_index.md b/docs/_index.md index 472fb0ac..dc6fde75 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -40,7 +40,7 @@ type: hextra-home
coverage - Codacy +Codacy Badge
From 7e328341ae83c301318d2f7c0ba785d2d63593ef Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 30 Sep 2026 09:26:46 +0200 Subject: [PATCH 11/13] Add be_uint24_t etc. to unaligned_type.h (#1592) * Add be_uint24_t etc. to unaligned_type.h A typical use case is to endian convert 24, 40, 48 bits etc. This change generalizes the pre-existing types in unaligned_type.h to those odd sizes. * Add documentation for odd sized unaligned_type specializations --- docs/types/unaligned-type.md | 314 +++++++++ include/etl/unaligned_type.h | 1061 +++++++++++++++++++++++++++++- test/test_unaligned_type.cpp | 202 ++++++ test/test_unaligned_type_ext.cpp | 244 +++++++ 4 files changed, 1817 insertions(+), 4 deletions(-) diff --git a/docs/types/unaligned-type.md b/docs/types/unaligned-type.md index 28c9f85f..8ae1378f 100644 --- a/docs/types/unaligned-type.md +++ b/docs/types/unaligned-type.md @@ -459,3 +459,317 @@ ETL_CONSTEXPR14 bool operator !=(T lhs, const unaligned_type& rhs) **Description** Inequality operator. Removed: `20.39.3` + +--- + +# Arbitrary width integers + +{{< callout type="info">}} + From: `20.50.0` +{{< /callout >}} + +`etl::unaligned_type` is templated on a fundamental type, so it can only store +integers whose width is that of a standard integer type. For protocols that use +odd widths, such as 24 or 48 bit integers, the following types are templated on +the **number of bytes of storage** instead. + +```cpp +template +class unaligned_uint_type + +template +class unaligned_int_type +``` + +Where `Size_` is the number of bytes of storage and `Endian_` is +`etl::endian::big` or `etl::endian::little`. + +`unaligned_uint_type` stores an unsigned integer. +`unaligned_int_type` stores a signed, two's complement integer, which is sign +extended when read. + +The value is interfaced to via `value_type`: the smallest standard integer type +that can hold `Size_` bytes. For example, `unaligned_uint_type<3U, ...>` has a +`value_type` of `uint32_t`. + +Values that do not fit in `Size_` bytes are truncated, in the same way as a +narrowing `static_cast`. + +Marked as `packed` for MSVC, GCC and Clang. + +## Constants +`int Endian` + The endianness of the type. +`size_t Size` + The size, in bytes, of the type. +`value_type Min_Value` + The lowest value that may be stored. +`value_type Max_Value` + The highest value that may be stored. + +## Predefined types + +The 40, 48 and 56 bit types are only defined if `ETL_USING_64BIT_TYPES`. +The host order types are only defined if `ETL_HAS_CONSTEXPR_ENDIANNESS`. + +### Little Endian +```cpp +le_uint24_t +le_int24_t +le_uint40_t ETL_USING_64BIT_TYPES +le_int40_t ETL_USING_64BIT_TYPES +le_uint48_t ETL_USING_64BIT_TYPES +le_int48_t ETL_USING_64BIT_TYPES +le_uint56_t ETL_USING_64BIT_TYPES +le_int56_t ETL_USING_64BIT_TYPES +``` + +### Big Endian +```cpp +be_uint24_t +be_int24_t +be_uint40_t ETL_USING_64BIT_TYPES +be_int40_t ETL_USING_64BIT_TYPES +be_uint48_t ETL_USING_64BIT_TYPES +be_int48_t ETL_USING_64BIT_TYPES +be_uint56_t ETL_USING_64BIT_TYPES +be_int56_t ETL_USING_64BIT_TYPES +``` + +### Network Order +Synonyms for Big Endian. +```cpp +using net_uint24_t = be_uint24_t +using net_int24_t = be_int24_t +using net_uint40_t = be_uint40_t +using net_int40_t = be_int40_t +using net_uint48_t = be_uint48_t +using net_int48_t = be_int48_t +using net_uint56_t = be_uint56_t +using net_int56_t = be_int56_t +``` + +### Host order +```cpp +host_uint24_t +host_int24_t +host_uint40_t +host_int40_t +host_uint48_t +host_int48_t +host_uint56_t +host_int56_t +``` + +## Constructors + +```cpp +unaligned_uint_type() +unaligned_int_type() +``` +**Description** +Constructs an uninitialised object. + +--- + +```cpp +ETL_CONSTEXPR14 unaligned_uint_type(value_type value) +ETL_CONSTEXPR14 unaligned_int_type(value_type value) +``` +**Description** +Constructs with the supplied value. +Values that do not fit in `Size` bytes are truncated. + +--- + +```cpp +unaligned_uint_type(const void* address) +unaligned_int_type(const void* address) +``` +**Description** +Constructs from the `Size` bytes at the address. + +--- + +```cpp +unaligned_uint_type(const void* address, size_t buffer_size) +unaligned_int_type(const void* address, size_t buffer_size) +``` +**Description** +Constructs from the `Size` bytes at the address. +`buffer_size` must be greater than or equal to `Size`. This is a precondition, +checked by `ETL_ASSERT`. If checks are disabled then a smaller buffer size +results in a read beyond the end of the buffer. + +--- + +```cpp +ETL_CONSTEXPR14 unaligned_uint_type(const unsigned char* address) +ETL_CONSTEXPR14 unaligned_int_type(const unsigned char* address) +``` +**Description** +Constructs from the `Size` bytes at the address. +Unlike the `const void*` overload, this requires no `reinterpret_cast` and so +may be used in a constexpr context. + +--- + +```cpp +ETL_CONSTEXPR14 unaligned_uint_type(const unsigned char* address, size_t buffer_size) +ETL_CONSTEXPR14 unaligned_int_type(const unsigned char* address, size_t buffer_size) +``` +**Description** +Constructs from the `Size` bytes at the address. +`buffer_size` must be greater than or equal to `Size`. This is a precondition, +checked by `ETL_ASSERT`. + +--- + +```cpp +template +unaligned_uint_type(const unaligned_uint_type& other) +``` +**Description** +Constructs from another type of the same size. +The endianness is converted, if necessary. + +## Member functions + +The storage interface (`data()`, `size()`, the iterators and `operator[]`) is +the same as that of `etl::unaligned_type`. + +```cpp +ETL_CONSTEXPR14 unaligned_uint_type& operator =(value_type value) +``` +**Description** +Assignment operator. + +--- + +```cpp +ETL_CONSTEXPR14 operator value_type() const +``` +**Description** +Conversion operator. +For `unaligned_int_type` the stored value is sign extended. + +--- + +```cpp +ETL_CONSTEXPR14 value_type value() const +``` +**Description** +Gets the value. +For `unaligned_int_type` the stored value is sign extended. + +--- + +```cpp +static ETL_CONSTEXPR14 value_type value_from(const_pointer address) +``` +**Description** +Gets the value of the `Size` bytes at the address. +Unlike construction from an address, the bytes are not copied to storage first. + +--- + +```cpp +template +ETL_CONSTEXPR14 unaligned_uint_type& operator =(const unaligned_uint_type& other) +``` +**Description** +Assignment operator from other endianness. + +--- + +# Arbitrary width integers with an external buffer + +{{< callout type="info">}} + From: `20.50.0` +{{< /callout >}} + +```cpp +template +class unaligned_uint_type_ext + +template +class unaligned_int_type_ext +``` + +These are the equivalents of `unaligned_uint_type` and `unaligned_int_type` +that do not own their storage, but reference an externally supplied buffer. +They have the same `value_type`, constants and value interface. + +The default constructor is deleted: the storage must always be supplied. + +### Predefined types +As for the internal storage versions, but with an `_ext_t` suffix. +```cpp +le_uint24_ext_t be_uint24_ext_t net_uint24_ext_t host_uint24_ext_t +le_int24_ext_t be_int24_ext_t net_int24_ext_t host_int24_ext_t +le_uint40_ext_t be_uint40_ext_t net_uint40_ext_t host_uint40_ext_t +le_int40_ext_t be_int40_ext_t net_int40_ext_t host_int40_ext_t +le_uint48_ext_t be_uint48_ext_t net_uint48_ext_t host_uint48_ext_t +le_int48_ext_t be_int48_ext_t net_int48_ext_t host_int48_ext_t +le_uint56_ext_t be_uint56_ext_t net_uint56_ext_t host_uint56_ext_t +le_int56_ext_t be_int56_ext_t net_int56_ext_t host_int56_ext_t +``` + +## Constructors + +```cpp +unaligned_uint_type_ext() ETL_DELETE +``` +**Description** +Deleted. The storage must always be supplied. + +--- + +```cpp +unaligned_uint_type_ext(pointer storage) +``` +**Description** +Constructs, referencing the supplied storage. +The storage is not modified. + +--- + +```cpp +unaligned_uint_type_ext(value_type value, pointer storage) +``` +**Description** +Constructs, referencing the supplied storage, and writes the value to it. + +--- + +```cpp +template +unaligned_uint_type_ext(const unaligned_uint_type_ext& other, pointer storage) +``` +**Description** +Constructs, referencing the supplied storage, and copies the value of `other` +to it, converting the endianness if necessary. +**Note** +The storage of `other` must either be the same storage as that supplied, or +completely separate. Partially overlapping storage is undefined behaviour. + +## Member functions + +```cpp +void set_storage(pointer storage) +``` +**Description** +Sets the storage for the type. +The storage is not modified. + +--- + +```cpp +template +unaligned_uint_type_ext& operator =(const unaligned_uint_type_ext& other) +``` +**Description** +Assignment operator from other endianness. +**Note** +The storage of `other` must either be the same storage as this object's, or +completely separate. Partially overlapping storage is undefined behaviour. diff --git a/include/etl/unaligned_type.h b/include/etl/unaligned_type.h index 6d8ef74c..34c945b0 100644 --- a/include/etl/unaligned_type.h +++ b/include/etl/unaligned_type.h @@ -43,6 +43,7 @@ SOFTWARE. #include "endianness.h" #include "exception.h" #include "file_error_numbers.h" +#include "integral_limits.h" #include "iterator.h" #include "type_traits.h" @@ -463,12 +464,40 @@ namespace etl value = value_from(store); } + //******************************* + /// \note 'src' and 'dst' must either be equal or refer to completely + /// separate storage. Partially overlapping storage is undefined + /// behaviour. //******************************* static ETL_CONSTEXPR14 void copy_store_to_store(const_pointer src, int endian_src, pointer dst) { - for (size_t i = 0UL; i < Size_; ++i) + if (Endian_ == endian_src) { - dst[i] = (Endian_ == endian_src) ? src[i] : src[Size_ - 1U - i]; + if (src != dst) + { + for (size_t i = 0UL; i < Size_; ++i) + { + dst[i] = src[i]; + } + } + } + else + { + // Swap mirrored bytes in pairs, so that the reversal is correct even + // when 'src' and 'dst' are the same storage. + for (size_t i = 0UL; i < (Size_ / 2UL); ++i) + { + const storage_type low = src[i]; + const storage_type high = src[Size_ - 1UL - i]; + + dst[i] = high; + dst[Size_ - 1UL - i] = low; + } + + if ((Size_ % 2UL) != 0UL) + { + dst[Size_ / 2UL] = src[Size_ / 2UL]; + } } } }; @@ -602,12 +631,39 @@ namespace etl // This is pure byte manipulation (copy + optional reversal), with no // floating point arithmetic involved, so it is always constexpr-capable // (C++14 and later), regardless of bit_cast/builtin availability. + /// \note 'src' and 'dst' must either be equal or refer to completely + /// separate storage. Partially overlapping storage is undefined + /// behaviour. //******************************* static ETL_CONSTEXPR14 void copy_store_to_store(const_pointer src, int endian_src, pointer dst) { - for (size_t i = 0UL; i < Size_; ++i) + if (Endian_ == endian_src) { - dst[i] = (Endian_ == endian_src) ? src[i] : src[Size_ - 1U - i]; + if (src != dst) + { + for (size_t i = 0UL; i < Size_; ++i) + { + dst[i] = src[i]; + } + } + } + else + { + // Swap mirrored bytes in pairs, so that the reversal is correct even + // when 'src' and 'dst' are the same storage. + for (size_t i = 0UL; i < (Size_ / 2UL); ++i) + { + const storage_type low = src[i]; + const storage_type high = src[Size_ - 1UL - i]; + + dst[i] = high; + dst[Size_ - 1UL - i] = low; + } + + if ((Size_ % 2UL) != 0UL) + { + dst[Size_ / 2UL] = src[Size_ / 2UL]; + } } } }; @@ -787,6 +843,475 @@ namespace etl template ETL_CONSTANT size_t unaligned_type::Size; + namespace private_unaligned_type + { + //************************************************************************* + /// The unsigned integer type used to interface to an unaligned unsigned + /// integer of 'Size' bytes. + /// The smallest standard unsigned integer type that can hold all of the + /// bytes is selected. + //************************************************************************* + template + struct interface_uint; + + template <> + struct interface_uint<1U> + { + typedef uint8_t type; + }; + + template <> + struct interface_uint<2U> + { + typedef uint16_t type; + }; + + template <> + struct interface_uint<3U> + { + typedef uint32_t type; + }; + + template <> + struct interface_uint<4U> + { + typedef uint32_t type; + }; + +#if ETL_USING_64BIT_TYPES + template <> + struct interface_uint<5U> + { + typedef uint64_t type; + }; + + template <> + struct interface_uint<6U> + { + typedef uint64_t type; + }; + + template <> + struct interface_uint<7U> + { + typedef uint64_t type; + }; + + template <> + struct interface_uint<8U> + { + typedef uint64_t type; + }; +#endif + + //************************************************************************* + /// The signed integer type used to interface to an unaligned signed + /// integer of 'Size' bytes. + /// The smallest standard signed integer type that can hold all of the + /// bytes is selected. + //************************************************************************* + template + struct interface_int; + + template <> + struct interface_int<1U> + { + typedef int8_t type; + }; + + template <> + struct interface_int<2U> + { + typedef int16_t type; + }; + + template <> + struct interface_int<3U> + { + typedef int32_t type; + }; + + template <> + struct interface_int<4U> + { + typedef int32_t type; + }; + +#if ETL_USING_64BIT_TYPES + template <> + struct interface_int<5U> + { + typedef int64_t type; + }; + + template <> + struct interface_int<6U> + { + typedef int64_t type; + }; + + template <> + struct interface_int<7U> + { + typedef int64_t type; + }; + + template <> + struct interface_int<8U> + { + typedef int64_t type; + }; +#endif + + //************************************************************************* + /// Sign extends the low 'Size_ * 8' bits of an unsigned value into the + /// full width of the signed interface type. + /// Uses only well defined unsigned arithmetic, so it is usable in a + /// constexpr context. + //************************************************************************* + template + ETL_CONSTEXPR TSigned sign_extend(TUnsigned uvalue) + { + return static_cast(static_cast(static_cast(uvalue ^ (static_cast(1) << ((Size_ * 8U) - 1U))) + - (static_cast(1) << ((Size_ * 8U) - 1U)))); + } + } // namespace private_unaligned_type + + //************************************************************************* + /// unaligned_uint_type + ///\brief Allows an unsigned integer of an arbitrary number of bytes to be + /// stored at an unaligned address. + /// Useful for types that have no native equivalent, such as a 24 bit + /// unsigned integer. + /// The value is interfaced to via the smallest standard unsigned + /// integer type that can hold it. + ///\tparam Size_ The number of bytes of storage. + ///\tparam Endian_ The endianness of the stored integer. + ///\note Values that do not fit in 'Size_' bytes are truncated, in the same + /// way as a narrowing static_cast. + //************************************************************************* + template + ETL_PACKED_CLASS(unaligned_uint_type) + : public private_unaligned_type::unaligned_type_storage + { + public: + + typedef typename private_unaligned_type::interface_uint::type value_type; + + typedef private_unaligned_type::unaligned_copy unaligned_copy; + + typedef typename private_unaligned_type::unaligned_type_storage::storage_type storage_type; + typedef typename private_unaligned_type::unaligned_type_storage::pointer pointer; + typedef typename private_unaligned_type::unaligned_type_storage::const_pointer const_pointer; + typedef typename private_unaligned_type::unaligned_type_storage::iterator iterator; + typedef typename private_unaligned_type::unaligned_type_storage::const_iterator const_iterator; + typedef typename private_unaligned_type::unaligned_type_storage::reverse_iterator reverse_iterator; + typedef typename private_unaligned_type::unaligned_type_storage::const_reverse_iterator const_reverse_iterator; + + static ETL_CONSTANT int Endian = Endian_; + static ETL_CONSTANT size_t Size = Size_; + static ETL_CONSTANT value_type Min_Value = static_cast(0); + static ETL_CONSTANT value_type Max_Value = static_cast(etl::integral_limits::max >> ((sizeof(value_type) - Size_) * 8U)); + + //************************************************************************* + /// Default constructor + //************************************************************************* + unaligned_uint_type() {} + + //************************************************************************* + /// Construct from a value. + //************************************************************************* + ETL_CONSTEXPR14 unaligned_uint_type(value_type value) + { + unaligned_copy::copy_value_to_store(value, this->storage); + } + + //************************************************************************* + /// Construct from an address. + //************************************************************************* + unaligned_uint_type(const void* address) + { + etl::copy_n(reinterpret_cast(address), Size_, this->storage); + } + + //************************************************************************* + /// Construct from an address and buffer size. + /// \note 'buffer_size' must be greater than or equal to 'Size_'. + //************************************************************************* + unaligned_uint_type(const void* address, size_t buffer_size) + { + ETL_ASSERT(Size_ <= buffer_size, ETL_ERROR(etl::unaligned_type_buffer_size)); + + etl::copy_n(reinterpret_cast(address), Size_, this->storage); + } + + //************************************************************************* + /// Construct from a byte buffer. + /// Usable in a constexpr context (C++14 and later). + //************************************************************************* + ETL_CONSTEXPR14 unaligned_uint_type(const unsigned char* address) + { + etl::copy_n(address, Size_, this->storage); + } + + //************************************************************************* + /// Construct from a byte buffer and buffer size. + /// \note 'buffer_size' must be greater than or equal to 'Size_'. + //************************************************************************* + ETL_CONSTEXPR14 unaligned_uint_type(const unsigned char* address, size_t buffer_size) + { + ETL_ASSERT(Size_ <= buffer_size, ETL_ERROR(etl::unaligned_type_buffer_size)); + + etl::copy_n(address, Size_, this->storage); + } + + //************************************************************************* + /// Copy constructor + //************************************************************************* + ETL_CONSTEXPR14 unaligned_uint_type(const unaligned_uint_type& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian, this->storage); + } + + //************************************************************************* + /// Copy constructor from other endianness. + //************************************************************************* + template + ETL_CONSTEXPR14 unaligned_uint_type(const unaligned_uint_type& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); + } + + //************************************************************************* + /// Assignment operator + //************************************************************************* + ETL_CONSTEXPR14 unaligned_uint_type& operator=(value_type value) + { + unaligned_copy::copy_value_to_store(value, this->storage); + + return *this; + } + + //************************************************************************* + /// Assignment operator. + //************************************************************************* + ETL_CONSTEXPR14 unaligned_uint_type& operator=(const unaligned_uint_type& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_, this->storage); + + return *this; + } + + //************************************************************************* + /// Assignment operator from other endianness. + //************************************************************************* + template + ETL_CONSTEXPR14 unaligned_uint_type& operator=(const unaligned_uint_type& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); + + return *this; + } + + //************************************************************************* + /// Conversion operator + //************************************************************************* + ETL_CONSTEXPR14 operator value_type() const + { + value_type value = value_type(); + + unaligned_copy::copy_store_to_value(this->storage, value); + + return value; + } + + //************************************************************************* + /// Get the value. + //************************************************************************* + ETL_CONSTEXPR14 value_type value() const + { + value_type value = value_type(); + + unaligned_copy::copy_store_to_value(this->storage, value); + + return value; + } + }; + ETL_END_PACKED + + template + ETL_CONSTANT int unaligned_uint_type::Endian; + + template + ETL_CONSTANT size_t unaligned_uint_type::Size; + + template + ETL_CONSTANT typename unaligned_uint_type::value_type unaligned_uint_type::Min_Value; + + template + ETL_CONSTANT typename unaligned_uint_type::value_type unaligned_uint_type::Max_Value; + + //************************************************************************* + /// unaligned_int_type + ///\brief Allows a signed, two's complement, integer of an arbitrary number + /// of bytes to be stored at an unaligned address. + /// Useful for types that have no native equivalent, such as a 24 bit + /// signed integer. + /// The value is interfaced to via the smallest standard signed + /// integer type that can hold it, and is sign extended on reading. + ///\tparam Size_ The number of bytes of storage. + ///\tparam Endian_ The endianness of the stored integer. + ///\note Values that do not fit in 'Size_' bytes are truncated, in the same + /// way as a narrowing static_cast. + //************************************************************************* + template + ETL_PACKED_CLASS(unaligned_int_type) + : public private_unaligned_type::unaligned_type_storage + { + public: + + typedef typename private_unaligned_type::interface_int::type value_type; + + typedef private_unaligned_type::unaligned_copy unaligned_copy; + + typedef typename private_unaligned_type::unaligned_type_storage::storage_type storage_type; + typedef typename private_unaligned_type::unaligned_type_storage::pointer pointer; + typedef typename private_unaligned_type::unaligned_type_storage::const_pointer const_pointer; + typedef typename private_unaligned_type::unaligned_type_storage::iterator iterator; + typedef typename private_unaligned_type::unaligned_type_storage::const_iterator const_iterator; + typedef typename private_unaligned_type::unaligned_type_storage::reverse_iterator reverse_iterator; + typedef typename private_unaligned_type::unaligned_type_storage::const_reverse_iterator const_reverse_iterator; + + static ETL_CONSTANT int Endian = Endian_; + static ETL_CONSTANT size_t Size = Size_; + static ETL_CONSTANT value_type Max_Value = + static_cast(etl::integral_limits::type>::max >> (((sizeof(value_type) - Size_) * 8U) + 1U)); + static ETL_CONSTANT value_type Min_Value = static_cast(-Max_Value - 1); + + //************************************************************************* + /// Default constructor + //************************************************************************* + unaligned_int_type() {} + + //************************************************************************* + /// Construct from a value. + //************************************************************************* + ETL_CONSTEXPR14 unaligned_int_type(value_type value) + { + unaligned_copy::copy_value_to_store(value, this->storage); + } + + //************************************************************************* + /// Construct from an address. + //************************************************************************* + unaligned_int_type(const void* address) + { + etl::copy_n(reinterpret_cast(address), Size_, this->storage); + } + + //************************************************************************* + /// Construct from an address and buffer size. + /// \note 'buffer_size' must be greater than or equal to 'Size_'. + //************************************************************************* + unaligned_int_type(const void* address, size_t buffer_size) + { + ETL_ASSERT(Size_ <= buffer_size, ETL_ERROR(etl::unaligned_type_buffer_size)); + + etl::copy_n(reinterpret_cast(address), Size_, this->storage); + } + + //************************************************************************* + /// Construct from a byte buffer. + /// Usable in a constexpr context (C++14 and later). + //************************************************************************* + ETL_CONSTEXPR14 unaligned_int_type(const unsigned char* address) + { + etl::copy_n(address, Size_, this->storage); + } + + //************************************************************************* + /// Construct from a byte buffer and buffer size. + /// \note 'buffer_size' must be greater than or equal to 'Size_'. + //************************************************************************* + ETL_CONSTEXPR14 unaligned_int_type(const unsigned char* address, size_t buffer_size) + { + ETL_ASSERT(Size_ <= buffer_size, ETL_ERROR(etl::unaligned_type_buffer_size)); + + etl::copy_n(address, Size_, this->storage); + } + + //************************************************************************* + /// Copy constructor + //************************************************************************* + ETL_CONSTEXPR14 unaligned_int_type(const unaligned_int_type& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian, this->storage); + } + + //************************************************************************* + /// Copy constructor from other endianness. + //************************************************************************* + template + ETL_CONSTEXPR14 unaligned_int_type(const unaligned_int_type& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); + } + + //************************************************************************* + /// Assignment operator + //************************************************************************* + ETL_CONSTEXPR14 unaligned_int_type& operator=(value_type value) + { + unaligned_copy::copy_value_to_store(value, this->storage); + + return *this; + } + + //************************************************************************* + /// Assignment operator. + //************************************************************************* + ETL_CONSTEXPR14 unaligned_int_type& operator=(const unaligned_int_type& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_, this->storage); + + return *this; + } + + //************************************************************************* + /// Assignment operator from other endianness. + //************************************************************************* + template + ETL_CONSTEXPR14 unaligned_int_type& operator=(const unaligned_int_type& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); + + return *this; + } + + //************************************************************************* + /// Conversion operator + //************************************************************************* + ETL_CONSTEXPR14 operator value_type() const + { + return value(); + } + + //************************************************************************* + /// Get the value. + //************************************************************************* + ETL_CONSTEXPR14 value_type value() const + { + typedef typename etl::unsigned_type::type unsigned_t; + + unsigned_t uvalue = unsigned_t(0); + + unaligned_copy::copy_store_to_value(this->storage, uvalue); + + return private_unaligned_type::sign_extend(uvalue); + } + }; + ETL_END_PACKED + //************************************************************************* /// unaligned_type_ext ///\brief Allows an arithmetic type to be stored at an unaligned address. @@ -839,6 +1364,9 @@ namespace etl //************************************************************************* /// Copy constructor with storage pointer + /// \note 'storage_' must either be the same storage as 'other's, or refer + /// to completely separate storage. Partially overlapping storage is + /// undefined behaviour. //************************************************************************* template unaligned_type_ext(const unaligned_type_ext& other, pointer storage_) @@ -897,6 +1425,9 @@ namespace etl //************************************************************************* /// Copy assignment operator from other endianness. + /// \note The storage of 'other' must either be the same storage as this + /// object's, or completely separate. Partially overlapping storage is + /// undefined behaviour. //************************************************************************* template unaligned_type_ext& operator=(const unaligned_type_ext& other) @@ -984,6 +1515,528 @@ namespace etl template ETL_CONSTANT size_t unaligned_type_ext::Size; + //************************************************************************* + /// unaligned_uint_type_ext + ///\brief Allows an unsigned integer of an arbitrary number of bytes to be + /// stored at an unaligned address. Uses an external buffer. + /// The value is interfaced to via the smallest standard unsigned + /// integer type that can hold it. + ///\tparam Size_ The number of bytes of storage. + ///\tparam Endian_ The endianness of the stored integer. + ///\note Values that do not fit in 'Size_' bytes are truncated, in the same + /// way as a narrowing static_cast. + //************************************************************************* + template + ETL_PACKED_CLASS(unaligned_uint_type_ext) + : public private_unaligned_type::unaligned_type_storage_ext + { + public: + + template + friend class unaligned_uint_type_ext; + + typedef typename private_unaligned_type::interface_uint::type value_type; + + typedef private_unaligned_type::unaligned_copy unaligned_copy; + + typedef typename private_unaligned_type::unaligned_type_storage_ext::storage_type storage_type; + typedef typename private_unaligned_type::unaligned_type_storage_ext::pointer pointer; + typedef typename private_unaligned_type::unaligned_type_storage_ext::const_pointer const_pointer; + typedef typename private_unaligned_type::unaligned_type_storage_ext::iterator iterator; + typedef typename private_unaligned_type::unaligned_type_storage_ext::const_iterator const_iterator; + typedef typename private_unaligned_type::unaligned_type_storage_ext::reverse_iterator reverse_iterator; + typedef typename private_unaligned_type::unaligned_type_storage_ext::const_reverse_iterator const_reverse_iterator; + + static ETL_CONSTANT int Endian = Endian_; + static ETL_CONSTANT size_t Size = Size_; + static ETL_CONSTANT value_type Min_Value = static_cast(0); + static ETL_CONSTANT value_type Max_Value = static_cast(etl::integral_limits::max >> ((sizeof(value_type) - Size_) * 8U)); + + //************************************************************************* + /// Construct from a storage pointer + //************************************************************************* + unaligned_uint_type_ext(pointer storage_) + : private_unaligned_type::unaligned_type_storage_ext(storage_) + { + } + + //************************************************************************* + /// Construct from a value and storage pointer + //************************************************************************* + unaligned_uint_type_ext(value_type value, pointer storage_) + : private_unaligned_type::unaligned_type_storage_ext(storage_) + { + unaligned_copy::copy_value_to_store(value, this->storage); + } + + //************************************************************************* + /// Copy constructor with storage pointer + /// \note 'storage_' must either be the same storage as 'other's, or refer + /// to completely separate storage. Partially overlapping storage is + /// undefined behaviour. + //************************************************************************* + template + unaligned_uint_type_ext(const unaligned_uint_type_ext& other, pointer storage_) + : private_unaligned_type::unaligned_type_storage_ext(storage_) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); + } + +#if ETL_USING_CPP11 + //************************************************************************* + /// Move constructor + //************************************************************************* + unaligned_uint_type_ext(unaligned_uint_type_ext && other) + : private_unaligned_type::unaligned_type_storage_ext(other.storage) + { + other.storage = ETL_NULLPTR; + } + + //************************************************************************* + /// Move constructor from other endianness + //************************************************************************* + template + unaligned_uint_type_ext(unaligned_uint_type_ext && other) + : private_unaligned_type::unaligned_type_storage_ext(other.storage) + { + // If we're constructing from a different endianness then we need to + // reverse the data order. + if (Endian != Endian_Other) + { + etl::reverse(this->begin(), this->end()); + } + + other.storage = ETL_NULLPTR; + } +#endif + + //************************************************************************* + /// Assignment operator + //************************************************************************* + unaligned_uint_type_ext& operator=(value_type value) + { + unaligned_copy::copy_value_to_store(value, this->storage); + + return *this; + } + + //************************************************************************* + /// Copy assignment operator. + //************************************************************************* + unaligned_uint_type_ext& operator=(const unaligned_uint_type_ext& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian, this->storage); + + return *this; + } + + //************************************************************************* + /// Copy assignment operator from other endianness. + /// \note The storage of 'other' must either be the same storage as this + /// object's, or completely separate. Partially overlapping storage is + /// undefined behaviour. + //************************************************************************* + template + unaligned_uint_type_ext& operator=(const unaligned_uint_type_ext& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); + + return *this; + } + +#if ETL_USING_CPP11 + //************************************************************************* + /// Move assignment operator. + //************************************************************************* + unaligned_uint_type_ext& operator=(unaligned_uint_type_ext&& other) + { + this->storage = other.storage; + other.storage = ETL_NULLPTR; + + return *this; + } + + //************************************************************************* + /// Move assignment operator from other endianness. + //************************************************************************* + template + unaligned_uint_type_ext& operator=(unaligned_uint_type_ext&& other) + { + this->storage = other.storage; + + // If we're assigning from a different endianness then we need to reverse + // the data order. + if (Endian != Endian_Other) + { + etl::reverse(this->begin(), this->end()); + } + + other.storage = ETL_NULLPTR; + + return *this; + } +#endif + + //************************************************************************* + /// Conversion operator + //************************************************************************* + operator value_type() const + { + return value(); + } + + //************************************************************************* + /// Get the value. + //************************************************************************* + value_type value() const + { + value_type v = value_type(); + + unaligned_copy::copy_store_to_value(this->storage, v); + + return v; + } + + //************************************************************************* + /// Sets the storage for the type. + //************************************************************************* + void set_storage(pointer storage_) + { + this->storage = storage_; + } + + private: + + unaligned_uint_type_ext() ETL_DELETE; + }; + ETL_END_PACKED + + template + ETL_CONSTANT int unaligned_uint_type_ext::Endian; + + template + ETL_CONSTANT size_t unaligned_uint_type_ext::Size; + + template + ETL_CONSTANT typename unaligned_uint_type_ext::value_type unaligned_uint_type_ext::Min_Value; + + template + ETL_CONSTANT typename unaligned_uint_type_ext::value_type unaligned_uint_type_ext::Max_Value; + + //************************************************************************* + /// unaligned_int_type_ext + ///\brief Allows a signed, two's complement, integer of an arbitrary number + /// of bytes to be stored at an unaligned address. Uses an external + /// buffer. + /// The value is interfaced to via the smallest standard signed + /// integer type that can hold it, and is sign extended on reading. + ///\tparam Size_ The number of bytes of storage. + ///\tparam Endian_ The endianness of the stored integer. + ///\note Values that do not fit in 'Size_' bytes are truncated, in the same + /// way as a narrowing static_cast. + //************************************************************************* + template + ETL_PACKED_CLASS(unaligned_int_type_ext) + : public private_unaligned_type::unaligned_type_storage_ext + { + public: + + template + friend class unaligned_int_type_ext; + + typedef typename private_unaligned_type::interface_int::type value_type; + + typedef private_unaligned_type::unaligned_copy unaligned_copy; + + typedef typename private_unaligned_type::unaligned_type_storage_ext::storage_type storage_type; + typedef typename private_unaligned_type::unaligned_type_storage_ext::pointer pointer; + typedef typename private_unaligned_type::unaligned_type_storage_ext::const_pointer const_pointer; + typedef typename private_unaligned_type::unaligned_type_storage_ext::iterator iterator; + typedef typename private_unaligned_type::unaligned_type_storage_ext::const_iterator const_iterator; + typedef typename private_unaligned_type::unaligned_type_storage_ext::reverse_iterator reverse_iterator; + typedef typename private_unaligned_type::unaligned_type_storage_ext::const_reverse_iterator const_reverse_iterator; + + static ETL_CONSTANT int Endian = Endian_; + static ETL_CONSTANT size_t Size = Size_; + static ETL_CONSTANT value_type Max_Value = + static_cast(etl::integral_limits::type>::max >> (((sizeof(value_type) - Size_) * 8U) + 1U)); + static ETL_CONSTANT value_type Min_Value = static_cast(-Max_Value - 1); + + //************************************************************************* + /// Construct from a storage pointer + //************************************************************************* + unaligned_int_type_ext(pointer storage_) + : private_unaligned_type::unaligned_type_storage_ext(storage_) + { + } + + //************************************************************************* + /// Construct from a value and storage pointer + //************************************************************************* + unaligned_int_type_ext(value_type value, pointer storage_) + : private_unaligned_type::unaligned_type_storage_ext(storage_) + { + unaligned_copy::copy_value_to_store(value, this->storage); + } + + //************************************************************************* + /// Copy constructor with storage pointer + /// \note 'storage_' must either be the same storage as 'other's, or refer + /// to completely separate storage. Partially overlapping storage is + /// undefined behaviour. + //************************************************************************* + template + unaligned_int_type_ext(const unaligned_int_type_ext& other, pointer storage_) + : private_unaligned_type::unaligned_type_storage_ext(storage_) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); + } + +#if ETL_USING_CPP11 + //************************************************************************* + /// Move constructor + //************************************************************************* + unaligned_int_type_ext(unaligned_int_type_ext && other) + : private_unaligned_type::unaligned_type_storage_ext(other.storage) + { + other.storage = ETL_NULLPTR; + } + + //************************************************************************* + /// Move constructor from other endianness + //************************************************************************* + template + unaligned_int_type_ext(unaligned_int_type_ext && other) + : private_unaligned_type::unaligned_type_storage_ext(other.storage) + { + // If we're constructing from a different endianness then we need to + // reverse the data order. + if (Endian != Endian_Other) + { + etl::reverse(this->begin(), this->end()); + } + + other.storage = ETL_NULLPTR; + } +#endif + + //************************************************************************* + /// Assignment operator + //************************************************************************* + unaligned_int_type_ext& operator=(value_type value) + { + unaligned_copy::copy_value_to_store(value, this->storage); + + return *this; + } + + //************************************************************************* + /// Copy assignment operator. + //************************************************************************* + unaligned_int_type_ext& operator=(const unaligned_int_type_ext& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian, this->storage); + + return *this; + } + + //************************************************************************* + /// Copy assignment operator from other endianness. + /// \note The storage of 'other' must either be the same storage as this + /// object's, or completely separate. Partially overlapping storage is + /// undefined behaviour. + //************************************************************************* + template + unaligned_int_type_ext& operator=(const unaligned_int_type_ext& other) + { + unaligned_copy::copy_store_to_store(other.data(), Endian_Other, this->storage); + + return *this; + } + +#if ETL_USING_CPP11 + //************************************************************************* + /// Move assignment operator. + //************************************************************************* + unaligned_int_type_ext& operator=(unaligned_int_type_ext&& other) + { + this->storage = other.storage; + other.storage = ETL_NULLPTR; + + return *this; + } + + //************************************************************************* + /// Move assignment operator from other endianness. + //************************************************************************* + template + unaligned_int_type_ext& operator=(unaligned_int_type_ext&& other) + { + this->storage = other.storage; + + // If we're assigning from a different endianness then we need to reverse + // the data order. + if (Endian != Endian_Other) + { + etl::reverse(this->begin(), this->end()); + } + + other.storage = ETL_NULLPTR; + + return *this; + } +#endif + + //************************************************************************* + /// Conversion operator + //************************************************************************* + operator value_type() const + { + return value(); + } + + //************************************************************************* + /// Get the value. + //************************************************************************* + value_type value() const + { + typedef typename etl::unsigned_type::type unsigned_t; + + unsigned_t uvalue = unsigned_t(0); + + unaligned_copy::copy_store_to_value(this->storage, uvalue); + + return private_unaligned_type::sign_extend(uvalue); + } + + //************************************************************************* + /// Sets the storage for the type. + //************************************************************************* + void set_storage(pointer storage_) + { + this->storage = storage_; + } + + private: + + unaligned_int_type_ext() ETL_DELETE; + }; + ETL_END_PACKED + + template + ETL_CONSTANT int unaligned_int_type::Endian; + + template + ETL_CONSTANT size_t unaligned_int_type::Size; + + template + ETL_CONSTANT typename unaligned_int_type::value_type unaligned_int_type::Min_Value; + + template + ETL_CONSTANT typename unaligned_int_type::value_type unaligned_int_type::Max_Value; + + // 24 bit integers. + typedef unaligned_uint_type<3U, etl::endian::little> le_uint24_t; + typedef unaligned_uint_type<3U, etl::endian::big> be_uint24_t; + typedef be_uint24_t net_uint24_t; + typedef unaligned_int_type<3U, etl::endian::little> le_int24_t; + typedef unaligned_int_type<3U, etl::endian::big> be_int24_t; + typedef be_int24_t net_int24_t; +#if ETL_HAS_CONSTEXPR_ENDIANNESS + typedef unaligned_uint_type<3U, etl::endianness::value()> host_uint24_t; + typedef unaligned_int_type<3U, etl::endianness::value()> host_int24_t; +#endif + +#if ETL_USING_64BIT_TYPES + // 40, 48 and 56 bit integers. + typedef unaligned_uint_type<5U, etl::endian::little> le_uint40_t; + typedef unaligned_uint_type<5U, etl::endian::big> be_uint40_t; + typedef be_uint40_t net_uint40_t; + typedef unaligned_int_type<5U, etl::endian::little> le_int40_t; + typedef unaligned_int_type<5U, etl::endian::big> be_int40_t; + typedef be_int40_t net_int40_t; + + typedef unaligned_uint_type<6U, etl::endian::little> le_uint48_t; + typedef unaligned_uint_type<6U, etl::endian::big> be_uint48_t; + typedef be_uint48_t net_uint48_t; + typedef unaligned_int_type<6U, etl::endian::little> le_int48_t; + typedef unaligned_int_type<6U, etl::endian::big> be_int48_t; + typedef be_int48_t net_int48_t; + + typedef unaligned_uint_type<7U, etl::endian::little> le_uint56_t; + typedef unaligned_uint_type<7U, etl::endian::big> be_uint56_t; + typedef be_uint56_t net_uint56_t; + typedef unaligned_int_type<7U, etl::endian::little> le_int56_t; + typedef unaligned_int_type<7U, etl::endian::big> be_int56_t; + typedef be_int56_t net_int56_t; + + #if ETL_HAS_CONSTEXPR_ENDIANNESS + typedef unaligned_uint_type<5U, etl::endianness::value()> host_uint40_t; + typedef unaligned_uint_type<6U, etl::endianness::value()> host_uint48_t; + typedef unaligned_uint_type<7U, etl::endianness::value()> host_uint56_t; + typedef unaligned_int_type<5U, etl::endianness::value()> host_int40_t; + typedef unaligned_int_type<6U, etl::endianness::value()> host_int48_t; + typedef unaligned_int_type<7U, etl::endianness::value()> host_int56_t; + #endif +#endif + + template + ETL_CONSTANT int unaligned_int_type_ext::Endian; + + template + ETL_CONSTANT size_t unaligned_int_type_ext::Size; + + template + ETL_CONSTANT typename unaligned_int_type_ext::value_type unaligned_int_type_ext::Min_Value; + + template + ETL_CONSTANT typename unaligned_int_type_ext::value_type unaligned_int_type_ext::Max_Value; + + // 24 bit integers, external buffer. + typedef unaligned_uint_type_ext<3U, etl::endian::little> le_uint24_ext_t; + typedef unaligned_uint_type_ext<3U, etl::endian::big> be_uint24_ext_t; + typedef be_uint24_ext_t net_uint24_ext_t; + typedef unaligned_int_type_ext<3U, etl::endian::little> le_int24_ext_t; + typedef unaligned_int_type_ext<3U, etl::endian::big> be_int24_ext_t; + typedef be_int24_ext_t net_int24_ext_t; +#if ETL_HAS_CONSTEXPR_ENDIANNESS + typedef unaligned_uint_type_ext<3U, etl::endianness::value()> host_uint24_ext_t; + typedef unaligned_int_type_ext<3U, etl::endianness::value()> host_int24_ext_t; +#endif + +#if ETL_USING_64BIT_TYPES + // 40, 48 and 56 bit integers, external buffer. + typedef unaligned_uint_type_ext<5U, etl::endian::little> le_uint40_ext_t; + typedef unaligned_uint_type_ext<5U, etl::endian::big> be_uint40_ext_t; + typedef be_uint40_ext_t net_uint40_ext_t; + typedef unaligned_int_type_ext<5U, etl::endian::little> le_int40_ext_t; + typedef unaligned_int_type_ext<5U, etl::endian::big> be_int40_ext_t; + typedef be_int40_ext_t net_int40_ext_t; + + typedef unaligned_uint_type_ext<6U, etl::endian::little> le_uint48_ext_t; + typedef unaligned_uint_type_ext<6U, etl::endian::big> be_uint48_ext_t; + typedef be_uint48_ext_t net_uint48_ext_t; + typedef unaligned_int_type_ext<6U, etl::endian::little> le_int48_ext_t; + typedef unaligned_int_type_ext<6U, etl::endian::big> be_int48_ext_t; + typedef be_int48_ext_t net_int48_ext_t; + + typedef unaligned_uint_type_ext<7U, etl::endian::little> le_uint56_ext_t; + typedef unaligned_uint_type_ext<7U, etl::endian::big> be_uint56_ext_t; + typedef be_uint56_ext_t net_uint56_ext_t; + typedef unaligned_int_type_ext<7U, etl::endian::little> le_int56_ext_t; + typedef unaligned_int_type_ext<7U, etl::endian::big> be_int56_ext_t; + typedef be_int56_ext_t net_int56_ext_t; + + #if ETL_HAS_CONSTEXPR_ENDIANNESS + typedef unaligned_uint_type_ext<5U, etl::endianness::value()> host_uint40_ext_t; + typedef unaligned_uint_type_ext<6U, etl::endianness::value()> host_uint48_ext_t; + typedef unaligned_uint_type_ext<7U, etl::endianness::value()> host_uint56_ext_t; + typedef unaligned_int_type_ext<5U, etl::endianness::value()> host_int40_ext_t; + typedef unaligned_int_type_ext<6U, etl::endianness::value()> host_int48_ext_t; + typedef unaligned_int_type_ext<7U, etl::endianness::value()> host_int56_ext_t; + #endif +#endif + #if ETL_HAS_CONSTEXPR_ENDIANNESS // Host order typedef unaligned_type host_char_t; diff --git a/test/test_unaligned_type.cpp b/test/test_unaligned_type.cpp index 578d0cfb..21b8502f 100644 --- a/test/test_unaligned_type.cpp +++ b/test/test_unaligned_type.cpp @@ -367,6 +367,208 @@ namespace CHECK_EQUAL(f, etl::be_float_t::value_from(be_f.data())); CHECK_EQUAL(d, etl::le_double_t::value_from(le_d.data())); } + /// Unsigned integers that have no native equivalent, such as 24 bit. + //************************************************************************* + TEST(test_uint24) + { + CHECK_EQUAL(3U, sizeof(etl::le_uint24_t)); + CHECK_EQUAL(3U, sizeof(etl::be_uint24_t)); + CHECK_EQUAL(3U, size_t(etl::le_uint24_t::Size)); + + CHECK((etl::is_same::value)); + CHECK_EQUAL(uint32_t(0x00FFFFFF), etl::le_uint24_t::Max_Value); + + etl::le_uint24_t le_v(0x123456U); + etl::be_uint24_t be_v(0x123456U); + + CHECK_EQUAL(uint32_t(0x123456), le_v.value()); + CHECK_EQUAL(uint32_t(0x123456), be_v.value()); + + // Check the storage byte order. + CHECK_EQUAL(0x56, int(le_v.data()[0])); + CHECK_EQUAL(0x34, int(le_v.data()[1])); + CHECK_EQUAL(0x12, int(le_v.data()[2])); + + CHECK_EQUAL(0x12, int(be_v.data()[0])); + CHECK_EQUAL(0x34, int(be_v.data()[1])); + CHECK_EQUAL(0x56, int(be_v.data()[2])); + + // Copy construction, including from the other endianness. + etl::le_uint24_t le_v2(le_v); + etl::be_uint24_t be_v2(be_v); + etl::le_uint24_t le_v3(be_v); + etl::be_uint24_t be_v3(le_v); + + CHECK_EQUAL(uint32_t(0x123456), le_v2.value()); + CHECK_EQUAL(uint32_t(0x123456), be_v2.value()); + CHECK_EQUAL(uint32_t(0x123456), le_v3.value()); + CHECK_EQUAL(uint32_t(0x123456), be_v3.value()); + + // Assignment. + le_v2 = 0xFFFFFFU; + be_v2 = 0xFFFFFFU; + CHECK_EQUAL(uint32_t(0xFFFFFF), le_v2.value()); + CHECK_EQUAL(uint32_t(0xFFFFFF), be_v2.value()); + + // Boundary values. + etl::le_uint24_t le_zero(0U); + etl::be_uint24_t be_zero(0U); + CHECK_EQUAL(uint32_t(0), le_zero.value()); + CHECK_EQUAL(uint32_t(0), be_zero.value()); + + // Values that do not fit are truncated, like a narrowing cast. + etl::le_uint24_t le_truncated(0xAB123456U); + etl::be_uint24_t be_truncated(0xAB123456U); + CHECK_EQUAL(uint32_t(0x123456), le_truncated.value()); + CHECK_EQUAL(uint32_t(0x123456), be_truncated.value()); + } + + //************************************************************************* + TEST(test_uint24_decode_buffer) + { + const std::array buffer = {0xAA, 0xBB, 0xCC, 0xDD}; + + etl::le_uint24_t le_v(buffer.data(), buffer.size()); + etl::be_uint24_t be_v(buffer.data(), buffer.size()); + + CHECK_EQUAL(uint32_t(0xCCBBAA), le_v.value()); + CHECK_EQUAL(uint32_t(0xAABBCC), be_v.value()); + + // One byte short. + CHECK_THROW(etl::le_uint24_t le_short(buffer.data(), 2U), etl::unaligned_type_buffer_size); + CHECK_THROW(etl::be_uint24_t be_short(buffer.data(), 2U), etl::unaligned_type_buffer_size); + } + +#if ETL_USING_64BIT_TYPES + //************************************************************************* + TEST(test_uint40_uint48_uint56) + { + CHECK_EQUAL(5U, sizeof(etl::be_uint40_t)); + CHECK_EQUAL(6U, sizeof(etl::be_uint48_t)); + CHECK_EQUAL(7U, sizeof(etl::be_uint56_t)); + + etl::be_uint48_t be_v(0x0123456789ABULL); + etl::le_uint48_t le_v(0x0123456789ABULL); + + CHECK_EQUAL(uint64_t(0x0123456789AB), be_v.value()); + CHECK_EQUAL(uint64_t(0x0123456789AB), le_v.value()); + + CHECK_EQUAL(0x01, int(be_v.data()[0])); + CHECK_EQUAL(0xAB, int(le_v.data()[0])); + + CHECK_EQUAL(uint64_t(0x000000FFFFFFFFFF), etl::be_uint40_t::Max_Value); + CHECK_EQUAL(uint64_t(0x00FFFFFFFFFFFFFF), etl::be_uint56_t::Max_Value); + } +#endif + + //************************************************************************* + /// Signed integers that have no native equivalent, such as 24 bit. + //************************************************************************* + TEST(test_int24) + { + CHECK_EQUAL(3U, sizeof(etl::le_int24_t)); + CHECK_EQUAL(3U, sizeof(etl::be_int24_t)); + CHECK_EQUAL(3U, size_t(etl::le_int24_t::Size)); + + CHECK((etl::is_same::value)); + CHECK_EQUAL(int32_t(8388607), etl::le_int24_t::Max_Value); + CHECK_EQUAL(int32_t(-8388608), etl::le_int24_t::Min_Value); + + etl::le_int24_t le_v(0x123456); + etl::be_int24_t be_v(0x123456); + + CHECK_EQUAL(int32_t(0x123456), le_v.value()); + CHECK_EQUAL(int32_t(0x123456), be_v.value()); + + // Check the storage byte order. + CHECK_EQUAL(0x56, int(le_v.data()[0])); + CHECK_EQUAL(0x12, int(le_v.data()[2])); + CHECK_EQUAL(0x12, int(be_v.data()[0])); + CHECK_EQUAL(0x56, int(be_v.data()[2])); + + // Negative values are sign extended on reading. + etl::le_int24_t le_n(-2); + etl::be_int24_t be_n(-2); + + CHECK_EQUAL(int32_t(-2), le_n.value()); + CHECK_EQUAL(int32_t(-2), be_n.value()); + + CHECK_EQUAL(0xFE, int(le_n.data()[0])); + CHECK_EQUAL(0xFF, int(le_n.data()[1])); + CHECK_EQUAL(0xFF, int(le_n.data()[2])); + + CHECK_EQUAL(0xFF, int(be_n.data()[0])); + CHECK_EQUAL(0xFF, int(be_n.data()[1])); + CHECK_EQUAL(0xFE, int(be_n.data()[2])); + + // Copy construction, including from the other endianness. + etl::le_int24_t le_n2(be_n); + etl::be_int24_t be_n2(le_n); + + CHECK_EQUAL(int32_t(-2), le_n2.value()); + CHECK_EQUAL(int32_t(-2), be_n2.value()); + + // Assignment. + le_n2 = -1; + be_n2 = -1; + CHECK_EQUAL(int32_t(-1), le_n2.value()); + CHECK_EQUAL(int32_t(-1), be_n2.value()); + + // Boundary values. + etl::le_int24_t le_min(etl::le_int24_t::Min_Value); + etl::be_int24_t be_min(etl::be_int24_t::Min_Value); + etl::le_int24_t le_max(etl::le_int24_t::Max_Value); + etl::be_int24_t be_max(etl::be_int24_t::Max_Value); + + CHECK_EQUAL(int32_t(-8388608), le_min.value()); + CHECK_EQUAL(int32_t(-8388608), be_min.value()); + CHECK_EQUAL(int32_t(8388607), le_max.value()); + CHECK_EQUAL(int32_t(8388607), be_max.value()); + + etl::le_int24_t le_zero(0); + CHECK_EQUAL(int32_t(0), le_zero.value()); + + // Values that do not fit are truncated, like a narrowing cast. + etl::be_int24_t be_truncated(0x7F123456); + CHECK_EQUAL(int32_t(0x123456), be_truncated.value()); + } + + //************************************************************************* + TEST(test_int24_decode_buffer) + { + const std::array buffer = {0x80, 0x00, 0x00, 0xDD}; + + etl::le_int24_t le_v(buffer.data(), buffer.size()); + etl::be_int24_t be_v(buffer.data(), buffer.size()); + + CHECK_EQUAL(int32_t(0x000080), le_v.value()); + CHECK_EQUAL(int32_t(-8388608), be_v.value()); + + // One byte short. + CHECK_THROW(etl::le_int24_t le_short(buffer.data(), 2U), etl::unaligned_type_buffer_size); + CHECK_THROW(etl::be_int24_t be_short(buffer.data(), 2U), etl::unaligned_type_buffer_size); + } + +#if ETL_USING_64BIT_TYPES + //************************************************************************* + TEST(test_int40_int48_int56) + { + CHECK_EQUAL(5U, sizeof(etl::be_int40_t)); + CHECK_EQUAL(6U, sizeof(etl::be_int48_t)); + CHECK_EQUAL(7U, sizeof(etl::be_int56_t)); + + etl::be_int48_t be_v(-1099511627775LL); + etl::le_int48_t le_v(-1099511627775LL); + + CHECK_EQUAL(int64_t(-1099511627775LL), be_v.value()); + CHECK_EQUAL(int64_t(-1099511627775LL), le_v.value()); + + CHECK_EQUAL(int64_t(549755813887LL), etl::be_int40_t::Max_Value); + CHECK_EQUAL(int64_t(-549755813888LL), etl::be_int40_t::Min_Value); + CHECK_EQUAL(int64_t(36028797018963967LL), etl::be_int56_t::Max_Value); + CHECK_EQUAL(int64_t(-36028797018963968LL), etl::be_int56_t::Min_Value); + } +#endif //************************************************************************* // The following tests demonstrate the 'decode' direction: given a raw byte diff --git a/test/test_unaligned_type_ext.cpp b/test/test_unaligned_type_ext.cpp index 1ffa050a..f858b5bb 100644 --- a/test/test_unaligned_type_ext.cpp +++ b/test/test_unaligned_type_ext.cpp @@ -49,6 +49,8 @@ namespace using storage_i16_t = std::array; using storage_i32_t = std::array; using storage_i64_t = std::array; + using storage_24_t = std::array; + using storage_48_t = std::array; SUITE(test_unaligned_type_ext) { @@ -506,6 +508,148 @@ namespace CHECK_EQUAL(sizeof(uint64_t), etl::net_uint64_ext_t::Size); } + //************************************************************************* + /// Integers that have no native equivalent, such as 24 bit. + //************************************************************************* + TEST(test_uint24) + { + storage_24_t store_le1; + storage_24_t store_be1; + storage_24_t store_le2; + storage_24_t store_be2; + + CHECK_EQUAL(3U, size_t(etl::le_uint24_ext_t::Size)); + CHECK((etl::is_same::value)); + CHECK_EQUAL(uint32_t(0x00FFFFFF), etl::le_uint24_ext_t::Max_Value); + CHECK_EQUAL(uint32_t(0), etl::le_uint24_ext_t::Min_Value); + + etl::le_uint24_ext_t le_v1(store_le1.data()); + le_v1 = 0x123456U; + etl::be_uint24_ext_t be_v1(0x123456U, store_be1.data()); + + CHECK_EQUAL(uint32_t(0x123456), le_v1.value()); + CHECK_EQUAL(uint32_t(0x123456), be_v1.value()); + + // The external buffers hold the requested byte order. + CHECK_EQUAL(0x56, int(store_le1[0])); + CHECK_EQUAL(0x12, int(store_le1[2])); + CHECK_EQUAL(0x12, int(store_be1[0])); + CHECK_EQUAL(0x56, int(store_be1[2])); + + // Copy construct from the other endianness. + etl::le_uint24_ext_t le_v2(be_v1, store_le2.data()); + etl::be_uint24_ext_t be_v2(le_v1, store_be2.data()); + + CHECK_EQUAL(uint32_t(0x123456), le_v2.value()); + CHECK_EQUAL(uint32_t(0x123456), be_v2.value()); + + // Assignment. + le_v2 = le_v1; + be_v2 = le_v1; + CHECK_EQUAL(uint32_t(0x123456), le_v2.value()); + CHECK_EQUAL(uint32_t(0x123456), be_v2.value()); + + // Values that do not fit are truncated. + be_v2 = 0xABCDEF12U; + CHECK_EQUAL(uint32_t(0xCDEF12), be_v2.value()); + } + + //************************************************************************* + TEST(test_move_construction_uint24) + { + storage_24_t store_le1; + storage_24_t store_be1; + + etl::le_uint24_ext_t le_v1(0x123456U, store_le1.data()); + etl::be_uint24_ext_t be_v1(0x123456U, store_be1.data()); + + etl::le_uint24_ext_t le_v2(etl::move(le_v1)); + etl::be_uint24_ext_t be_v2(etl::move(be_v1)); + + CHECK_TRUE(nullptr == le_v1.data()); + CHECK_TRUE(nullptr == be_v1.data()); + CHECK_EQUAL(uint32_t(0x123456), le_v2.value()); + CHECK_EQUAL(uint32_t(0x123456), be_v2.value()); + } + + //************************************************************************* + TEST(test_int24) + { + storage_24_t store_le1; + storage_24_t store_be1; + storage_24_t store_le2; + storage_24_t store_be2; + + CHECK_EQUAL(3U, size_t(etl::le_int24_ext_t::Size)); + CHECK((etl::is_same::value)); + CHECK_EQUAL(int32_t(8388607), etl::le_int24_ext_t::Max_Value); + CHECK_EQUAL(int32_t(-8388608), etl::le_int24_ext_t::Min_Value); + + etl::le_int24_ext_t le_v1(store_le1.data()); + le_v1 = -2; + etl::be_int24_ext_t be_v1(-2, store_be1.data()); + + // Negative values are sign extended on reading. + CHECK_EQUAL(int32_t(-2), le_v1.value()); + CHECK_EQUAL(int32_t(-2), be_v1.value()); + + CHECK_EQUAL(0xFE, int(store_le1[0])); + CHECK_EQUAL(0xFF, int(store_le1[2])); + CHECK_EQUAL(0xFF, int(store_be1[0])); + CHECK_EQUAL(0xFE, int(store_be1[2])); + + // Copy construct from the other endianness. + etl::le_int24_ext_t le_v2(be_v1, store_le2.data()); + etl::be_int24_ext_t be_v2(le_v1, store_be2.data()); + + CHECK_EQUAL(int32_t(-2), le_v2.value()); + CHECK_EQUAL(int32_t(-2), be_v2.value()); + + // Boundary values. + le_v2 = etl::le_int24_ext_t::Min_Value; + be_v2 = etl::be_int24_ext_t::Max_Value; + + CHECK_EQUAL(int32_t(-8388608), le_v2.value()); + CHECK_EQUAL(int32_t(8388607), be_v2.value()); + + le_v2 = 0; + CHECK_EQUAL(int32_t(0), le_v2.value()); + } + + //************************************************************************* + TEST(test_move_construction_int24) + { + storage_24_t store_le1; + storage_24_t store_be1; + + etl::le_int24_ext_t le_v1(-1234567, store_le1.data()); + etl::be_int24_ext_t be_v1(-1234567, store_be1.data()); + + etl::le_int24_ext_t le_v2(etl::move(le_v1)); + etl::be_int24_ext_t be_v2(etl::move(be_v1)); + + CHECK_TRUE(nullptr == le_v1.data()); + CHECK_TRUE(nullptr == be_v1.data()); + CHECK_EQUAL(int32_t(-1234567), le_v2.value()); + CHECK_EQUAL(int32_t(-1234567), be_v2.value()); + } + +#if ETL_USING_64BIT_TYPES + //************************************************************************* + TEST(test_uint48_int48) + { + storage_48_t store_u; + storage_48_t store_i; + + etl::be_uint48_ext_t be_u(0x0123456789ABULL, store_u.data()); + etl::be_int48_ext_t be_i(-1099511627775LL, store_i.data()); + + CHECK_EQUAL(6U, size_t(etl::be_uint48_ext_t::Size)); + CHECK_EQUAL(uint64_t(0x0123456789AB), be_u.value()); + CHECK_EQUAL(int64_t(-1099511627775LL), be_i.value()); + } +#endif + #if ETL_HAS_CONSTEXPR_ENDIANNESS //************************************************************************* TEST(test_host_sizes) @@ -1528,6 +1672,106 @@ namespace CHECK_EQUAL(0x12, bev0); CHECK_EQUAL(0x34, bev1); } + + //************************************************************************* + TEST(test_shared_storage_copy_construction_uint24) + { + storage_24_t store; + + etl::be_uint24_ext_t be_v(0x123456, store.data()); + + // Reinterpret the same buffer with the opposite endianness. + etl::le_uint24_ext_t le_v(be_v, store.data()); + + CHECK_EQUAL(0x56, int(store[0])); + CHECK_EQUAL(0x34, int(store[1])); + CHECK_EQUAL(0x12, int(store[2])); + CHECK_EQUAL(0x123456, le_v.value()); + } + + //************************************************************************* + TEST(test_shared_storage_copy_construction_int24) + { + storage_24_t store; + + etl::be_int24_ext_t be_v(-0x123456, store.data()); + + etl::le_int24_ext_t le_v(be_v, store.data()); + + CHECK_EQUAL(-0x123456, le_v.value()); + } + + //************************************************************************* + TEST(test_shared_storage_copy_assignment_uint24) + { + storage_24_t store; + + etl::be_uint24_ext_t be_v(0x123456, store.data()); + etl::le_uint24_ext_t le_v(store.data()); + + le_v = be_v; + + CHECK_EQUAL(0x56, int(store[0])); + CHECK_EQUAL(0x34, int(store[1])); + CHECK_EQUAL(0x12, int(store[2])); + CHECK_EQUAL(0x123456, le_v.value()); + } + + //************************************************************************* + TEST(test_shared_storage_copy_assignment_int24) + { + storage_24_t store; + + etl::be_int24_ext_t be_v(-0x123456, store.data()); + etl::le_int24_ext_t le_v(store.data()); + + le_v = be_v; + + CHECK_EQUAL(-0x123456, le_v.value()); + } + + //************************************************************************* + TEST(test_shared_storage_copy_assignment_uint32) + { + storage_i32_t store; + + etl::be_uint32_ext_t be_v(0x12345678, store.data()); + etl::le_uint32_ext_t le_v(store.data()); + + le_v = be_v; + + CHECK_EQUAL(0x78, int(store[0])); + CHECK_EQUAL(0x56, int(store[1])); + CHECK_EQUAL(0x34, int(store[2])); + CHECK_EQUAL(0x12, int(store[3])); + CHECK_EQUAL(0x12345678, le_v.value()); + } + + //************************************************************************* + TEST(test_shared_storage_copy_assignment_same_endianness) + { + storage_i32_t store; + + etl::le_uint32_ext_t le_v1(0x12345678, store.data()); + etl::le_uint32_ext_t le_v2(store.data()); + + le_v2 = le_v1; + + CHECK_EQUAL(0x12345678, le_v2.value()); + } + + //************************************************************************* + TEST(test_shared_storage_copy_assignment_float) + { + storage_f_t store; + + etl::be_float_ext_t be_v(3.1415927f, store.data()); + etl::le_float_ext_t le_v(store.data()); + + le_v = be_v; + + CHECK_EQUAL(3.1415927f, le_v.value()); + } } } // namespace From b0548949df3eaed6b810a7433a5b15639832a2f3 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 30 Sep 2026 09:27:23 +0200 Subject: [PATCH 12/13] Fix circular reference in callback_timer_* (#1611) * Add docs and tutorial for etl::intrusive_avl_tree (#1608) * Add docs and tutorial for etl::intrusive_avl_tree Documents the intrusive AVL tree added in PR #1425: a full API reference page under Containers/Trees, and a tutorial demonstrating ordered access to intrusively-linked objects with unpredictable lifetimes, including move support and destructor-driven automatic removal from the tree. Co-Authored-By: Claude Sonnet 5 * Add complexity shortcode and document its usage Introduces a {{< complexity >}} Hugo shortcode (with light/dark styling for constant/logarithmic/linear/linearithmic/polynomial/exponential classes) for rendering Big-O complexity inline, applies it throughout the intrusive_avl_tree API reference in place of raw "O(...)" text, and documents the Description/Complexity structure and supported classes in DOCUMENTATION.md. Co-Authored-By: Claude Sonnet 5 * Fix issues found by @CodeRabbit * Put complexity notes on their own lines in intrusive_avl_tree docs Move the complexity shortcode from the inline "**Complexity:**" label to its own line under a "**Complexity**" label. Notes that give several alternatives (e.g. amortized vs. worst-case) now show each one on a separate line. Update the guideline in DOCUMENTATION.md to match. * Update complexity examples in DOCUMENTATION.md Add the hard line break after the "**Complexity**" label in the example, and rewrite the list of supported complexity classes in the new two-line format, including an example with several alternatives. * Add logarithmic complexity example to DOCUMENTATION.md --------- Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove * Updated sponsors pages * Refactor the documentation css to group styles for light and dark themes * Fix circular reference in callback_timer_* Fixes #967 * Unify timer tick() ordering and detect duplicate inserts Address review feedback on #1611. Replace the is_active() guard with a consistent tick() ordering across all timer controllers: a repeating timer is now reinserted before its callback is invoked or its message is sent, matching etl::callback_timer. A callback that restarts the timer then replaces the existing entry instead of causing a second insert. Applied to message_timer_atomic, message_timer_interrupt and message_timer_locked as well, which are affected in the same way. Reject an insert of a timer that is already in the active list, rather than linking it to itself, and record that it happened. The regression tests now assert duplicate_insert_detected() instead of relying on the resulting circular reference hanging the test. --------- Co-authored-by: Sergei Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove Co-authored-by: John Wellbelove --- include/etl/callback_timer.h | 34 +++++++++++ include/etl/callback_timer_atomic.h | 48 ++++++++++++++-- include/etl/callback_timer_interrupt.h | 47 +++++++++++++-- include/etl/callback_timer_locked.h | 47 +++++++++++++-- include/etl/message_timer.h | 34 +++++++++++ include/etl/message_timer_atomic.h | 47 +++++++++++++-- include/etl/message_timer_interrupt.h | 47 +++++++++++++-- include/etl/message_timer_locked.h | 47 +++++++++++++-- test/test_callback_timer_atomic.cpp | 77 +++++++++++++++++++++++++ test/test_callback_timer_interrupt.cpp | 77 +++++++++++++++++++++++++ test/test_callback_timer_locked.cpp | 80 ++++++++++++++++++++++++++ 11 files changed, 554 insertions(+), 31 deletions(-) diff --git a/include/etl/callback_timer.h b/include/etl/callback_timer.h index 800a34e2..591ace1f 100644 --- a/include/etl/callback_timer.h +++ b/include/etl/callback_timer.h @@ -411,6 +411,16 @@ namespace etl return false; } + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + //******************************************* /// Check if there is an active timer. //******************************************* @@ -630,6 +640,7 @@ namespace etl : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) , ptimers(ptimers_) + , duplicate_insert(false) { } @@ -639,6 +650,15 @@ namespace etl return head == etl::timer::id::NO_TIMER; } + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + //******************************* // Inserts the timer at the correct delta position //******************************* @@ -646,6 +666,16 @@ namespace etl { timer_data& timer = ptimers[id_]; + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + if (head == etl::timer::id::NO_TIMER) { // No entries yet. @@ -788,6 +818,10 @@ namespace etl etl::timer::id::type tail; timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; }; // The array of timer data structures. diff --git a/include/etl/callback_timer_atomic.h b/include/etl/callback_timer_atomic.h index 98d92651..462601da 100644 --- a/include/etl/callback_timer_atomic.h +++ b/include/etl/callback_timer_atomic.h @@ -174,12 +174,9 @@ namespace etl active_list.remove(timer.id, true); remove_callback.call_if(timer.id); - if (timer.callback.is_valid()) - { - // Call the delegate callback. - timer.callback(); - } - + // The timer is reinserted before the callback is called, so that + // a callback that restarts the timer replaces this entry instead + // of the timer being inserted into the active list twice. if (timer.repeating) { // Reinsert the timer. @@ -188,6 +185,11 @@ namespace etl insert_callback.call_if(timer.id); } + if (timer.callback.is_valid()) + { + timer.callback(); + } + has_active = !active_list.empty(); } @@ -301,6 +303,16 @@ namespace etl return false; } + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + //******************************************* /// Check if there is an active timer. //******************************************* @@ -486,6 +498,7 @@ namespace etl : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) , ptimers(ptimers_) + , duplicate_insert(false) { } @@ -495,6 +508,15 @@ namespace etl return head == etl::timer::id::NO_TIMER; } + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + //******************************* // Inserts the timer at the correct delta position //******************************* @@ -502,6 +524,16 @@ namespace etl { timer_data& timer = ptimers[id_]; + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + if (head == etl::timer::id::NO_TIMER) { // No entries yet. @@ -644,6 +676,10 @@ namespace etl etl::timer::id::type tail; timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; }; // The array of timer data structures. diff --git a/include/etl/callback_timer_interrupt.h b/include/etl/callback_timer_interrupt.h index a67754cb..3c6aeb9b 100644 --- a/include/etl/callback_timer_interrupt.h +++ b/include/etl/callback_timer_interrupt.h @@ -178,11 +178,9 @@ namespace etl active_list.remove(timer.id, true); remove_callback.call_if(timer.id); - if (timer.callback.is_valid()) - { - timer.callback(); - } - + // The timer is reinserted before the callback is called, so that + // a callback that restarts the timer replaces this entry instead + // of the timer being inserted into the active list twice. if (timer.repeating) { // Reinsert the timer. @@ -191,6 +189,11 @@ namespace etl insert_callback.call_if(timer.id); } + if (timer.callback.is_valid()) + { + timer.callback(); + } + has_active = !active_list.empty(); } @@ -305,6 +308,16 @@ namespace etl return false; } + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + //******************************************* /// Check if there is an active timer. //******************************************* @@ -487,6 +500,7 @@ namespace etl : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) , ptimers(ptimers_) + , duplicate_insert(false) { } @@ -496,6 +510,15 @@ namespace etl return head == etl::timer::id::NO_TIMER; } + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + //******************************* // Inserts the timer at the correct delta position //******************************* @@ -503,6 +526,16 @@ namespace etl { timer_data& timer = ptimers[id_]; + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + if (head == etl::timer::id::NO_TIMER) { // No entries yet. @@ -645,6 +678,10 @@ namespace etl etl::timer::id::type tail; timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; }; // The array of timer data structures. diff --git a/include/etl/callback_timer_locked.h b/include/etl/callback_timer_locked.h index 5579ec0b..fde93249 100644 --- a/include/etl/callback_timer_locked.h +++ b/include/etl/callback_timer_locked.h @@ -264,6 +264,16 @@ namespace etl unlock = unlock_; } + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + //******************************************* /// Check if there is an active timer. //******************************************* @@ -460,6 +470,7 @@ namespace etl : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) , ptimers(ptimers_) + , duplicate_insert(false) { } @@ -469,6 +480,15 @@ namespace etl return head == etl::timer::id::NO_TIMER; } + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + //******************************* // Inserts the timer at the correct delta position //******************************* @@ -476,6 +496,16 @@ namespace etl { timer_data& timer = ptimers[id_]; + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + if (head == etl::timer::id::NO_TIMER) { // No entries yet. @@ -618,6 +648,10 @@ namespace etl etl::timer::id::type tail; timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; }; //******************************************* @@ -716,11 +750,9 @@ namespace etl active_list.remove(timer.id, true); remove_callback.call_if(timer.id); - if (timer.callback.is_valid()) - { - timer.callback(); - } - + // The timer is reinserted before the callback is called, so that + // a callback that restarts the timer replaces this entry instead + // of the timer being inserted into the active list twice. if (timer.repeating) { // Reinsert the timer. @@ -729,6 +761,11 @@ namespace etl insert_callback.call_if(timer.id); } + if (timer.callback.is_valid()) + { + timer.callback(); + } + has_active = !active_list.empty(); } diff --git a/include/etl/message_timer.h b/include/etl/message_timer.h index a9c0ce31..cb2151ec 100644 --- a/include/etl/message_timer.h +++ b/include/etl/message_timer.h @@ -158,6 +158,7 @@ namespace etl : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) , ptimers(ptimers_) + , duplicate_insert(false) { } @@ -167,6 +168,15 @@ namespace etl return head == etl::timer::id::NO_TIMER; } + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + //******************************* // Inserts the timer at the correct delta position //******************************* @@ -174,6 +184,16 @@ namespace etl { etl::message_timer_data& timer = ptimers[id_]; + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + if (head == etl::timer::id::NO_TIMER) { // No entries yet. @@ -316,6 +336,10 @@ namespace etl etl::timer::id::type tail; etl::message_timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; }; } // namespace private_message_timer @@ -579,6 +603,16 @@ namespace etl return false; } + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + //******************************************* /// Check if there is an active timer. //******************************************* diff --git a/include/etl/message_timer_atomic.h b/include/etl/message_timer_atomic.h index 8d716d8b..02dc4ab2 100644 --- a/include/etl/message_timer_atomic.h +++ b/include/etl/message_timer_atomic.h @@ -183,11 +183,9 @@ namespace etl active_list.remove(timer.id, true); remove_callback.call_if(timer.id); - if (timer.p_router != ETL_NULLPTR) - { - timer.p_router->receive(timer.destination_router_id, *(timer.p_message)); - } - + // The timer is reinserted before the message is sent, so that + // a receiver that restarts the timer replaces this entry instead + // of the timer being inserted into the active list twice. if (timer.repeating) { timer.delta = timer.period; @@ -195,6 +193,11 @@ namespace etl insert_callback.call_if(timer.id); } + if (timer.p_router != ETL_NULLPTR) + { + timer.p_router->receive(timer.destination_router_id, *(timer.p_message)); + } + has_active = !active_list.empty(); } @@ -308,6 +311,16 @@ namespace etl return false; } + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + //******************************************* /// Check if there is an active timer. //******************************************* @@ -467,6 +480,7 @@ namespace etl : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) , ptimers(ptimers_) + , duplicate_insert(false) { } @@ -476,6 +490,15 @@ namespace etl return head == etl::timer::id::NO_TIMER; } + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + //******************************* // Inserts the timer at the correct delta position //******************************* @@ -483,6 +506,16 @@ namespace etl { timer_data& timer = ptimers[id_]; + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + if (head == etl::timer::id::NO_TIMER) { // No entries yet. @@ -625,6 +658,10 @@ namespace etl etl::timer::id::type tail; timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; }; // The array of timer data structures. diff --git a/include/etl/message_timer_interrupt.h b/include/etl/message_timer_interrupt.h index 33ac248c..18aa4d24 100644 --- a/include/etl/message_timer_interrupt.h +++ b/include/etl/message_timer_interrupt.h @@ -189,11 +189,9 @@ namespace etl active_list.remove(timer.id, true); remove_callback.call_if(timer.id); - if (timer.p_router != ETL_NULLPTR) - { - timer.p_router->receive(timer.destination_router_id, *(timer.p_message)); - } - + // The timer is reinserted before the message is sent, so that + // a receiver that restarts the timer replaces this entry instead + // of the timer being inserted into the active list twice. if (timer.repeating) { // Reinsert the timer. @@ -202,6 +200,11 @@ namespace etl insert_callback.call_if(timer.id); } + if (timer.p_router != ETL_NULLPTR) + { + timer.p_router->receive(timer.destination_router_id, *(timer.p_message)); + } + has_active = !active_list.empty(); } @@ -316,6 +319,16 @@ namespace etl return false; } + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + //******************************************* /// Check if there is an active timer. //******************************************* @@ -473,6 +486,7 @@ namespace etl : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) , ptimers(ptimers_) + , duplicate_insert(false) { } @@ -482,6 +496,15 @@ namespace etl return head == etl::timer::id::NO_TIMER; } + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + //******************************* // Inserts the timer at the correct delta position //******************************* @@ -489,6 +512,16 @@ namespace etl { timer_data& timer = ptimers[id_]; + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + if (head == etl::timer::id::NO_TIMER) { // No entries yet. @@ -631,6 +664,10 @@ namespace etl etl::timer::id::type tail; timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; }; // The array of timer data structures. diff --git a/include/etl/message_timer_locked.h b/include/etl/message_timer_locked.h index 92b41488..aa95aa57 100644 --- a/include/etl/message_timer_locked.h +++ b/include/etl/message_timer_locked.h @@ -186,11 +186,9 @@ namespace etl active_list.remove(timer.id, true); remove_callback.call_if(timer.id); - if (timer.p_router != ETL_NULLPTR) - { - timer.p_router->receive(timer.destination_router_id, *(timer.p_message)); - } - + // The timer is reinserted before the message is sent, so that + // a receiver that restarts the timer replaces this entry instead + // of the timer being inserted into the active list twice. if (timer.repeating) { timer.delta = timer.period; @@ -198,6 +196,11 @@ namespace etl insert_callback.call_if(timer.id); } + if (timer.p_router != ETL_NULLPTR) + { + timer.p_router->receive(timer.destination_router_id, *(timer.p_message)); + } + has_active = !active_list.empty(); } @@ -323,6 +326,16 @@ namespace etl unlock = unlock_; } + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + //******************************************* /// Check if there is an active timer. //******************************************* @@ -481,6 +494,7 @@ namespace etl : head(etl::timer::id::NO_TIMER) , tail(etl::timer::id::NO_TIMER) , ptimers(ptimers_) + , duplicate_insert(false) { } @@ -490,6 +504,15 @@ namespace etl return head == etl::timer::id::NO_TIMER; } + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + //******************************* // Inserts the timer at the correct delta position //******************************* @@ -497,6 +520,16 @@ namespace etl { timer_data& timer = ptimers[id_]; + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + if (head == etl::timer::id::NO_TIMER) { // No entries yet. @@ -639,6 +672,10 @@ namespace etl etl::timer::id::type tail; timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; }; // The array of timer data structures. diff --git a/test/test_callback_timer_atomic.cpp b/test/test_callback_timer_atomic.cpp index 0dcea9ab..06e543f6 100644 --- a/test/test_callback_timer_atomic.cpp +++ b/test/test_callback_timer_atomic.cpp @@ -149,6 +149,35 @@ namespace callback_type free_function_callback2 = callback_type::create(); + //*************************************************************************** + // Fixture for a repeating timer that restarts itself from within its own + // callback (regression test for issue #967). + //*************************************************************************** + struct RestartInCallback + { + etl::callback_timer_atomic<1, std::atomic_uint32_t>* p_controller = nullptr; + etl::timer::id::type id = etl::timer::id::NO_TIMER; + uint32_t new_period = 0U; + std::vector call_ticks; + + void callback() + { + call_ticks.push_back(ticks); + + // Change the period (this stops/deactivates the timer) and restart it + // from inside the callback. Before the fix, tick() would then insert the + // timer a second time, creating a circular reference in the active list. + p_controller->set_period(id, new_period); + p_controller->start(id); + } + }; + + RestartInCallback restart_in_callback; + + using timer_callback_type = etl::icallback_timer_atomic::callback_type; + timer_callback_type restart_in_callback_member = + timer_callback_type::create(); + SUITE(test_callback_timer_atomic) { //************************************************************************* @@ -855,6 +884,54 @@ namespace CHECK_EQUAL(3, timerInsertRemoveTest.removed); } + //************************************************************************* + // Regression test for issue #967: + // Calling start() from within a repeating timer's callback (after the + // period has been changed, which stops the timer) must not insert the timer + // twice and create a circular reference in the active list. + //************************************************************************* + TEST(callback_timer_atomic_restart_in_callback_no_circular_reference) + { + etl::callback_timer_atomic<1, std::atomic_uint32_t> timer_controller; + + restart_in_callback.p_controller = &timer_controller; + restart_in_callback.new_period = 20U; + restart_in_callback.call_ticks.clear(); + + etl::timer::id::type id = timer_controller.register_timer(restart_in_callback_member, 10, etl::timer::mode::Repeating); + restart_in_callback.id = id; + + timer_controller.start(id); + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + // Run long enough for several firings at the original (10) then new (20) period. + while (ticks < 100U) + { + ticks += step; + timer_controller.tick(step); + } + + // Detect a duplicated entry in the active list directly, rather than + // relying on the resulting circular reference hanging the test. + CHECK_FALSE(timer_controller.duplicate_insert_detected()); + + // The timer must still be active (alive, not corrupted) and scheduled at + // the new period. A circular reference would corrupt the active list. + CHECK_TRUE(timer_controller.is_active(id)); + + // First firing at 10, then every 20 thereafter: 10, 30, 50, 70, 90. + std::vector compare = {10, 30, 50, 70, 90}; + + CHECK_EQUAL(compare.size(), restart_in_callback.call_ticks.size()); + CHECK_ARRAY_EQUAL(compare.data(), restart_in_callback.call_ticks.data(), compare.size()); + + timer_controller.clear(); + } + //************************************************************************* #if REALTIME_TEST diff --git a/test/test_callback_timer_interrupt.cpp b/test/test_callback_timer_interrupt.cpp index b7830cf4..e2d71377 100644 --- a/test/test_callback_timer_interrupt.cpp +++ b/test/test_callback_timer_interrupt.cpp @@ -162,6 +162,33 @@ namespace callback_type free_function_callback2 = callback_type::create(); + //*************************************************************************** + // Fixture for a repeating timer that restarts itself from within its own + // callback (regression test for issue #967). + //*************************************************************************** + struct RestartInCallback + { + etl::callback_timer_interrupt<1, ScopedGuard>* p_controller = nullptr; + etl::timer::id::type id = etl::timer::id::NO_TIMER; + uint32_t new_period = 0U; + std::vector call_ticks; + + void callback() + { + call_ticks.push_back(ticks); + + // Change the period (this stops/deactivates the timer) and restart it + // from inside the callback. Before the fix, tick() would then insert the + // timer a second time, creating a circular reference in the active list. + p_controller->set_period(id, new_period); + p_controller->start(id); + } + }; + + RestartInCallback restart_in_callback; + + callback_type restart_in_callback_member = callback_type::create(); + SUITE(test_callback_timer_interrupt) { //************************************************************************* @@ -1001,5 +1028,55 @@ namespace // Check the CHECK_EQUAL(0U, ScopedGuard::guard_count); } + + //************************************************************************* + // Regression test for issue #967: + // Calling start() from within a repeating timer's callback (after the + // period has been changed, which stops the timer) must not insert the timer + // twice and create a circular reference in the active list. + //************************************************************************* + TEST(callback_timer_interrupt_restart_in_callback_no_circular_reference) + { + etl::callback_timer_interrupt<1, ScopedGuard> timer_controller; + + restart_in_callback.p_controller = &timer_controller; + restart_in_callback.new_period = 20U; + restart_in_callback.call_ticks.clear(); + + etl::timer::id::type id = timer_controller.register_timer(restart_in_callback_member, 10, etl::timer::mode::Repeating); + restart_in_callback.id = id; + + timer_controller.start(id); + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + // Run long enough for several firings at the original (10) then new (20) period. + while (ticks < 100U) + { + ticks += step; + timer_controller.tick(step); + } + + // Detect a duplicated entry in the active list directly, rather than + // relying on the resulting circular reference hanging the test. + CHECK_FALSE(timer_controller.duplicate_insert_detected()); + + // The timer must still be active (alive, not corrupted) and scheduled at + // the new period. A circular reference would corrupt the active list. + CHECK_TRUE(timer_controller.is_active(id)); + + // First firing at 10, then every 20 thereafter: 10, 30, 50, 70, 90. + std::vector compare = {10, 30, 50, 70, 90}; + + CHECK_EQUAL(compare.size(), restart_in_callback.call_ticks.size()); + CHECK_ARRAY_EQUAL(compare.data(), restart_in_callback.call_ticks.data(), compare.size()); + + timer_controller.clear(); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } } } // namespace diff --git a/test/test_callback_timer_locked.cpp b/test/test_callback_timer_locked.cpp index b87c4b8c..2ce71280 100644 --- a/test/test_callback_timer_locked.cpp +++ b/test/test_callback_timer_locked.cpp @@ -184,6 +184,33 @@ namespace callback_type free_function_callback2 = callback_type::create(); + //*************************************************************************** + // Fixture for a repeating timer that restarts itself from within its own + // callback (regression test for issue #967). + //*************************************************************************** + struct RestartInCallback + { + etl::callback_timer_locked<1>* p_controller = nullptr; + etl::timer::id::type id = etl::timer::id::NO_TIMER; + uint32_t new_period = 0U; + std::vector call_ticks; + + void callback() + { + call_ticks.push_back(ticks); + + // Change the period (this stops/deactivates the timer) and restart it + // from inside the callback. Before the fix, tick() would then insert the + // timer a second time, creating a circular reference in the active list. + p_controller->set_period(id, new_period); + p_controller->start(id); + } + }; + + RestartInCallback restart_in_callback; + + callback_type restart_in_callback_member = callback_type::create(); + SUITE(test_callback_timer_locked) { //************************************************************************* @@ -1004,6 +1031,59 @@ namespace CHECK_FALSE(timer_controller.has_active_timer()); } + //************************************************************************* + // Regression test for issue #967: + // Calling start() from within a repeating timer's callback (after the + // period has been changed, which stops the timer) must not insert the timer + // twice and create a circular reference in the active list. + //************************************************************************* + TEST(callback_timer_locked_restart_in_callback_no_circular_reference) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_locked<1> timer_controller(try_lock, lock, unlock); + + restart_in_callback.p_controller = &timer_controller; + restart_in_callback.new_period = 20U; + restart_in_callback.call_ticks.clear(); + + etl::timer::id::type id = timer_controller.register_timer(restart_in_callback_member, 10, etl::timer::mode::Repeating); + restart_in_callback.id = id; + + timer_controller.start(id); + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + // Run long enough for several firings at the original (10) then new (20) period. + while (ticks < 100U) + { + ticks += step; + timer_controller.tick(step); + } + + // Detect a duplicated entry in the active list directly, rather than + // relying on the resulting circular reference hanging the test. + CHECK_FALSE(timer_controller.duplicate_insert_detected()); + + // The timer must still be active (alive, not corrupted) and scheduled at + // the new period. A circular reference would corrupt the active list. + CHECK_TRUE(timer_controller.is_active(id)); + + // First firing at 10, then every 20 thereafter: 10, 30, 50, 70, 90. + std::vector compare = {10, 30, 50, 70, 90}; + + CHECK_EQUAL(compare.size(), restart_in_callback.call_ticks.size()); + CHECK_ARRAY_EQUAL(compare.data(), restart_in_callback.call_ticks.data(), compare.size()); + + timer_controller.clear(); + } + //************************************************************************* #if REALTIME_TEST From 360e18df189ccc9dfc037159ec0864b2b80de07f Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 30 Sep 2026 10:12:00 +0200 Subject: [PATCH 13/13] Fix is_swappable for MSVC in C++14 (#1600) * Fix is_swappable for MSVC in C++14 * MSVC 19.44, Debug build, C++20 specific format adjustment * Add docs and tutorial for etl::intrusive_avl_tree (#1608) * Add docs and tutorial for etl::intrusive_avl_tree Documents the intrusive AVL tree added in PR #1425: a full API reference page under Containers/Trees, and a tutorial demonstrating ordered access to intrusively-linked objects with unpredictable lifetimes, including move support and destructor-driven automatic removal from the tree. Co-Authored-By: Claude Sonnet 5 * Add complexity shortcode and document its usage Introduces a {{< complexity >}} Hugo shortcode (with light/dark styling for constant/logarithmic/linear/linearithmic/polynomial/exponential classes) for rendering Big-O complexity inline, applies it throughout the intrusive_avl_tree API reference in place of raw "O(...)" text, and documents the Description/Complexity structure and supported classes in DOCUMENTATION.md. Co-Authored-By: Claude Sonnet 5 * Fix issues found by @CodeRabbit * Put complexity notes on their own lines in intrusive_avl_tree docs Move the complexity shortcode from the inline "**Complexity:**" label to its own line under a "**Complexity**" label. Notes that give several alternatives (e.g. amortized vs. worst-case) now show each one on a separate line. Update the guideline in DOCUMENTATION.md to match. * Update complexity examples in DOCUMENTATION.md Add the hard line break after the "**Complexity**" label in the example, and rewrite the list of supported complexity classes in the new two-line format, including an example with several alternatives. * Add logarithmic complexity example to DOCUMENTATION.md --------- Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove * Updated sponsors pages * Refactor the documentation css to group styles for light and dark themes --------- Co-authored-by: John Wellbelove Co-authored-by: Sergei Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove --- .github/workflows/msvc.yml | 32 ++++-- include/etl/format.h | 17 +++- include/etl/type_traits.h | 194 ++++++++++++++++++++++++++++--------- 3 files changed, 187 insertions(+), 56 deletions(-) diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml index 8f7d4c00..dd5d3e45 100644 --- a/.github/workflows/msvc.yml +++ b/.github/workflows/msvc.yml @@ -36,8 +36,12 @@ concurrency: jobs: build-windows-msvc-stl: - name: Windows - STL + name: Windows - STL - C++${{ matrix.cxx_standard }} runs-on: [windows-2022] + strategy: + fail-fast: false + matrix: + cxx_standard: [14, 17, 20] steps: - name: Checkout @@ -50,7 +54,7 @@ jobs: - name: Build run: | - cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=17 ./ + cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=${{ matrix.cxx_standard }} ./ MSBuild.exe -version MSBuild.exe .\etl.sln @@ -58,8 +62,12 @@ jobs: run: test/Debug/etl_tests.exe -v build-windows-msvc-no-stl: - name: Windows - No STL + name: Windows - No STL - C++${{ matrix.cxx_standard }} runs-on: [windows-2022] + strategy: + fail-fast: false + matrix: + cxx_standard: [14, 17, 20] steps: - name: Checkout uses: actions/checkout@v6.0.3 @@ -71,7 +79,7 @@ jobs: - name: Build run: | - cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=ON -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=17 ./ + cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=ON -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=OFF -DETL_CXX_STANDARD=${{ matrix.cxx_standard }} ./ MSBuild.exe -version MSBuild.exe .\etl.sln @@ -79,8 +87,12 @@ jobs: run: test/Debug/etl_tests.exe -v build-windows-msvc-stl-force-cpp03: - name: Windows - STL - Force C++03 + name: Windows - STL - Force C++03 - C++${{ matrix.cxx_standard }} runs-on: [windows-2022] + strategy: + fail-fast: false + matrix: + cxx_standard: [14, 17, 20] steps: - name: Checkout @@ -93,7 +105,7 @@ jobs: - name: Build run: | - cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=ON -DETL_CXX_STANDARD=17 ./ + cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=ON -DETL_CXX_STANDARD=${{ matrix.cxx_standard }} ./ MSBuild.exe -version MSBuild.exe .\etl.sln @@ -101,8 +113,12 @@ jobs: run: test/Debug/etl_tests.exe -v build-windows-msvc-no-stl-force-cpp03: - name: Windows - No STL - Force C++03 + name: Windows - No STL - Force C++03 - C++${{ matrix.cxx_standard }} runs-on: [windows-2022] + strategy: + fail-fast: false + matrix: + cxx_standard: [14, 17, 20] steps: - name: Checkout @@ -115,7 +131,7 @@ jobs: - name: Build run: | - cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=ON -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=ON -DETL_CXX_STANDARD=17 ./ + cmake -G "Visual Studio 17 2022" -AWin32 -DBUILD_TESTS=ON -DNO_STL=ON -DETL_USE_TYPE_TRAITS_BUILTINS=OFF -DETL_USER_DEFINED_TYPE_TRAITS=OFF -DETL_FORCE_TEST_CPP03_IMPLEMENTATION=ON -DETL_CXX_STANDARD=${{ matrix.cxx_standard }} ./ MSBuild.exe -version MSBuild.exe .\etl.sln diff --git a/include/etl/format.h b/include/etl/format.h index 288454fd..f34121b9 100644 --- a/include/etl/format.h +++ b/include/etl/format.h @@ -509,7 +509,8 @@ namespace etl struct basic_format_string { inline ETL_CONSTEVAL basic_format_string(const char* fmt) - : _sv(fmt) + : _str(fmt) + , _length(etl::char_traits::length(fmt)) { #if ETL_USING_CPP20 // Compile-time validation: check_format runs at compile time via consteval. @@ -527,12 +528,22 @@ namespace etl ETL_CONSTEXPR string_view get() const { - return _sv; + return string_view(_str, _length); } private: - string_view _sv; + // The format string is deliberately stored as a pointer plus a length, and + // not as a string_view. + // + // A string_view holds a pair of pointers, so constructing one here would + // initialise a member with the pointer arithmetic 'fmt + length(fmt)'. + // Microsoft's compiler (19.44) miscompiles exactly that case in a consteval + // constructor in debug builds: the resulting end pointer, and therefore the + // size of the view, is wrong. Storing the length as an integer instead + // computes correctly, and the view is formed on demand in get(). + const char* _str; + size_t _length; }; template diff --git a/include/etl/type_traits.h b/include/etl/type_traits.h index b01b308f..a7df81ae 100644 --- a/include/etl/type_traits.h +++ b/include/etl/type_traits.h @@ -4310,13 +4310,53 @@ namespace etl #endif #if ETL_USING_CPP11 + //********************************************* + // Builtin-free noexcept-constructibility/assignability detection. Used when + // the __is_nothrow_constructible/__is_nothrow_assignable builtins are + // unavailable. Mirrors the void_t-based is_constructible/is_assignable + // fallbacks above so that (e.g.) a class with implicit noexcept move + // operations is correctly reported nothrow-(move-)constructible/assignable, + // and a const-qualified type is correctly reported NOT nothrow-assignable + // (the old is_arithmetic||is_pointer fallback ignored const-qualification). + #if !ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE + namespace private_type_traits + { + template + struct is_nothrow_constructible_ : etl::false_type + { + }; + + template + struct is_nothrow_constructible_()...))>, T, TArgs...> + : etl::bool_constant()...))> + { + }; + } // namespace private_type_traits + #endif + + #if !ETL_USING_BUILTIN_IS_NOTHROW_ASSIGNABLE + namespace private_type_traits + { + template + struct is_nothrow_assignable_ : etl::false_type + { + }; + + template + struct is_nothrow_assignable_() = etl::declval())>, T, U> + : etl::bool_constant() = etl::declval())> + { + }; + } // namespace private_type_traits + #endif + //********************************************* // is_nothrow_constructible template #if ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE struct is_nothrow_constructible : public etl::bool_constant<__is_nothrow_constructible(T, TArgs...)> #else - struct is_nothrow_constructible : public etl::bool_constant::value || etl::is_pointer::value> + struct is_nothrow_constructible : public private_type_traits::is_nothrow_constructible_, T, TArgs...> #endif { }; @@ -4324,33 +4364,21 @@ namespace etl //********************************************* // is_nothrow_default_constructible template - #if ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE struct is_nothrow_default_constructible : public etl::is_nothrow_constructible - #else - struct is_nothrow_default_constructible : public etl::bool_constant::value || etl::is_pointer::value> - #endif { }; //********************************************* // is_nothrow_copy_constructible template - #if ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE struct is_nothrow_copy_constructible : public etl::is_nothrow_constructible::type> - #else - struct is_nothrow_copy_constructible : public etl::bool_constant::value || etl::is_pointer::value> - #endif { }; //********************************************* // is_nothrow_move_constructible template - #if ETL_USING_BUILTIN_IS_NOTHROW_CONSTRUCTIBLE struct is_nothrow_move_constructible : public etl::is_nothrow_constructible::type> - #else - struct is_nothrow_move_constructible : public etl::bool_constant::value || etl::is_pointer::value> - #endif { }; @@ -4360,7 +4388,7 @@ namespace etl #if ETL_USING_BUILTIN_IS_NOTHROW_ASSIGNABLE struct is_nothrow_assignable : public etl::bool_constant<__is_nothrow_assignable(T, U)> #else - struct is_nothrow_assignable : public etl::bool_constant::value || etl::is_pointer::value> + struct is_nothrow_assignable : public private_type_traits::is_nothrow_assignable_, T, U> #endif { }; @@ -4368,24 +4396,16 @@ namespace etl //********************************************* // is_nothrow_copy_assignable template - #if ETL_USING_BUILTIN_IS_NOTHROW_ASSIGNABLE struct is_nothrow_copy_assignable : public etl::is_nothrow_assignable::type, typename etl::add_lvalue_reference::type> - #else - struct is_nothrow_copy_assignable : public etl::bool_constant::value || etl::is_pointer::value> - #endif { }; //********************************************* // is_nothrow_move_assignable template - #if ETL_USING_BUILTIN_IS_NOTHROW_ASSIGNABLE struct is_nothrow_move_assignable : public etl::is_nothrow_assignable::type, typename etl::add_rvalue_reference::type> - #else - struct is_nothrow_move_assignable : public etl::bool_constant::value || etl::is_pointer::value> - #endif { }; #endif @@ -5175,20 +5195,26 @@ namespace etl #elif ETL_USING_CPP11 namespace private_type_traits { + template + struct is_swappable_helper; + namespace swap_detect { - // Bring std::swap into scope, if it is available, so that the unqualified - // call below finds it in those configurations that do not define etl::swap. - // Otherwise etl::swap is found by ordinary lookup in the enclosing - // namespace. Either way, a swap found by argument dependent lookup takes - // part in overload resolution. - #if ETL_USING_STL || defined(ETL_IN_UNIT_TEST) - using std::swap; - #endif + // A dummy declaration that can never be selected, as it takes no + // arguments, but which makes the name visible to ordinary lookup so that + // the unqualified calls below are well formed. + // Ordinary lookup stops here, so only a swap found by argument dependent + // lookup takes part in overload resolution. + // The generic swap that the standard library (or etl) provides is not + // detected this way, because not every implementation constrains it + // (Microsoft's C++14 std::swap does not), which would make every type + // appear to be swappable. Its viability is determined by the + // is_default_swappable traits below instead. + void swap(); // Selected only if 'swap(declval(), declval())' is a valid expression. template - struct is_swappable_test + struct is_adl_swappable_test { private: @@ -5206,7 +5232,7 @@ namespace etl // Selected only if both 'swap(declval(), declval())' and // 'swap(declval(), declval())' are valid expressions. template - struct is_swappable_with_test + struct is_adl_swappable_with_test { private: @@ -5221,15 +5247,61 @@ namespace etl static ETL_CONSTANT bool value = decltype(test(0))::value; }; + + // Determines whether an expression of type T can be bound to a non-const + // lvalue reference to its referenced type. + // This is not simply 'is_lvalue_reference', because some compilers offer + // an extension that binds a class rvalue to a non-const lvalue reference. + template + void binds_to_lvalue_reference_test(T&); + + template + struct binds_to_lvalue_reference : etl::false_type + { + }; + + template + struct binds_to_lvalue_reference< + T, etl::void_t::type>(etl::declval()))>> : etl::true_type + { + }; } // namespace swap_detect + // Determines whether the generic swap(T&, T&) is viable for T. template - struct is_swappable_helper : etl::bool_constant::value> + struct is_default_swappable : etl::bool_constant::value && etl::is_move_assignable::value> + { + }; + + // The generic swap for arrays requires the element type to be swappable. + template + struct is_default_swappable : is_swappable_helper + { + }; + + // An array of unknown bound cannot be swapped. + template + struct is_default_swappable : etl::false_type + { + }; + + // The generic swap only participates when both arguments are lvalues of + // the same type. + template + struct is_default_swappable_with + : etl::bool_constant::value && etl::is_same::value + && is_default_swappable::type>::value> + { + }; + + template + struct is_swappable_helper : etl::bool_constant::value || is_default_swappable::value> { }; template - struct is_swappable_with_helper : etl::bool_constant::value> + struct is_swappable_with_helper + : etl::bool_constant::value || is_default_swappable_with::value> { }; } // namespace private_type_traits @@ -5271,43 +5343,75 @@ namespace etl #elif ETL_USING_CPP11 namespace private_type_traits { + template + struct is_nothrow_swappable_helper; + namespace swap_detect { - // Only 'true' if the type is swappable and the swap expression is noexcept. - template ::value> - struct is_nothrow_swappable_test + // Only 'true' if the type has an ADL found swap and the swap expression is noexcept. + template ::value> + struct is_nothrow_adl_swappable_test { static ETL_CONSTANT bool value = false; }; template - struct is_nothrow_swappable_test + struct is_nothrow_adl_swappable_test { static ETL_CONSTANT bool value = noexcept(swap(etl::declval(), etl::declval())); }; - // Only 'true' if the types are swappable with each other and both of the - // swap expressions are noexcept. - template ::value> - struct is_nothrow_swappable_with_test + // Only 'true' if the types have ADL found swaps for each other and both of + // the swap expressions are noexcept. + template ::value> + struct is_nothrow_adl_swappable_with_test { static ETL_CONSTANT bool value = false; }; template - struct is_nothrow_swappable_with_test + struct is_nothrow_adl_swappable_with_test { static ETL_CONSTANT bool value = noexcept(swap(etl::declval(), etl::declval())) && noexcept(swap(etl::declval(), etl::declval())); }; } // namespace swap_detect + // Determines whether the generic swap(T&, T&) is viable for T and cannot throw. template - struct is_nothrow_swappable_helper : etl::bool_constant::value> + struct is_nothrow_default_swappable + : etl::bool_constant::value && etl::is_nothrow_move_assignable::value> + { + }; + + template + struct is_nothrow_default_swappable : is_nothrow_swappable_helper + { + }; + + template + struct is_nothrow_default_swappable : etl::false_type { }; template - struct is_nothrow_swappable_with_helper : etl::bool_constant::value> + struct is_nothrow_default_swappable_with + : etl::bool_constant::value && etl::is_same::value + && is_nothrow_default_swappable::type>::value> + { + }; + + // An ADL found swap hides the generic one, so it decides the result. + template + struct is_nothrow_swappable_helper + : etl::bool_constant::value ? swap_detect::is_nothrow_adl_swappable_test::value + : is_nothrow_default_swappable::value> + { + }; + + template + struct is_nothrow_swappable_with_helper + : etl::bool_constant::value ? swap_detect::is_nothrow_adl_swappable_with_test::value + : is_nothrow_default_swappable_with::value> { }; } // namespace private_type_traits