mirror of
https://github.com/ETLCPP/etl.git
synced 2026-10-28 18:07:10 +08:00
Add safe integer comparisons (#1506)
Adding cmp_equal, cmp_not_equal, cmp_less, cmp_greater, cmp_less_equal, cmp_greater_equal and in_range. Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>
This commit is contained in:
parent
66f904b1a3
commit
49f690a921
@ -71,6 +71,82 @@ T exchange(T& object, const U& new_value)
|
||||
Copies the new value to object and returns the old value.
|
||||
Note: This is not an atomic operation.
|
||||
|
||||
## Integer comparison functions
|
||||
|
||||
Safe comparison of integers of different signedness. Unlike the built-in
|
||||
comparison operators, negative signed integers always compare less than (and
|
||||
never equal to) unsigned integers, so the result is never affected by
|
||||
value-changing implicit conversions.
|
||||
The arguments must be standard signed or unsigned integer types, including
|
||||
`signed char` and `unsigned char`. `bool`, `char` and the extended character
|
||||
types are not permitted.
|
||||
When the STL is available and the compiler provides
|
||||
`__cpp_lib_integer_comparison_functions` (C++20), the `std` versions are used.
|
||||
Since: `20.49.0`
|
||||
|
||||
```cpp
|
||||
template <typename T, typename U>
|
||||
constexpr bool cmp_equal(T t, U u) noexcept
|
||||
```
|
||||
**Description**
|
||||
Returns `true` if `t` is equal to `u`.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
template <typename T, typename U>
|
||||
constexpr bool cmp_not_equal(T t, U u) noexcept
|
||||
```
|
||||
**Description**
|
||||
Returns `true` if `t` is not equal to `u`.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
template <typename T, typename U>
|
||||
constexpr bool cmp_less(T t, U u) noexcept
|
||||
```
|
||||
**Description**
|
||||
Returns `true` if `t` is less than `u`.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
template <typename T, typename U>
|
||||
constexpr bool cmp_greater(T t, U u) noexcept
|
||||
```
|
||||
**Description**
|
||||
Returns `true` if `t` is greater than `u`.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
template <typename T, typename U>
|
||||
constexpr bool cmp_less_equal(T t, U u) noexcept
|
||||
```
|
||||
**Description**
|
||||
Returns `true` if `t` is less than or equal to `u`.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
template <typename T, typename U>
|
||||
constexpr bool cmp_greater_equal(T t, U u) noexcept
|
||||
```
|
||||
**Description**
|
||||
Returns `true` if `t` is greater than or equal to `u`.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
template <typename R, typename T>
|
||||
constexpr bool in_range(T t) noexcept
|
||||
```
|
||||
**Description**
|
||||
Returns `true` if the value of `t` is in the range of values that can be
|
||||
represented by `R`, that is, if `t` can be converted to `R` without loss of its
|
||||
value.
|
||||
|
||||
## add_const
|
||||
|
||||
```cpp
|
||||
|
||||
@ -32,6 +32,7 @@ SOFTWARE.
|
||||
#define ETL_UTILITY_INCLUDED
|
||||
|
||||
#include "platform.h"
|
||||
#include "limits.h"
|
||||
#include "type_traits.h"
|
||||
|
||||
#include "private/tuple_element.h"
|
||||
@ -142,6 +143,173 @@ namespace etl
|
||||
return static_cast<typename underlying_type<T>::type>(value);
|
||||
}
|
||||
|
||||
#if ETL_USING_STL && ETL_USING_CPP20 && defined(__cpp_lib_integer_comparison_functions)
|
||||
//***************************************************************************
|
||||
// Safe integer comparison functions.
|
||||
//***************************************************************************
|
||||
using std::cmp_equal;
|
||||
using std::cmp_greater;
|
||||
using std::cmp_greater_equal;
|
||||
using std::cmp_less;
|
||||
using std::cmp_less_equal;
|
||||
using std::cmp_not_equal;
|
||||
using std::in_range;
|
||||
#else
|
||||
namespace private_utility
|
||||
{
|
||||
//***********************************
|
||||
/// Determines whether T is one of the standard integer types permitted as
|
||||
/// an argument to the safe integer comparison functions. The permitted
|
||||
/// types are the signed and unsigned standard integer types, including
|
||||
/// signed char and unsigned char. bool, char and the extended character
|
||||
/// types are excluded, matching the C++ standard's constraints on
|
||||
/// etl::cmp_* and etl::in_range.
|
||||
//***********************************
|
||||
template <typename T>
|
||||
struct is_standard_integer : etl::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct is_standard_integer<signed char> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<short> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<int> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<long> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<long long> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<unsigned char> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<unsigned short> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<unsigned int> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<unsigned long> : etl::true_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_standard_integer<unsigned long long> : etl::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_standard_integer<const T> : is_standard_integer<T>
|
||||
{
|
||||
};
|
||||
template <typename T>
|
||||
struct is_standard_integer<volatile T> : is_standard_integer<T>
|
||||
{
|
||||
};
|
||||
template <typename T>
|
||||
struct is_standard_integer<const volatile T> : is_standard_integer<T>
|
||||
{
|
||||
};
|
||||
|
||||
//***********************************
|
||||
/// Enabled when both T and U are permitted comparison types.
|
||||
//***********************************
|
||||
template <typename T, typename U, typename TReturn = void>
|
||||
struct enable_if_comparable : etl::enable_if<is_standard_integer<T>::value && is_standard_integer<U>::value, TReturn>
|
||||
{
|
||||
};
|
||||
} // namespace private_utility
|
||||
|
||||
//***************************************************************************
|
||||
/// Compares the values of two integers.
|
||||
/// Unlike the built-in comparison operators, negative signed integers always
|
||||
/// compare less than (and not equal to) unsigned integers.
|
||||
/// The comparison is safe against value-changing implicit conversions.
|
||||
/// https://en.cppreference.com/w/cpp/utility/intcmp
|
||||
///\ingroup utility
|
||||
//***************************************************************************
|
||||
template <typename T, typename U>
|
||||
ETL_NODISCARD ETL_CONSTEXPR typename private_utility::enable_if_comparable<T, U, bool>::type cmp_equal(T t, U u) ETL_NOEXCEPT
|
||||
{
|
||||
return (etl::is_signed<T>::value == etl::is_signed<U>::value) ? (t == u)
|
||||
: etl::is_signed<T>::value ? ((t >= T(0)) && (typename etl::make_unsigned<T>::type(t) == u))
|
||||
: ((u >= U(0)) && (t == typename etl::make_unsigned<U>::type(u)));
|
||||
}
|
||||
|
||||
//***************************************************************************
|
||||
/// See @ref cmp_equal.
|
||||
//***************************************************************************
|
||||
template <typename T, typename U>
|
||||
ETL_NODISCARD ETL_CONSTEXPR typename private_utility::enable_if_comparable<T, U, bool>::type cmp_not_equal(T t, U u) ETL_NOEXCEPT
|
||||
{
|
||||
return !etl::cmp_equal(t, u);
|
||||
}
|
||||
|
||||
//***************************************************************************
|
||||
/// See @ref cmp_equal.
|
||||
//***************************************************************************
|
||||
template <typename T, typename U>
|
||||
ETL_NODISCARD ETL_CONSTEXPR typename private_utility::enable_if_comparable<T, U, bool>::type cmp_less(T t, U u) ETL_NOEXCEPT
|
||||
{
|
||||
return (etl::is_signed<T>::value == etl::is_signed<U>::value) ? (t < u)
|
||||
: etl::is_signed<T>::value ? ((t < T(0)) || (typename etl::make_unsigned<T>::type(t) < u))
|
||||
: ((u >= U(0)) && (t < typename etl::make_unsigned<U>::type(u)));
|
||||
}
|
||||
|
||||
//***************************************************************************
|
||||
/// See @ref cmp_equal.
|
||||
//***************************************************************************
|
||||
template <typename T, typename U>
|
||||
ETL_NODISCARD ETL_CONSTEXPR typename private_utility::enable_if_comparable<T, U, bool>::type cmp_greater(T t, U u) ETL_NOEXCEPT
|
||||
{
|
||||
return etl::cmp_less(u, t);
|
||||
}
|
||||
|
||||
//***************************************************************************
|
||||
/// See @ref cmp_equal.
|
||||
//***************************************************************************
|
||||
template <typename T, typename U>
|
||||
ETL_NODISCARD ETL_CONSTEXPR typename private_utility::enable_if_comparable<T, U, bool>::type cmp_less_equal(T t, U u) ETL_NOEXCEPT
|
||||
{
|
||||
return !etl::cmp_less(u, t);
|
||||
}
|
||||
|
||||
//***************************************************************************
|
||||
/// See @ref cmp_equal.
|
||||
//***************************************************************************
|
||||
template <typename T, typename U>
|
||||
ETL_NODISCARD ETL_CONSTEXPR typename private_utility::enable_if_comparable<T, U, bool>::type cmp_greater_equal(T t, U u) ETL_NOEXCEPT
|
||||
{
|
||||
return !etl::cmp_less(t, u);
|
||||
}
|
||||
|
||||
//***************************************************************************
|
||||
/// Returns whether the value of t is in the range of values that can be
|
||||
/// represented by R, that is, whether t can be converted to R without loss
|
||||
/// of its value.
|
||||
/// https://en.cppreference.com/w/cpp/utility/in_range
|
||||
///\ingroup utility
|
||||
//***************************************************************************
|
||||
template <typename R, typename T>
|
||||
ETL_NODISCARD ETL_CONSTEXPR typename private_utility::enable_if_comparable<R, T, bool>::type in_range(T t) ETL_NOEXCEPT
|
||||
{
|
||||
return etl::cmp_greater_equal(t, etl::numeric_limits<R>::min()) && etl::cmp_less_equal(t, etl::numeric_limits<R>::max());
|
||||
}
|
||||
#endif
|
||||
|
||||
// We can't have std::swap and etl::swap templates coexisting in the unit
|
||||
// tests as the compiler will be unable to decide which one to use, due to
|
||||
// ADL.
|
||||
|
||||
@ -785,6 +785,161 @@ namespace
|
||||
}
|
||||
#endif
|
||||
|
||||
//*********************************
|
||||
TEST(test_cmp_equal)
|
||||
{
|
||||
// Same signedness.
|
||||
CHECK_TRUE(etl::cmp_equal(1, 1));
|
||||
CHECK_FALSE(etl::cmp_equal(1, 2));
|
||||
CHECK_TRUE(etl::cmp_equal(1U, 1U));
|
||||
CHECK_FALSE(etl::cmp_equal(1U, 2U));
|
||||
|
||||
// Mixed signedness where the built-in operator would give the wrong result.
|
||||
// -1 as unsigned would be a large value, so a naive (a == b) would be false anyway,
|
||||
// but the classic trap is comparing a negative signed with an equal-bit-pattern unsigned.
|
||||
CHECK_FALSE(etl::cmp_equal(-1, static_cast<unsigned int>(-1)));
|
||||
CHECK_FALSE(etl::cmp_equal(static_cast<unsigned int>(-1), -1));
|
||||
|
||||
// Non-negative signed compared with unsigned.
|
||||
CHECK_TRUE(etl::cmp_equal(5, 5U));
|
||||
CHECK_TRUE(etl::cmp_equal(5U, 5));
|
||||
CHECK_FALSE(etl::cmp_equal(-5, 5U));
|
||||
CHECK_FALSE(etl::cmp_equal(5U, -5));
|
||||
|
||||
// Different widths.
|
||||
CHECK_TRUE(etl::cmp_equal(int8_t(100), int64_t(100)));
|
||||
CHECK_FALSE(etl::cmp_equal(int8_t(-1), uint64_t(0xFFFFFFFFFFFFFFFFULL)));
|
||||
}
|
||||
|
||||
//*********************************
|
||||
TEST(test_cmp_not_equal)
|
||||
{
|
||||
CHECK_FALSE(etl::cmp_not_equal(1, 1));
|
||||
CHECK_TRUE(etl::cmp_not_equal(1, 2));
|
||||
CHECK_TRUE(etl::cmp_not_equal(-1, static_cast<unsigned int>(-1)));
|
||||
CHECK_FALSE(etl::cmp_not_equal(5, 5U));
|
||||
CHECK_TRUE(etl::cmp_not_equal(-5, 5U));
|
||||
}
|
||||
|
||||
//*********************************
|
||||
TEST(test_cmp_less)
|
||||
{
|
||||
// Same signedness.
|
||||
CHECK_TRUE(etl::cmp_less(1, 2));
|
||||
CHECK_FALSE(etl::cmp_less(2, 1));
|
||||
CHECK_FALSE(etl::cmp_less(1, 1));
|
||||
CHECK_TRUE(etl::cmp_less(1U, 2U));
|
||||
CHECK_FALSE(etl::cmp_less(2U, 1U));
|
||||
|
||||
// A negative signed value is always less than any unsigned value.
|
||||
CHECK_TRUE(etl::cmp_less(-1, 0U));
|
||||
CHECK_TRUE(etl::cmp_less(-1, static_cast<unsigned int>(-1)));
|
||||
CHECK_FALSE(etl::cmp_less(static_cast<unsigned int>(-1), -1));
|
||||
|
||||
// Non-negative signed with unsigned.
|
||||
CHECK_TRUE(etl::cmp_less(5, 6U));
|
||||
CHECK_FALSE(etl::cmp_less(6, 5U));
|
||||
CHECK_TRUE(etl::cmp_less(5U, 6));
|
||||
CHECK_FALSE(etl::cmp_less(6U, 5));
|
||||
|
||||
// Large unsigned is not less than a small signed.
|
||||
CHECK_FALSE(etl::cmp_less(0xFFFFFFFFU, 1));
|
||||
CHECK_TRUE(etl::cmp_less(1, 0xFFFFFFFFU));
|
||||
}
|
||||
|
||||
//*********************************
|
||||
TEST(test_cmp_greater)
|
||||
{
|
||||
CHECK_TRUE(etl::cmp_greater(2, 1));
|
||||
CHECK_FALSE(etl::cmp_greater(1, 2));
|
||||
CHECK_FALSE(etl::cmp_greater(1, 1));
|
||||
|
||||
CHECK_FALSE(etl::cmp_greater(-1, 0U));
|
||||
CHECK_TRUE(etl::cmp_greater(static_cast<unsigned int>(-1), -1));
|
||||
CHECK_TRUE(etl::cmp_greater(0xFFFFFFFFU, 1));
|
||||
CHECK_FALSE(etl::cmp_greater(1, 0xFFFFFFFFU));
|
||||
}
|
||||
|
||||
//*********************************
|
||||
TEST(test_cmp_less_equal)
|
||||
{
|
||||
CHECK_TRUE(etl::cmp_less_equal(1, 1));
|
||||
CHECK_TRUE(etl::cmp_less_equal(1, 2));
|
||||
CHECK_FALSE(etl::cmp_less_equal(2, 1));
|
||||
|
||||
CHECK_TRUE(etl::cmp_less_equal(-1, static_cast<unsigned int>(-1)));
|
||||
CHECK_FALSE(etl::cmp_less_equal(static_cast<unsigned int>(-1), -1));
|
||||
CHECK_TRUE(etl::cmp_less_equal(5U, 5));
|
||||
CHECK_FALSE(etl::cmp_less_equal(6U, 5));
|
||||
}
|
||||
|
||||
//*********************************
|
||||
TEST(test_cmp_greater_equal)
|
||||
{
|
||||
CHECK_TRUE(etl::cmp_greater_equal(1, 1));
|
||||
CHECK_TRUE(etl::cmp_greater_equal(2, 1));
|
||||
CHECK_FALSE(etl::cmp_greater_equal(1, 2));
|
||||
|
||||
CHECK_FALSE(etl::cmp_greater_equal(-1, static_cast<unsigned int>(-1)));
|
||||
CHECK_TRUE(etl::cmp_greater_equal(static_cast<unsigned int>(-1), -1));
|
||||
CHECK_TRUE(etl::cmp_greater_equal(5, 5U));
|
||||
CHECK_FALSE(etl::cmp_greater_equal(-5, 5U));
|
||||
}
|
||||
|
||||
//*********************************
|
||||
TEST(test_in_range)
|
||||
{
|
||||
// Signed target type.
|
||||
CHECK_TRUE(etl::in_range<int8_t>(0));
|
||||
CHECK_TRUE(etl::in_range<int8_t>(127));
|
||||
CHECK_TRUE(etl::in_range<int8_t>(-128));
|
||||
CHECK_FALSE(etl::in_range<int8_t>(128));
|
||||
CHECK_FALSE(etl::in_range<int8_t>(-129));
|
||||
CHECK_FALSE(etl::in_range<int8_t>(200));
|
||||
|
||||
// Unsigned target type.
|
||||
CHECK_TRUE(etl::in_range<uint8_t>(0));
|
||||
CHECK_TRUE(etl::in_range<uint8_t>(255));
|
||||
CHECK_FALSE(etl::in_range<uint8_t>(256));
|
||||
CHECK_FALSE(etl::in_range<uint8_t>(-1));
|
||||
|
||||
// Unsigned source that would be negative if reinterpreted as signed.
|
||||
CHECK_FALSE(etl::in_range<int8_t>(static_cast<unsigned int>(-1)));
|
||||
CHECK_TRUE(etl::in_range<uint32_t>(static_cast<unsigned int>(-1)));
|
||||
|
||||
// Wider target always contains a narrower value.
|
||||
CHECK_TRUE(etl::in_range<int64_t>(int8_t(-1)));
|
||||
CHECK_TRUE(etl::in_range<int64_t>(uint32_t(0xFFFFFFFFU)));
|
||||
}
|
||||
|
||||
#if ETL_USING_CPP14
|
||||
//*********************************
|
||||
TEST(test_cmp_and_in_range_constexpr)
|
||||
{
|
||||
constexpr bool ce0 = etl::cmp_equal(1, 1);
|
||||
constexpr bool ce1 = etl::cmp_not_equal(-1, static_cast<unsigned int>(-1));
|
||||
constexpr bool ce2 = etl::cmp_less(-1, 0U);
|
||||
constexpr bool ce3 = etl::cmp_greater(static_cast<unsigned int>(-1), -1);
|
||||
constexpr bool ce4 = etl::cmp_less_equal(-1, static_cast<unsigned int>(-1));
|
||||
constexpr bool ce5 = etl::cmp_greater_equal(static_cast<unsigned int>(-1), -1);
|
||||
constexpr bool ce6 = etl::in_range<int8_t>(100);
|
||||
constexpr bool ce7 = etl::in_range<int8_t>(200);
|
||||
|
||||
static_assert(ce0, "cmp_equal constexpr");
|
||||
static_assert(ce1, "cmp_not_equal constexpr");
|
||||
static_assert(ce2, "cmp_less constexpr");
|
||||
static_assert(ce3, "cmp_greater constexpr");
|
||||
static_assert(ce4, "cmp_less_equal constexpr");
|
||||
static_assert(ce5, "cmp_greater_equal constexpr");
|
||||
static_assert(ce6, "in_range true constexpr");
|
||||
static_assert(!ce7, "in_range false constexpr");
|
||||
|
||||
CHECK_TRUE(ce0);
|
||||
CHECK_TRUE(ce6);
|
||||
CHECK_FALSE(ce7);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ETL_HAS_PACKED
|
||||
//*********************************
|
||||
TEST(test_packed)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user