Here's my approach by doing the steps:
Find the superior type (the type that would yield if an operation was performed)
promote the type to the next type according to your rules 1-4
Perform the addition while casting both sides to the promoted type.
The superior type can be found by following the general rules:
floating point + anything -> floating point
if left and right have the same bit size choose larger one (unsigned beats signed)
else choose where sizeof()
yields the largest
the last 2 steps can be guaranteed (and simplified) by making a helper struct which returns the larger type (ab)using std::numerical_limits<T>::digits
which neatly does exactly what we want (also regarding the signed / unsignedness) since:
This will work for all arithmetic types accordingly.
template<typename T, typename U>
struct larger_arithmetic_type {
static_assert(std::is_arithmetic_v<T>, "T must be arithmetic");
static_assert(std::is_arithmetic_v<U>, "U must be arithmetic");
using type = typename std::conditional_t<(std::numeric_limits<T>::digits < std::numeric_limits<U>::digits), U, T>;
};
template<typename T, typename U>
using larger_arithmetic_type_t = typename larger_arithmetic_type<T, U>::type;
With this we can enable a struct arithmetic_superior_type
(following said general rules) to find the superior type from integers and/or floating point numbers:
template<typename T, typename U>
struct arithmetic_superior_type {
using type = typename
std::conditional_t<std::is_floating_point_v<T> && std::is_floating_point_v<U>, larger_arithmetic_type_t<T, U>,
std::conditional_t<std::is_floating_point_v<T>, T,
std::conditional_t<std::is_floating_point_v<U>, U,
larger_arithmetic_type_t<T, U>>>>;
};
template<typename T, typename U>
using arithmetic_superior_type_t = typename arithmetic_superior_type<T, U>::type;
Therefore arithmetic_seperior_type_t<T, U>
returns the type that +
, -
, *
and /
between T
and U
would yield:
arithmetic_superior_type_t<std::int32_t, float> a; //-> float
arithmetic_superior_type_t<std::uint32_t, std::int32_t> b; //-> std::uint32_t
arithmetic_superior_type_t<std::uint32_t, std::uint32_t> c; //-> std::uint32_t
arithmetic_superior_type_t<std::uint64_t, std::uint32_t> d; //-> std::uint64_t
arithmetic_superior_type_t<float, double> e; //-> double
arithmetic_superior_type_t<std::uint16_t, std::int64_t> f; //-> std::int64_t
Now as you said, this type alone is not enough. Overflow is possible therefore promote_superior_type
is step 2 to receive the promotion of the superior type from T
and U
- a type which will definetely hold any addition result:
template<typename T, typename U>
struct promote_superior_type {
using superior_type = arithmetic_superior_type_t<T, U>;
using type = typename
std::conditional_t<(sizeof(T) == 8u || sizeof(U) == 8u), double,
std::conditional_t<std::is_floating_point_v<superior_type>, superior_type,
std::conditional_t<(std::numeric_limits<superior_type>::digits < std::numeric_limits<std::int16_t>::digits), std::conditional_t<std::is_signed_v<superior_type>, std::int16_t, std::uint16_t>,
std::conditional_t<(std::numeric_limits<superior_type>::digits < std::numeric_limits<std::int32_t>::digits), std::conditional_t<std::is_signed_v<superior_type>, std::int32_t, std::uint32_t>,
std::conditional_t<(std::numeric_limits<superior_type>::digits < std::numeric_limits<std::int64_t>::digits), std::conditional_t<std::is_signed_v<superior_type>, std::int64_t, std::uint64_t>, double>>>>>;
};
template<typename T, typename U>
using promote_superior_type_t = typename promote_superior_type<T, U>::type;
Finally the add<T, U>
function can be added, step 3:
template<typename T, typename U, typename R = promote_superior_type_t<T, U>>
constexpr R add(T a, U b) {
return static_cast<R>(a) + static_cast<R>(b);
}
That's all that's needed. static_asserting
for correct expected output considering every possible type matchup:
//8_t + U
auto add_i8_i8 = add(std::int8_t(10), std::int8_t(10)); //i8 + i8 -> i16
auto add_i8_u8 = add(std::int8_t(10), std::uint8_t(10)); //i8 + u8 -> u16
auto add_u8_u8 = add(std::uint8_t(10), std::uint8_t(10)); //u8 + u8 -> u16
auto add_i8_i16 = add(std::int8_t(10), std::int16_t(10)); //i8 + i16 -> i32
auto add_i8_u16 = add(std::int8_t(10), std::uint16_t(10)); //i8 + u16 -> u32
auto add_u8_u16 = add(std::uint8_t(10), std::uint16_t(10)); //u8 + u16 -> u32
auto add_i8_i32 = add(std::int8_t(10), std::int32_t(10)); //i8 + i32 -> i64
auto add_i8_u32 = add(std::int8_t(10), std::uint32_t(10)); //i8 + u32 -> u64
auto add_u8_u32 = add(std::uint8_t(10), std::uint32_t(10)); //u8 + u32 -> u64
auto add_i8_i64 = add(std::int8_t(10), std::int64_t(10)); //i8 + i64 -> d64
auto add_i8_u64 = add(std::int8_t(10), std::uint64_t(10)); //i8 + u64 -> d64
auto add_u8_u64 = add(std::uint8_t(10), std::uint64_t(10)); //u8 + u64 -> d64
auto add_i8_f32 = add(std::int8_t(10), float(10)); //i8 + f32 -> f32
auto add_u8_f32 = add(std::uint8_t(10), float(10)); //u8 + f32 -> f32
auto add_i8_d64 = add(std::int8_t(10), double(10)); //i8 + d64 -> d64
auto add_u8_d64 = add(std::uint8_t(10), double(10)); //u8 + d64 -> d64
//16_t + U
auto add_i16_i16 = add(std::int16_t(10), std::int16_t(10)); //i16 + i16 -> i32
auto add_i16_u16 = add(std::int16_t(10), std::uint16_t(10)); //i16 + u16 -> u32
auto add_u16_u16 = add(std::uint16_t(10), std::uint16_t(10)); //u16 + u16 -> u32
auto add_i16_i32 = add(std::int16_t(10), std::int32_t(10)); //i16 + i32 -> i64
auto add_i16_u32 = add(std::int16_t(10), std::uint32_t(10)); //i16 + u32 -> u64
auto add_u16_u32 = add(std::uint16_t(10), std::uint32_t(10)); //u16 + u32 -> u64
auto add_i16_i64 = add(std::int16_t(10), std::int64_t(10)); //i16 + i64 -> d64
auto add_i16_u64 = add(std::int16_t(10), std::uint64_t(10)); //i16 + u64 -> d64
auto add_u16_u64 = add(std::uint16_t(10), std::uint64_t(10)); //u16 + u64 -> d64
auto add_i16_f32 = add(std::int16_t(10), float(10)); //i16 + f32 -> f32
auto add_u16_f32 = add(std::uint16_t(10), float(10)); //u16 + f32 -> f32
auto add_i16_d64 = add(std::int16_t(10), double(10)); //i16 + d64 -> d64
auto add_u16_d64 = add(std::uint16_t(10), double(10)); //u16 + d64 -> d64
//32_t + U
auto add_i32_i32 = add(std::int32_t(10), std::int32_t(10)); //i32 + i32 -> i64
auto add_i32_u32 = add(std::int32_t(10), std::uint32_t(10)); //i32 + u32 -> u64
auto add_u32_u32 = add(std::uint32_t(10), std::uint32_t(10)); //u32 + u32 -> u64
auto add_i32_i64 = add(std::int32_t(10), std::int64_t(10)); //i32 + i64 -> d64
auto add_i32_u64 = add(std::int32_t(10), std::uint64_t(10)); //i32 + u64 -> d64
auto add_u32_u64 = add(std::uint32_t(10), std::uint64_t(10)); //u32 + u64 -> d64
auto add_i32_f32 = add(std::int32_t(10), float(10)); //i32 + f32 -> f32
auto add_u32_f32 = add(std::uint32_t(10), float(10)); //u32 + f32 -> f32
auto add_i32_d64 = add(std::int32_t(10), double(10)); //i32 + d64 -> d64
auto add_u32_d64 = add(std::uint32_t(10), double(10)); //u32 + d64 -> d64
//64_t + U
auto add_i64_i64 = add(std::int64_t(10), std::int64_t(10)); //i64 + i64 -> d64
auto add_i64_u64 = add(std::int64_t(10), std::uint64_t(10)); //i64 + u64 -> d64
auto add_u64_u64 = add(std::uint64_t(10), std::uint64_t(10)); //u64 + u64 -> d64
auto add_i64_f32 = add(std::int64_t(10), float(10)); //i64 + f32 -> d64
auto add_u64_f32 = add(std::uint64_t(10), float(10)); //u64 + f32 -> d64
auto add_i64_d64 = add(std::int64_t(10), double(10)); //i64 + d64 -> d64
auto add_u64_d64 = add(std::uint64_t(10), double(10)); //u64 + d64 -> d64
static_assert(std::is_same_v<decltype(add_i8_i8), std::int16_t>, "");
static_assert(std::is_same_v<decltype(add_i8_u8), std::uint16_t>, "");
static_assert(std::is_same_v<decltype(add_u8_u8), std::uint16_t>, "");
static_assert(std::is_same_v<decltype(add_i8_i16), std::int32_t>, "");
static_assert(std::is_same_v<decltype(add_i8_u16), std::uint32_t>, "");
static_assert(std::is_same_v<decltype(add_u8_u16), std::uint32_t>, "");
static_assert(std::is_same_v<decltype(add_i8_i32), std::int64_t>, "");
static_assert(std::is_same_v<decltype(add_i8_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_u8_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_i8_i64), double>, "");
static_assert(std::is_same_v<decltype(add_i8_u64), double>, "");
static_assert(std::is_same_v<decltype(add_u8_u64), double>, "");
static_assert(std::is_same_v<decltype(add_i8_f32), float>, "");
static_assert(std::is_same_v<decltype(add_u8_f32), float>, "");
static_assert(std::is_same_v<decltype(add_i8_d64), double>, "");
static_assert(std::is_same_v<decltype(add_u8_d64), double>, "");
static_assert(std::is_same_v<decltype(add_i16_i16), std::int32_t>, "");
static_assert(std::is_same_v<decltype(add_i16_u16), std::uint32_t>, "");
static_assert(std::is_same_v<decltype(add_u16_u16), std::uint32_t>, "");
static_assert(std::is_same_v<decltype(add_i16_i32), std::int64_t>, "");
static_assert(std::is_same_v<decltype(add_i16_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_u16_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_i16_i64), double>, "");
static_assert(std::is_same_v<decltype(add_i16_u64), double>, "");
static_assert(std::is_same_v<decltype(add_u16_u64), double>, "");
static_assert(std::is_same_v<decltype(add_i16_f32), float>, "");
static_assert(std::is_same_v<decltype(add_u16_f32), float>, "");
static_assert(std::is_same_v<decltype(add_i16_d64), double>, "");
static_assert(std::is_same_v<decltype(add_u16_d64), double>, "");
static_assert(std::is_same_v<decltype(add_i32_i32), std::int64_t>, "");
static_assert(std::is_same_v<decltype(add_i32_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_u32_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_i32_i64), double>, "");
static_assert(std::is_same_v<decltype(add_i32_u64), double>, "");
static_assert(std::is_same_v<decltype(add_u32_u64), double>, "");
static_assert(std::is_same_v<decltype(add_i32_f32), float>, "");
static_assert(std::is_same_v<decltype(add_u32_f32), float>, "");
static_assert(std::is_same_v<decltype(add_i32_d64), double>, "");
static_assert(std::is_same_v<decltype(add_u32_d64), double>, "");
static_assert(std::is_same_v<decltype(add_i64_i64), double>, "");
static_assert(std::is_same_v<decltype(add_i64_u64), double>, "");
static_assert(std::is_same_v<decltype(add_u64_u64), double>, "");
static_assert(std::is_same_v<decltype(add_i64_f32), double>, "");
static_assert(std::is_same_v<decltype(add_u64_f32), double>, "");
static_assert(std::is_same_v<decltype(add_i64_d64), double>, "");
static_assert(std::is_same_v<decltype(add_u64_d64), double>, "");
There's one uncertainty:
i64 + f32 -> d64
u64 + f32 -> d64
//...
static_assert(std::is_same_v<decltype(add_i64_f32), double>, "");
static_assert(std::is_same_v<decltype(add_u64_f32), double>, "");
according to your rules it could also be:
i64 + f32 -> f32
u64 + f32 -> f32
//...
static_assert(std::is_same_v<decltype(add_i64_f32), float>, "");
static_assert(std::is_same_v<decltype(add_u64_f32), float>, "");
full code:
#include <type_traits>
#include <limits>
#include <cstdint>
template<typename T, typename U>
struct larger_arithmetic_type {
static_assert(std::is_arithmetic_v<T>, "T must be arithmetic");
static_assert(std::is_arithmetic_v<U>, "U must be arithmetic");
using type = typename std::conditional_t<(std::numeric_limits<T>::digits < std::numeric_limits<U>::digits), U, T>;
};
template<typename T, typename U>
using larger_arithmetic_type_t = typename larger_arithmetic_type<T, U>::type;
template<typename T, typename U>
struct arithmetic_superior_type {
using type = typename
std::conditional_t<std::is_floating_point_v<T> && std::is_floating_point_v<U>, larger_arithmetic_type_t<T, U>,
std::conditional_t<std::is_floating_point_v<T>, T,
std::conditional_t<std::is_floating_point_v<U>, U,
larger_arithmetic_type_t<T, U>>>>;
};
template<typename T, typename U>
using arithmetic_superior_type_t = typename arithmetic_superior_type<T, U>::type;
template<typename T, typename U>
struct promote_superior_type {
using superior_type = arithmetic_superior_type_t<T, U>;
using type = typename
std::conditional_t<(sizeof(T) == 8u || sizeof(U) == 8u), double,
std::conditional_t<std::is_floating_point_v<superior_type>, superior_type,
std::conditional_t<(std::numeric_limits<superior_type>::digits < std::numeric_limits<std::int16_t>::digits), std::conditional_t<std::is_signed_v<superior_type>, std::int16_t, std::uint16_t>,
std::conditional_t<(std::numeric_limits<superior_type>::digits < std::numeric_limits<std::int32_t>::digits), std::conditional_t<std::is_signed_v<superior_type>, std::int32_t, std::uint32_t>,
std::conditional_t<(std::numeric_limits<superior_type>::digits < std::numeric_limits<std::int64_t>::digits), std::conditional_t<std::is_signed_v<superior_type>, std::int64_t, std::uint64_t>, double>>>>>;
};
template<typename T, typename U>
using promote_superior_type_t = typename promote_superior_type<T, U>::type;
template<typename T, typename U, typename R = promote_superior_type_t<T, U>>
constexpr R add(T a, U b) {
return static_cast<R>(a) + static_cast<R>(b);
}
int main() {
arithmetic_superior_type_t<std::int32_t, float> a; //-> float
arithmetic_superior_type_t<std::uint32_t, std::int32_t> b; //-> std::uint32_t
arithmetic_superior_type_t<std::uint32_t, std::uint32_t> c; //-> std::uint32_t
arithmetic_superior_type_t<std::uint64_t, std::uint32_t> d; //-> std::uint64_t
arithmetic_superior_type_t<float, double> e; //-> double
arithmetic_superior_type_t<std::uint16_t, std::int64_t> f; //-> std::int64_t
//8_t + U
auto add_i8_i8 = add(std::int8_t(10), std::int8_t(10)); //i8 + i8 -> i16
auto add_i8_u8 = add(std::int8_t(10), std::uint8_t(10)); //i8 + u8 -> u16
auto add_u8_u8 = add(std::uint8_t(10), std::uint8_t(10)); //u8 + u8 -> u16
auto add_i8_i16 = add(std::int8_t(10), std::int16_t(10)); //i8 + i16 -> i32
auto add_i8_u16 = add(std::int8_t(10), std::uint16_t(10)); //i8 + u16 -> u32
auto add_u8_u16 = add(std::uint8_t(10), std::uint16_t(10)); //u8 + u16 -> u32
auto add_i8_i32 = add(std::int8_t(10), std::int32_t(10)); //i8 + i32 -> i64
auto add_i8_u32 = add(std::int8_t(10), std::uint32_t(10)); //i8 + u32 -> u64
auto add_u8_u32 = add(std::uint8_t(10), std::uint32_t(10)); //u8 + u32 -> u64
auto add_i8_i64 = add(std::int8_t(10), std::int64_t(10)); //i8 + i64 -> d64
auto add_i8_u64 = add(std::int8_t(10), std::uint64_t(10)); //i8 + u64 -> d64
auto add_u8_u64 = add(std::uint8_t(10), std::uint64_t(10)); //u8 + u64 -> d64
auto add_i8_f32 = add(std::int8_t(10), float(10)); //i8 + f32 -> f32
auto add_u8_f32 = add(std::uint8_t(10), float(10)); //u8 + f32 -> f32
auto add_i8_d64 = add(std::int8_t(10), double(10)); //i8 + d64 -> d64
auto add_u8_d64 = add(std::uint8_t(10), double(10)); //u8 + d64 -> d64
//16_t + U
auto add_i16_i16 = add(std::int16_t(10), std::int16_t(10)); //i16 + i16 -> i32
auto add_i16_u16 = add(std::int16_t(10), std::uint16_t(10)); //i16 + u16 -> u32
auto add_u16_u16 = add(std::uint16_t(10), std::uint16_t(10)); //u16 + u16 -> u32
auto add_i16_i32 = add(std::int16_t(10), std::int32_t(10)); //i16 + i32 -> i64
auto add_i16_u32 = add(std::int16_t(10), std::uint32_t(10)); //i16 + u32 -> u64
auto add_u16_u32 = add(std::uint16_t(10), std::uint32_t(10)); //u16 + u32 -> u64
auto add_i16_i64 = add(std::int16_t(10), std::int64_t(10)); //i16 + i64 -> d64
auto add_i16_u64 = add(std::int16_t(10), std::uint64_t(10)); //i16 + u64 -> d64
auto add_u16_u64 = add(std::uint16_t(10), std::uint64_t(10)); //u16 + u64 -> d64
auto add_i16_f32 = add(std::int16_t(10), float(10)); //i16 + f32 -> f32
auto add_u16_f32 = add(std::uint16_t(10), float(10)); //u16 + f32 -> f32
auto add_i16_d64 = add(std::int16_t(10), double(10)); //i16 + d64 -> d64
auto add_u16_d64 = add(std::uint16_t(10), double(10)); //u16 + d64 -> d64
//32_t + U
auto add_i32_i32 = add(std::int32_t(10), std::int32_t(10)); //i32 + i32 -> i64
auto add_i32_u32 = add(std::int32_t(10), std::uint32_t(10)); //i32 + u32 -> u64
auto add_u32_u32 = add(std::uint32_t(10), std::uint32_t(10)); //u32 + u32 -> u64
auto add_i32_i64 = add(std::int32_t(10), std::int64_t(10)); //i32 + i64 -> d64
auto add_i32_u64 = add(std::int32_t(10), std::uint64_t(10)); //i32 + u64 -> d64
auto add_u32_u64 = add(std::uint32_t(10), std::uint64_t(10)); //u32 + u64 -> d64
auto add_i32_f32 = add(std::int32_t(10), float(10)); //i32 + f32 -> f32
auto add_u32_f32 = add(std::uint32_t(10), float(10)); //u32 + f32 -> f32
auto add_i32_d64 = add(std::int32_t(10), double(10)); //i32 + d64 -> d64
auto add_u32_d64 = add(std::uint32_t(10), double(10)); //u32 + d64 -> d64
//64_t + U
auto add_i64_i64 = add(std::int64_t(10), std::int64_t(10)); //i64 + i64 -> d64
auto add_i64_u64 = add(std::int64_t(10), std::uint64_t(10)); //i64 + u64 -> d64
auto add_u64_u64 = add(std::uint64_t(10), std::uint64_t(10)); //u64 + u64 -> d64
auto add_i64_f32 = add(std::int64_t(10), float(10)); //i64 + f32 -> d64
auto add_u64_f32 = add(std::uint64_t(10), float(10)); //u64 + f32 -> d64
auto add_i64_d64 = add(std::int64_t(10), double(10)); //i64 + d64 -> d64
auto add_u64_d64 = add(std::uint64_t(10), double(10)); //u64 + d64 -> d64
static_assert(std::is_same_v<decltype(add_i8_i8), std::int16_t>, "");
static_assert(std::is_same_v<decltype(add_i8_u8), std::uint16_t>, "");
static_assert(std::is_same_v<decltype(add_u8_u8), std::uint16_t>, "");
static_assert(std::is_same_v<decltype(add_i8_i16), std::int32_t>, "");
static_assert(std::is_same_v<decltype(add_i8_u16), std::uint32_t>, "");
static_assert(std::is_same_v<decltype(add_u8_u16), std::uint32_t>, "");
static_assert(std::is_same_v<decltype(add_i8_i32), std::int64_t>, "");
static_assert(std::is_same_v<decltype(add_i8_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_u8_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_i8_i64), double>, "");
static_assert(std::is_same_v<decltype(add_i8_u64), double>, "");
static_assert(std::is_same_v<decltype(add_u8_u64), double>, "");
static_assert(std::is_same_v<decltype(add_i8_f32), float>, "");
static_assert(std::is_same_v<decltype(add_u8_f32), float>, "");
static_assert(std::is_same_v<decltype(add_i8_d64), double>, "");
static_assert(std::is_same_v<decltype(add_u8_d64), double>, "");
static_assert(std::is_same_v<decltype(add_i16_i16), std::int32_t>, "");
static_assert(std::is_same_v<decltype(add_i16_u16), std::uint32_t>, "");
static_assert(std::is_same_v<decltype(add_u16_u16), std::uint32_t>, "");
static_assert(std::is_same_v<decltype(add_i16_i32), std::int64_t>, "");
static_assert(std::is_same_v<decltype(add_i16_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_u16_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_i16_i64), double>, "");
static_assert(std::is_same_v<decltype(add_i16_u64), double>, "");
static_assert(std::is_same_v<decltype(add_u16_u64), double>, "");
static_assert(std::is_same_v<decltype(add_i16_f32), float>, "");
static_assert(std::is_same_v<decltype(add_u16_f32), float>, "");
static_assert(std::is_same_v<decltype(add_i16_d64), double>, "");
static_assert(std::is_same_v<decltype(add_u16_d64), double>, "");
static_assert(std::is_same_v<decltype(add_i32_i32), std::int64_t>, "");
static_assert(std::is_same_v<decltype(add_i32_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_u32_u32), std::uint64_t>, "");
static_assert(std::is_same_v<decltype(add_i32_i64), double>, "");
static_assert(std::is_same_v<decltype(add_i32_u64), double>, "");
static_assert(std::is_same_v<decltype(add_u32_u64), double>, "");
static_assert(std::is_same_v<decltype(add_i32_f32), float>, "");
static_assert(std::is_same_v<decltype(add_u32_f32), float>, "");
static_assert(std::is_same_v<decltype(add_i32_d64), double>, "");
static_assert(std::is_same_v<decltype(add_u32_d64), double>, "");
static_assert(std::is_same_v<decltype(add_i64_i64), double>, "");
static_assert(std::is_same_v<decltype(add_i64_u64), double>, "");
static_assert(std::is_same_v<decltype(add_u64_u64), double>, "");
static_assert(std::is_same_v<decltype(add_i64_f32), double>, "");
static_assert(std::is_same_v<decltype(add_u64_f32), double>, "");
static_assert(std::is_same_v<decltype(add_i64_d64), double>, "");
static_assert(std::is_same_v<decltype(add_u64_d64), double>, "");
}