Skip to content

Commit

Permalink
[safe_op] Add Safe::cast<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
D4N committed Jun 4, 2019
1 parent 04d770a commit 7ce0e66
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
100 changes: 100 additions & 0 deletions src/safe_op.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include <limits>
#include <stdexcept>
#include <type_traits>

#ifdef _MSC_VER
#include <Intsafe.h>
Expand Down Expand Up @@ -331,4 +332,103 @@ namespace Safe
return num < 0 ? -num : num;
}

namespace Internal
{
template <typename from, typename to, typename = void>
struct is_safely_convertible : std::false_type
{
static_assert(std::is_integral<from>::value&& std::is_integral<to>::value,
"from and to must both be integral types");
};

template <typename from, typename to>
struct is_safely_convertible<
from, to,
typename std::enable_if<(std::numeric_limits<from>::max() <= std::numeric_limits<to>::max()) &&
(std::numeric_limits<from>::min() >= std::numeric_limits<to>::min())>::type>
: std::true_type
{
};

template <typename T, typename U, typename = void>
struct have_same_signedness : std::false_type
{
static_assert(std::is_integral<T>::value&& std::is_integral<U>::value,
"T and U must both be integral types");
};

// SFINAE overload for (T signed and U signed) or (T unsigned and U unsigned)
// note: !a != !b == a XOR b (for a, b bool)
template <typename T, typename U>
struct have_same_signedness<
T, U, typename std::enable_if<!((!std::is_signed<T>::value) != (!std::is_signed<U>::value))>::type>
: std::true_type
{
};

} // namespace Internal

#ifdef PARSED_BY_DOXYGEN
/// Convert a value of type U to type T without causing over- or underflows.
///
/// @throw std::overflow_error When `value` is outside the representable range of T
template <typename T, typename U>
constexpr T cast(U value)
{
}
#else
// trivial version: T can represent all values that U can
template <typename T, typename U>
constexpr typename std::enable_if<Internal::is_safely_convertible<U, T>::value, T>::type cast(U value) noexcept
{
return static_cast<T>(value);
}

// T cannot represent all values that U can,
// but T and U are either both signed or unsigned
// => can compare them without any issues
template <typename T, typename U>
constexpr typename std::enable_if<
(!Internal::is_safely_convertible<U, T>::value) && Internal::have_same_signedness<T, U>::value, T>::type
cast(U value)
{
return (value <= std::numeric_limits<T>::max()) && (value >= std::numeric_limits<T>::min())
? static_cast<T>(value)
: throw std::overflow_error("Cannot convert number without over or underflow");
}

// - T cannot represent all values that U can,
// - T is signed, U is unsigned
// => must cast them compare them without any issues
template <typename T, typename U>
constexpr typename std::enable_if<(!Internal::is_safely_convertible<U, T>::value) && std::is_signed<T>::value &&
std::is_unsigned<U>::value,
T>::type
cast(U value)
{
static_assert(std::numeric_limits<T>::max() < std::numeric_limits<U>::max(),
"maximum value of T must be smaller than the maximum value of U");
// U unsigned, T signed => T_MAX < U_MAX
return (value <= static_cast<U>(std::numeric_limits<T>::max())) && (value >= 0)
? static_cast<T>(value)
: throw std::overflow_error("Cannot convert number without over or underflow");
}

// - T cannot represent all values that U can,
// - T is unsigned, U is signed
// => must cast them compare them without any issues
template <typename T, typename U>
constexpr typename std::enable_if<(!Internal::is_safely_convertible<U, T>::value) && std::is_unsigned<T>::value &&
std::is_signed<U>::value,
T>::type
cast(U value)
{
// U signed, T unsigned => T_MAX < U_MAX
return (value <= std::numeric_limits<T>::max()) && (value >= std::numeric_limits<T>::min())
? static_cast<T>(value)
: throw std::overflow_error("Cannot convert number without over or underflow");
}

