-
Notifications
You must be signed in to change notification settings - Fork 278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Safe::cast<T> #898
base: old-master
Are you sure you want to change the base?
Add Safe::cast<T> #898
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,9 +29,16 @@ | |
|
||
#include <limits> | ||
#include <stdexcept> | ||
#include <type_traits> | ||
|
||
#ifdef _MSC_VER | ||
#include <Intsafe.h> | ||
|
||
// MSVC is stupid and pollutes the global namespace with max() and min() macros | ||
// that break std::numeric_limits<T>::max() and min() | ||
#undef max | ||
#undef min | ||
|
||
#endif | ||
|
||
/*! | ||
|
@@ -331,4 +338,117 @@ namespace Safe | |
return num < 0 ? -num : num; | ||
} | ||
|
||
namespace Internal | ||
{ | ||
// metafunction to determine whether the integral type `from_t` can be safely converted to the type `to_t` | ||
// without causing over or underflows. | ||
template <typename from_t, typename to_t, typename = void> | ||
struct is_safely_convertible : std::false_type | ||
{ | ||
// clang-format off | ||
static_assert(std::is_integral<from_t>::value && std::is_integral<to_t>::value, | ||
"from_t and to_t must both be integral types"); | ||
// clang-format on | ||
}; | ||
|
||
// overload of is_safely_convertible for `from_t` being safely convertible to `to_t` | ||
template <typename from_t, typename to_t> | ||
struct is_safely_convertible< | ||
from_t, to_t, | ||
typename std::enable_if<((std::numeric_limits<from_t>::max() <= std::numeric_limits<to_t>::max()) && | ||
(std::numeric_limits<from_t>::min() >= std::numeric_limits<to_t>::min()))>::type> | ||
: std::true_type | ||
{ | ||
// clang-format off | ||
static_assert(std::is_integral<from_t>::value && std::is_integral<to_t>::value, | ||
"from_t and to_t must both be integral types"); | ||
// clang-format on | ||
}; | ||
|
||
template <typename T, typename U, typename = void> | ||
struct have_same_signedness : std::false_type | ||
{ | ||
// clang-format off | ||
static_assert(std::is_integral<T>::value && std::is_integral<U>::value, | ||
"T and U must both be integral types"); | ||
// clang-format on | ||
}; | ||
|
||
// SFINAE overload for (T signed and U signed) or (T unsigned and U unsigned) | ||
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 | ||
{ | ||
// clang-format off | ||
static_assert(std::is_integral<T>::value && std::is_integral<U>::value, | ||
"T and U must both be integral types"); | ||
// clang-format on | ||
}; | ||
|
||
} // 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())) | ||
? 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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is possible that
|
||
? static_cast<T>(value) | ||
: throw std::overflow_error("Cannot convert number without over or underflow"); | ||
} | ||
|
||
#endif // PARSED_BY_DOXYGEN | ||
|
||
} // namespace Safe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should define
NOMINMAX
, see https://stackoverflow.com/questions/4913922/possible-problems-with-nominmax-on-visual-c.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is actually already defined in
cmake/compilerFlags.cmake
. Although it seems it is only defined for MSVC. @D4N maybe you had issues with MinGW ?