Open utterances-bot opened 2 years ago
For integral addition/subtraction wrote specialized routines which add/subtract "mathematically" and check for overflows. The result type is specified by the caller.
nice @PaltryProgrammer ! Do you have code somewhere? are those functions similar to c++20's cmp_**
?
IMO never overlook a signed/unsigned warning in code. At some point it will probably come back to bite you - hard! Also, casting a signed to an unsigned (or vice-versa) in the code to remove the warnings is often the sign of ill-thought out code which could benefit from a re-think.
Also, IMO, it's never a good idea to use a -ve number to indicate an error and a +ve number as a valid value. Use something like std::optional or std::expected (C++23). Then you can have an unsigned type for the value and still indicate an error condition.
Also, when dealing with .size() or std::size() use a type of size_t - not an int (or unsigned). If you really need a signed type for some reason, then use std::ssize() (C++20) which has a type of std::ptrdiff_t (a signed type).
If you need an integral constant of type size_t then use the suffix uz (C++23) or for an integral constant of type std::ptrdiff_t then use suffix z (C++23) - otherwise previously you'll need to use a static_cast. By default, am integral numeric constant is of type int. This is especially important if using auto to initialise integral variables.
By the way, always remember that for an unsigned type of value 0, then decrementing is valid and although technically UB it will most likely result in a very large +ve number. Consider:
#include <iostream>
int main() {
constexpr size_t init { 5 };
for (size_t t { init }; t <= init; --t)
std::cout << t << ' ';
std::cout << '\n';
}
which displays (VS):
5 4 3 2 1 0
Honestly, it's a real struggle. Like, int8_t and int16_t at a blink will convert up to native int, which is not great. uint8_t and uint16_t at a blink will convert up to native int and therefore also change their sign, which is worse.
Oh yes. Implicit promotion can be a real 'gotcha' and can bite - hard! Unless you're got memory space issues (embedded etc), then why have the hassle of using int16_t etc? int8_t is just char - uint8_t is unsigned char.
Don't forget that a numeric integral literal has a type of int. eg 6, 87, 789 are all of type int. And if you use an int with a type of fewer bits then the other type is promoted to type int. So if a is type uint16_t then a + 6 gives a type int as 6 is int and a is promoted under the rules above. Also note that from the standard:
A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
So even if you add 2 int16_t values, the result type will be of type int - NOT type int16_t as might have been expected. This also applies to say adding say (char)7 to a type char. The result is still a type int. This is also the same for adding say uint8_t to uint8_t. The result type is still int!
As I've read in "Beautiful C++," - just use signed types if you plan to do some arithmetic operations, and signed, in this case will give you correct results. Use unsigned
only for working with sizeof
and other rare cases.
See the C++ Core Guidelines: ES.100 - Don't mix signed and unsigned arithmetic ES.102 - Use signed types for arithmetic
See: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-mix https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-signed
Greetings Kind Regards I do not know how C++ 20 cmp works . If you wish to view my code it is attached . Kind Regards
On Mon, Sep 12, 2022 at 6:49 AM JFT @.***> wrote:
IMO never overlook a signed/unsigned warning in code. At some point it will probably come back to bite you - hard! Also, casting a signed to an unsigned (or vice-versa) in the code to remove the warnings is often the sign of ill-thought out code which could benefit from a re-think.
Also, IMO, it's never a good idea to use a -ve number to indicate an error and a +ve number as a valid value. Use something like std::optional or std::expected (C++23). Then you can have an unsigned type for the value and still indicate an error condition.
Also, when dealing with .size() or std::size() use a type of size_t - not an int (or unsigned). If you really need a signed type for some reason, then use std::ssize() (C++20) which has a type of std::ptrdiff_t (a signed type).
If you need an integral constant of type size_t then use the suffix uz (C++23) or for an integral constant of type std::ptrdiff_t then use suffix z (C++23) - otherwise previously you'll need to use a static_cast. By default, am integral numeric constant is of type int.
By the way, always remember that for an unsigned type of value 0, then decrementing is valid and although technically UB it will most likely result in a very large +ve number. Consider:
include
int main() { constexpr size_t init { 5 };
for (size_t t { init }; t <= init; --t) std::cout << t << ' '; std::cout << '\n';
}
which displays (VS):
5 4 3 2 1 0
— Reply to this email directly, view it on GitHub https://github.com/fenbf/cppstories-discussions/issues/96#issuecomment-1243557883, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALSJS4BZXZRA25QAC4R27G3V54DCTANCNFSM6AAAAAAQKIQXSM . You are receiving this because you were mentioned.Message ID: @.***>
-- Below is my "Signature" apologies if offends "I once put instant coffee into the microwave and went back in time." - Steven Wright "Shut up and calculate" - apparently N. David Mermin possibly Richard Feynman “I want to sing, I want to cry, I want to laugh. Everything together. And jump and dance. The day has arrived — yippee!” - Desmond Tutu “When the green flag drops the bullshit stops!” "It is cheaper to save the world than it is to ruin it." "I must have had lessons" - Reverend Jim Ignatowski / Christopher Lloyd "Dripping water hollows out stone, not through force, but through persistence." - Ovid, Roman poet
extern const string numeric_overflow_msg; extern const string logical_overflow_msg; extern const string integral_subtraction_overflow_msg; extern const string integral_subtraction_assign_overflow_msg; extern const string integral_addition_logical_overflow_msg; extern const string integral_subtraction_logical_overflow_msg;
namespace math_adjunct
{
template
template<size_t rank, bool isSigned>
struct rank_signed_type
{
using type = conditional_t<rank == sizeof(char), char, conditional_t < rank == sizeof(short), conditional_t<isSigned, short, unsigned short>, conditional_t<rank == sizeof(int), conditional_t<isSigned, int, unsigned int>, conditional_t<rank == sizeof(long), conditional_t<isSigned, long, unsigned long>, conditional_t<rank == sizeof(long long), conditional_t<isSigned, long long, unsigned long long>, void>>>>>;
};
template<size_t rank, bool isSigned>
using rank_signed_type_t = rank_signed_type<rank, isSigned>::type;
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
struct ccommon_type
{
static constexpr size_t rank_T = sizeof(T);
static constexpr size_t rank_U = sizeof(U);
static constexpr bool is_signed_T = is_signed_v<T>;
static constexpr bool is_signed_U = is_signed_v<U>;
static constexpr size_t signed_rank = [&]() { if (is_signed_T && !is_signed_U) return rank_T; else if (is_signed_U && !is_signed_T) return rank_U; else return static_cast<size_t>(0); }();
static constexpr size_t unsigned_rank = [&]() { if (is_signed_T && !is_signed_U) return rank_U; else if (is_signed_U && !is_signed_T) return rank_T; else return static_cast<size_t>(0); }();
// same type -> common type
// same rank same signed -> rank_T, signed_T
// same rank different signed -> rank_T*2, signed
// different ranks same signed -> larger rank, signed T
// different ranks different signed
// signed rank < unsigned rank -> unsigned rank*2 signed
// signed rank > unsigned rank -> signed type
using type = conditional_t < is_same_v<T, U>, common_type_t<T, U>, conditional_t < rank_T == rank_U && is_signed_T == is_signed_U, rank_signed_type_t<rank_T, is_signed_T>, conditional_t < rank_T == rank_U && is_signed_T != is_signed_U, rank_signed_type_t<rank_T * 2, true>, conditional_t < rank_T != rank_U && is_signed_T == is_signed_U, rank_signed_type_t < std::max<size_t>(rank_T, rank_U), is_signed_T>, conditional_t <std::cmp_less(signed_rank, unsigned_rank), rank_signed_type_t<unsigned_rank * 2, true>, rank_signed_type_t<signed_rank, true>>>>>>;
};
template<typename T, typename U>
using ccommon_type_t = ccommon_type<T, U>::type;
template<typename T, typename U>
concept CONCEPT_common_type = !is_same_v<void, ccommon_type_t<T, U>>;
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
struct positive_common_type
{
static constexpr size_t rank_T = sizeof(T);
static constexpr size_t rank_U = sizeof(U);
static constexpr bool is_signed_T = is_signed_v<T>;
static constexpr bool is_signed_U = is_signed_v<U>;
static constexpr size_t signed_rank = [&]() { if (is_signed_T && !is_signed_U) return rank_T; else if (is_signed_U && !is_signed_T) return rank_U; else return static_cast<size_t>(0); }();
static constexpr size_t unsigned_rank = [&]() { if (is_signed_T && !is_signed_U) return rank_U; else if (is_signed_U && !is_signed_T) return rank_T; else return static_cast<size_t>(0); }();
// same type -> common type
// same rank same signed -> rank_T, signed T
// same rank different signed -> rank_T, false
// different rank same signed -> larger rank, signed T
// different rank different signed -> larger rank, sign of larger rank
using type = conditional_t < is_same_v<T, U>, common_type_t<T, U>, conditional_t < rank_T == rank_U && is_signed_T == is_signed_U, rank_signed_type_t<rank_T, is_signed_T>, conditional_t < rank_T == rank_U && is_signed_T != is_signed_U, rank_signed_type_t<rank_T, false>, conditional_t < rank_T != rank_U && is_signed_T == is_signed_U, rank_signed_type_t < std::max<size_t>(rank_T, rank_U), is_signed_T >, rank_signed_type_t < std::max<size_t>(rank_T, rank_U), std::cmp_greater(rank_T, rank_U) ? is_signed_T : is_signed_U>>>>>;
};
template<typename T, typename U>
using positive_common_type_t = positive_common_type<T, U>::type;
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U> && signed_integral<T> && signed_integral<U>
struct negative_common_type
{
static constexpr size_t rank_T = sizeof(T);
static constexpr size_t rank_U = sizeof(U);
// same type -> common type
// same rank -> rank_T, true
// different rank -> larger rank, true
using type = conditional_t < is_same_v<T, U>, common_type_t<T, U>, conditional_t < rank_T == rank_U, rank_signed_type_t<rank_T, true>, rank_signed_type_t < std::max<size_t>(rank_T, rank_U), true>>>;
};
template<typename T, typename U>
using negative_common_type_t = negative_common_type<T, U>::type;
template<typename argType> requires CONCEPT_strict_integral<argType>
struct promoted_integral;
// note these are hard coded and do not change automatically per any change in the
// language
//template<> struct promoted_integral<signed char> { using type = int; };
template<> struct promoted_integral<short> { using type = int; };
//template<> struct promoted_integral<unsigned char> { using type = int; };
template<> struct promoted_integral<unsigned short> { using type = int; };
//template<> struct promoted_integral<char> { using type = conditional_t<is_signed_v<char>, int, unsigned int>; };
//template<> struct promoted_integral<wchar_t> { using type = conditional_t<can_hold_range_v<int, wchar_t>, int, unsigned int>; };
//template<> struct promoted_integral<char8_t> { using type = conditional_t<can_hold_range_v<int, char8_t>, int, unsigned int>; };
//template<> struct promoted_integral<char16_t> { using type = conditional_t<can_hold_range_v<int, char16_t>, int, unsigned int>; };
//template<> struct promoted_integral<char32_t> { using type = conditional_t<can_hold_range_v<int, char32_t>, int, unsigned int>; };
template<> struct promoted_integral<int> { using type = int; };
template<> struct promoted_integral<unsigned int> { using type = unsigned int; };
template<> struct promoted_integral<long> { using type = long; };
template<> struct promoted_integral<unsigned long> { using type = unsigned long; };
template<> struct promoted_integral<long long > { using type = long long; };
template<> struct promoted_integral<unsigned long long> { using type = unsigned long long; };
template<typename T>
using promoted_integral_t = promoted_integral<T>::type;
template<typename leftArgType, typename rightArgType> requires CONCEPT_strict_integral<leftArgType>&& CONCEPT_strict_integral<rightArgType>
struct integral_binary_arithmetic_converted { using type = common_type_t<promoted_integral_t<leftArgType>, promoted_integral_t<rightArgType>>; };
template<typename castType, typename valueType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<valueType>
struct is_convertible_type
{
static constexpr bool value = is_same_v<valueType, castType>
||
(is_signed_v<castType> && is_signed_v<valueType> && sizeof(castType) >= sizeof(valueType))
||
(is_signed_v<castType> && !is_signed_v<valueType> && sizeof(castType) > sizeof(valueType))
||
(!is_signed_v<castType> && !is_signed_v<valueType> && sizeof(castType) >= sizeof(valueType));
};
template<typename castType, typename valueType>
constexpr bool is_convertible_type_v = is_convertible_type<castType, valueType>::value;
template<typename castType, typename valueType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<valueType>
bool is_convertible_value(valueType valueArg);
template<typename castType, typename argType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<argType>
castType cast(argType value);
template<typename argType> requires CONCEPT_strict_integral<argType>
struct climits
{
using arg_type = argType;
arg_type lower;
arg_type upper;
climits();
template<typename lowerType, typename upperType>
climits(lowerType lower, upperType upper);
};
// detection of overflow in unsigned integral addition and subtraction
// https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap
template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr bool addition_numeric_overflow(augendType augend, addendType addend);
template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr bool subtraction_numeric_overflow(minuendType minuend, subtrahendType subtrahend);
template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr bool addition_overflow(augendType augend, addendType addend, MATH_CLIMITS_TYPE(sumType) limits=MATH_CLIMITS(sumType));
template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr bool subtraction_overflow(minuendType minuend, subtrahendType subtrahend, MATH_CLIMITS_TYPE(differenceType) limits=MATH_CLIMITS(differenceType));
// detection of overflow in signed integral addition and subtraction
// https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow
template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr optional<sumType> addition(augendType augend, addendType addend, MATH_CLIMITS_TYPE(sumType) limits = MATH_CLIMITS(sumType), bool throw_on_overflow = true);
template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr optional<differenceType> subtraction(minuendType minuend, subtrahendType subtrahend, MATH_CLIMITS_TYPE(differenceType) limits = MATH_CLIMITS(differenceType), bool throw_on_overflow = true);
template<typename T, typename U> requires CONCEPT_common_type<T, U>
constexpr ccommon_type_t<T, U> min(T left, U right);
template<typename T, typename U> requires CONCEPT_common_type<T, U>
constexpr ccommon_type_t<T, U> max(T left, U right);
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
constexpr positive_common_type_t<T, U> positive_min(T left, U right);
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
constexpr positive_common_type_t<T, U> positive_max(T left, U right);
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U> && signed_integral<T> && signed_integral<U>
constexpr negative_common_type_t<T, U> negative_min(T left, U right);
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>&& signed_integral<T>&& signed_integral<U>
constexpr negative_common_type_t<T, U> negative_max(T left, U right);
#ifdef _UNDEFINED_
[0] signed char or signed short can be converted to int;
[1] unsigned char, char8_t(since C++20) or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;
[2] char can be converted to int or unsigned int depending on the underlying type : signed char or unsigned char(see above);
[3] wchar_t, char8_t, char16_t, and char32_t(since C++11) can be converted to the first type from the following list able to hold their entire value range : int, unsigned int, long, unsigned long, long long, unsigned long long(since C++11);
#endif
#ifdef _UNDEFINED_
[0] signed char or signed short can be converted to int;
[1] unsigned char, char8_t(since C++20) or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;
[2] char can be converted to int or unsigned int depending on the underlying type : signed char or unsigned char(see above);
[3] wchar_t, char8_t, char16_t, and char32_t(since C++11) can be converted to the first type from the following list able to hold their entire value range : int, unsigned int, long, unsigned long, long long, unsigned long long(since C++11);
#endif
#ifdef _UNDEFINED_
left arg right arg condition
addition subtraction
signed signed a signed rank greater than each exists
signed unsigned a signed rank greater than each exists
unsigned signed a signed rank greater than each exists
unsigned unsigned an unsigned rank greater than each exists
#endif
#ifdef _UNDEFINED_
// identical addition and subtraction requires syntax
// sum = augend + addend
//augendType addendType sumType requires
signed signed signed sizeof sumType >= sizeof augendType && sizeof sumType >= sizeof addendType
signed signed unsigned false
signed unsigned signed sizeof sumType >= sizeof augendType && sizeof sumType > sizeof addendType
unsigned signed signed sizeof sumType > sizeof augendType && sizeof sumType >= sizeof addendType
signed unsigned unsigned false
unsigned signed unsigned false
unsigned unsigned signed sizeof sumType > sizeof augendType && sizeof sumType > sizeof addendType
unsigned unsigned unsigned sizeof sumType >= sizeof augendType && sizeof sumType >= sizeof addendType
// difference = minuend - subtrahend
// minuendType subtrahendType differentType
signed signed signed sizeof differenceType >= sizeof minuendType && sizeof differenceType >= sizeof subtrahendType
signed signed unsigned false
signed unsigned signed sizeof differenceType >= sizeof minuendType && sizeof differenceType > sizeof subtrahendType
unsigned signed signed sizeof differenceType > sizeof minuendType && sizeof differenceType >= sizeof subtrahendType
signed unsigned unsigned false
unsigned signed unsigned false
unsigned unsigned signed sizeof differenceType > sizeof minuendType && sizeof differenceType > sizeof subtrahendType
unsigned unsigned unsigned sizeof differenceType >= sizeof minuendType && sizeof differenceType >= sizeof subtrahendType
#endif
#ifdef _UNDEFINED_
can cast conditions
arg type result type
signed signed
size arg type <= size result type
-> true
size arg type > size result type
arg < 0
->arg >= result type::min
arg > 0
->arg <= result type::max
signed unsigned
size arg type < size result type
->arg >= 0
size arg type == size result type
->arg >= 0
size arg type > size result type
arg < 0
-> false
arg >= 0
->arg <= result type::max
unsigned signed
->arg <= result type::max
unsigned unsigned
->arg <= result type::max
#endif
template<typename leftArgType, typename rightArgType> struct greater_rank_binary_operator {};
template<> struct greater_rank_binary_operator<short, short> { using type = int; };
template<> struct greater_rank_binary_operator<short, unsigned short> { using type = int; };
template<> struct greater_rank_binary_operator<short, int> { using type = long long; };
template<> struct greater_rank_binary_operator<short, unsigned int> { using type = long long; };
template<> struct greater_rank_binary_operator<short, long> { using type = long long; };
template<> struct greater_rank_binary_operator<short, unsigned long> { using type = long long; };
template<> struct greater_rank_binary_operator<short, long long> { using type = void; };
template<> struct greater_rank_binary_operator<short, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned short, short> { using type = int; };
template<> struct greater_rank_binary_operator<unsigned short, unsigned short> { using type = unsigned int; };
template<> struct greater_rank_binary_operator<unsigned short, int> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned short, unsigned int> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned short, long> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned short, unsigned long> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned short, long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned short, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<int, short> { using type = long long; };
template<> struct greater_rank_binary_operator<int, unsigned short> { using type = long long; };
template<> struct greater_rank_binary_operator<int, int> { using type = long long; };
template<> struct greater_rank_binary_operator<int, unsigned int> { using type = long long; };
template<> struct greater_rank_binary_operator<int, long> { using type = long long; };
template<> struct greater_rank_binary_operator<int, unsigned long> { using type = long long; };
template<> struct greater_rank_binary_operator<int, long long> { using type = void; };
template<> struct greater_rank_binary_operator<int, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned int, short> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned int, unsigned short> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned int, int> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned int, unsigned int> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned int, long> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned int, unsigned long> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned int, long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned int, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<long, short> { using type = long long; };
template<> struct greater_rank_binary_operator<long, unsigned short> { using type = long long; };
template<> struct greater_rank_binary_operator<long, int> { using type = long long; };
template<> struct greater_rank_binary_operator<long, unsigned int> { using type = long long; };
template<> struct greater_rank_binary_operator<long, long> { using type = long long; };
template<> struct greater_rank_binary_operator<long, unsigned long> { using type = long long; };
template<> struct greater_rank_binary_operator<long, long long> { using type = void; };
template<> struct greater_rank_binary_operator<long, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long, short> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned long, unsigned short> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned long, int> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned long, unsigned int> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned long, long> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned long, unsigned long> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned long, long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<long long, short> { using type = void; };
template<> struct greater_rank_binary_operator<long long, unsigned short> { using type = void; };
template<> struct greater_rank_binary_operator<long long, int> { using type = void; };
template<> struct greater_rank_binary_operator<long long, unsigned int> { using type = void; };
template<> struct greater_rank_binary_operator<long long, long> { using type = void; };
template<> struct greater_rank_binary_operator<long long, unsigned long> { using type = void; };
template<> struct greater_rank_binary_operator<long long, long long> { using type = void; };
template<> struct greater_rank_binary_operator<long long, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, short> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, unsigned short> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, int> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, unsigned int> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, unsigned long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, unsigned long long> { using type = void; };
template<typename T, typename R> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<R>
struct cinteger
{
using result_type = R;
T value;
math_adjunct::climits<R> limits;
cinteger(T _value) : value(_value), limits(numeric_limits<R>::min(), numeric_limits<R>::max()) {}
cinteger(T _value, R _lower_limit, R _upper_limit) : value(_value), limits(_lower_limit, _upper_limit) {}
};
template<typename T, typename R, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<R>&& CONCEPT_strict_integral<U>
R operator + (cinteger<T, R> augend, U addend)
{
auto sum = addition(augend.value, addend, augend.limits);
return *sum;
}
template<typename T, typename R, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<R>&& CONCEPT_strict_integral<U>
R operator - (cinteger<T, R> minuend, U subtrahend)
{
auto difference = subtraction(minuend.value, subtrahend, minuend.limits);
return *difference;
}
template<typename T> requires CONCEPT_strict_integral<T>
cinteger<T, T> make_cinteger(T value)
{
return cinteger<T, T>(value);
}
template<typename T, typename R> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<R>
cinteger<T, R> make_cinteger(T value, R lower_limit, R upper_limit)
{
return cinteger<T, R>(value, lower_limit, upper_limit);
}
}
namespace math_adjunct
{
template
template<typename integralType> requires signed_integral<integralType>
constexpr make_unsigned_t<integralType> absolute_value(integralType _value)
{
using result_type = make_unsigned_t<integralType>;
if (_value < 0)
{
if (_value != numeric_limits<integralType>::min()) return -_value;
else
{
++_value;
result_type absolute_value = -_value;
return absolute_value + literal::_1u;
}
}
else return _value;
}
template<typename integralType> requires unsigned_integral<integralType>
constexpr integralType absolute_value(integralType value) { return value; }
template<typename castType, typename valueType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<valueType>
bool is_convertible_value(valueType valueArg)
{
bool value = valueArg == 0
||
// cast type value type
// signed signed
(
signed_integral<castType> && signed_integral<valueType>
&&
(
(valueArg < 0 && valueArg >= numeric_limits<castType>::min())
||
(valueArg >= 0 && valueArg <= numeric_limits<castType>::max())
)
)
||
(
// unsigned signed
unsigned_integral<castType> && signed_integral<valueType>
&&
(
valueArg >= 0 && valueArg <= numeric_limits<castType>::max()
)
)
||
(
// signed unsigned
signed_integral<castType> && unsigned_integral<valueType>
&&
valueArg <= numeric_limits<castType>::max()
)
||
(
// unsigned unsigned
unsigned_integral<castType> && unsigned_integral<valueType>
&&
valueArg <= numeric_limits<castType>::max()
);
return value;
}
template<typename castType, typename argType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<argType>
castType cast(argType value)
{
auto _can_cast = is_convertible_value<castType, argType>(value);
MAKER_ASSERT(_can_cast)
return static_cast<castType>(value);
}
template<typename argType> requires CONCEPT_strict_integral<argType>
climits<argType>::climits() : lower(numeric_limits<arg_type>::min()), upper(numeric_limits<arg_type>::max()) {}
template<typename argType> requires CONCEPT_strict_integral<argType>
template<typename lowerType, typename upperType>
climits<argType>::climits(lowerType lower, upperType upper) : lower(lower), upper(upper) { CALLER_ASSERT(is_convertible_value<argType>(lower) && is_convertible_value<argType>(upper)) }
// detection of overflow in unsigned integral addition and subtraction
// https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap
template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr bool addition_numeric_overflow(augendType augend, addendType addend)
{
auto _can_cast = is_convertible_value<sumType>(augend) && is_convertible_value<sumType>(addend);
if (!_can_cast) return true;
return numeric_limits<sumType>::max() - static_cast<sumType>(augend) < static_cast<sumType>(addend);
}
template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr bool subtraction_numeric_overflow(minuendType minuend, subtrahendType subtrahend)
{
auto _can_cast = is_convertible_value<differenceType>(minuend) && is_convertible_value<differenceType>(subtrahend);
if (!_can_cast) return true;
return minuend < subtrahend;
}
template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr bool addition_overflow(augendType augend, addendType addend, MATH_CLIMITS_TYPE(sumType) limits)
{
auto _can_cast = is_convertible_value<sumType>(augend) && is_convertible_value<sumType>(addend);
if (!_can_cast) return true;
auto overflow = addition_numeric_overflow<sumType>(augend, addend) || !(limits.upper >= static_cast<sumType>(augend) + static_cast<sumType>(addend)) || !(limits.lower <= static_cast<sumType>(augend) + static_cast<sumType>(addend));
return overflow;
}
template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr bool subtraction_overflow(minuendType minuend, subtrahendType subtrahend, MATH_CLIMITS_TYPE(differenceType) limits)
{
auto _can_cast = is_convertible_value<differenceType>(minuend) && is_convertible_value<differenceType>(subtrahend);
if (!_can_cast) return true;
auto overflow = subtraction_numeric_overflow<differenceType>(minuend, subtrahend) || !(limits.upper >= static_cast<differenceType>(minuend) - static_cast<differenceType>(subtrahend)) || !(limits.lower <= static_cast<differenceType>(minuend) - static_cast<differenceType>(subtrahend));
return overflow;
}
// detection of overflow in signed integral addition and subtraction
// https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow
template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr optional<sumType> addition(augendType augend, addendType addend, MATH_CLIMITS_TYPE(sumType) limits, bool throw_on_overflow)
{
bool _can_cast = is_convertible_value<sumType>(augend)&& is_convertible_value<sumType>(addend);
if (!_can_cast) { if (throw_on_overflow) throw OVERFLOW_ERROR else return optional<sumType>(); }
else
{
bool _overflow = addition_overflow(augend, addend, limits);
if (_overflow) { if (throw_on_overflow) throw OVERFLOW_ERROR else return optional<sumType>(); }
return optional<sumType>(static_cast<sumType>(augend) + static_cast<sumType>(addend));
}
}
template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr optional<differenceType> subtraction(minuendType minuend, subtrahendType subtrahend, MATH_CLIMITS_TYPE(differenceType) limits, bool throw_on_overflow)
{
bool _can_cast = is_convertible_value<differenceType>(minuend) && is_convertible_value<differenceType>(subtrahend);
if (!_can_cast) { if (throw_on_overflow) throw OVERFLOW_ERROR else return optional<differenceType>(); }
else
{
bool _overflow = subtraction_overflow(minuend, subtrahend, limits);
if (_overflow) { if (throw_on_overflow) throw OVERFLOW_ERROR else return optional<differenceType>(); }
return optional<differenceType>(static_cast<differenceType>(minuend) - static_cast<differenceType>(subtrahend));
}
}
template<typename T, typename U> requires CONCEPT_common_type<T, U>
constexpr ccommon_type_t<T, U> min(T left, U right)
{
using _common_type = ccommon_type_t<T, U>;
return std::min(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_common_type<T, U>
constexpr ccommon_type_t<T, U> max(T left, U right)
{
using _common_type = ccommon_type_t<T, U>;
return std::max(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
constexpr positive_common_type_t<T, U> positive_min(T left, U right)
{
MAKER_ASSERT(left >= 0 && right >= 0)
using _common_type = positive_common_type_t<T, U>;
return std::min(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
constexpr positive_common_type_t<T, U> positive_max(T left, U right)
{
MAKER_ASSERT(left >= 0 && right >= 0)
using _common_type = positive_common_type_t<T, U>;
return std::max(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U> && signed_integral<T> && signed_integral<U>
constexpr negative_common_type_t<T, U> negative_min(T left, U right)
{
MAKER_ASSERT(left <= 0 && right <= 0)
using _common_type = negative_common_type_t<T, U>;
return std::min(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>&& signed_integral<T>&& signed_integral<U>
constexpr negative_common_type_t<T, U> negative_max(T left, U right)
{
MAKER_ASSERT(left <= 0 && right <= 0)
using _common_type = negative_common_type_t<T, U>;
return std::max(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
}
Integer Conversions and Safe Comparisons in C++20 - C++ Stories
Sometimes, If you mix different integer types in an expression, you might end up with tricky cases. For example, comparing long with size_t might give different results than long with unsigned short. C++20 brings some help, and there’s no need to learn all the complex rules :) Conversion and Ranks Let’s have a look at two comparisons:
https://www.cppstories.com/2022/safe-int-cmp-cpp20/