#endif // PARSED_BY_DOXYGEN

} // namespace Safe
92 changes: 92 additions & 0 deletions unitTests/test_safe_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,95 @@ TEST(safeAbs, checkValues)
}
ASSERT_EQ(Safe::abs(std::numeric_limits<int>::min()), std::numeric_limits<int>::max());
}

//
// sanity checks of is_safely_convertible
//
static_assert(si::is_safely_convertible<uint8_t, uint16_t>::value, "uint8_t must be always convertible to uint16_t");
static_assert(!si::is_safely_convertible<uint16_t, uint8_t>::value, "uint16_t must not always convertible to uint8_t");

static_assert(si::is_safely_convertible<uint8_t, int16_t>::value, "uint8_t must be always convertible to int16_t");
static_assert(!si::is_safely_convertible<int16_t, uint8_t>::value, "int16_t must not always be convertible to uint8_t");

//
// sanity checks for Safe::cast<>
//
static_assert(si::have_same_signedness<uint16_t, uint8_t>::value, "uint8_t must have the same signedness as uint16_t");
static_assert(!si::have_same_signedness<int16_t, uint8_t>::value,
"uint8_t must have a different signedness as int16_t");

//
// sanity checks for Safe::cast<>
//
static_assert(std::is_same<decltype(Safe::cast<int>(static_cast<short>(8))), int>::value,
"Return value of Safe::cast<int>(short) must be int");
static_assert(std::is_same<decltype(Safe::cast<int>(8ull)), int>::value,
"Return value of Safe::cast<int>(unsigned long long) must be int");

TEST(SafeCast, TriviallyConvertible)
{
ASSERT_EQ(Safe::cast<int>(static_cast<short>(5)), 5);
}

//
// Test Safe::cast to a signed integer
//
template <typename T>
struct SafeCastToInt16 : public ::testing::Test
{
};

using BiggerRangeThanInt16 = ::testing::Types<uint16_t, int32_t, uint32_t, int64_t, uint64_t>;

TYPED_TEST_CASE(SafeCastToInt16, BiggerRangeThanInt16);

TYPED_TEST(SafeCastToInt16, ThrowsForTooLargeValue)
{
ASSERT_THROW(Safe::cast<int16_t>(static_cast<TypeParam>(std::numeric_limits<int16_t>::max()) + 1),
std::overflow_error);
}

TYPED_TEST(SafeCastToInt16, ThrowsForTooSmallValue)
{
if (std::is_signed<TypeParam>::value) {
ASSERT_THROW(Safe::cast<int16_t>(static_cast<TypeParam>(std::numeric_limits<int16_t>::min()) - 1),
std::overflow_error);
}
}

TYPED_TEST(SafeCastToInt16, DoesNotThrowForRepresentableValue)
{
constexpr TypeParam test_value = std::numeric_limits<int16_t>::max() - 1;
ASSERT_EQ(Safe::cast<int16_t>(test_value), test_value);
}

//
// Test Safe::cast to an unsigned integer
//
template <typename T>
struct SafeCastToUInt32 : public ::testing::Test
{
};

using BiggerRangeThanUInt32 = ::testing::Types<int64_t, uint64_t>;

TYPED_TEST_CASE(SafeCastToUInt32, BiggerRangeThanUInt32);

TYPED_TEST(SafeCastToUInt32, ThrowsForTooLargeValue)
{
ASSERT_THROW(Safe::cast<uint32_t>(static_cast<TypeParam>(std::numeric_limits<uint32_t>::max()) + 1),
std::overflow_error);
}

TYPED_TEST(SafeCastToUInt32, DoesNotThrowForRepresentableValue)
{
constexpr TypeParam test_value = std::numeric_limits<uint32_t>::max() - 1;
ASSERT_EQ(Safe::cast<uint32_t>(test_value), test_value);
}

TYPED_TEST(SafeCastToUInt32, ThrowsForTooSmallValue)
{
if (std::is_signed<TypeParam>::value) {
ASSERT_THROW(Safe::cast<uint32_t>(static_cast<TypeParam>(-1)), std::overflow_error);
}
}

0 comments on commit 7ce0e66

Please sign in to comment.