在不列出所有可能组合的情况下编写饱和转换运算符
Writing a saturate casting operator without listing all possible combinations
我想在不同类型之间创建模板化操作(假设这是列表:int8_t、int16_t、int32_t、int64_t、uint8_t, uint16_t, uint32_t, uint64_t, float, double)。
然后我想允许 saturate_cast<>()
函数获取输入值,检查它是否在输出类型限制内,并在需要时饱和到这些限制。
问题是如果我对两个 int32_t
求和,默认的 C++ 操作在溢出的情况下有未定义的行为,所以我想将操作提升到 int64_t
并使用它键入以执行操作。
暂定的解决方案可能是:
#include <cstdint>
#include <limits>
template<typename T1, typename T2> struct type_which_fits { using type = decltype(T1() + T2()); };
template<> struct type_which_fits<int32_t, int32_t> { using type = int64_t; };
template<typename T1, typename T2>
auto TAdd(T1 lhs, T2 rhs) {
using type = typename type_which_fits<T1, T2>::type;
return static_cast<type>(lhs) + static_cast<type>(rhs);
}
template<typename ODT, typename IDT>
ODT saturate_cast(const IDT& v) {
if (v > std::numeric_limits<ODT>::max()) {
return std::numeric_limits<ODT>::max();
}
if (v < std::numeric_limits<ODT>::min()) {
return std::numeric_limits<ODT>::min();
}
return static_cast<ODT>(v);
}
int main()
{
auto x = saturate_cast<int8_t>(TAdd(1, 1u));
return 0;
}
不幸的是,这样我需要进一步指定所有可能的类型组合,我只需要这些规则(按给定顺序验证):
- 如果其中一种类型是浮点数,则使用默认提升
- 如果其中一种类型是 64 位,则提升为双精度
- 如果其中一种类型是 32 位,则提升为 64 位
- 否则使用默认促销
而且在saturate_cast<>()
中弹出一堆signed/unsigned警告,当两个类型不一样时"signedness"。
同样,这可以通过专注于所有可能的组合来解决,但不知何故感觉 "wrong"。
当我需要更多类型时,你能提出一个更灵活的解决方案吗?
这是我的方法,执行以下步骤:
找到上级类型(执行操作时产生的类型)
根据您的规则将类型提升为下一个类型1-4
执行加法,同时将两侧都转换为提升的类型。
可以通过以下一般规则找到高级类型:
浮点数 + 任意 -> 浮点数
如果左右有相同的位大小选择较大的(无符号比有符号)
else 选择 sizeof()
产量最大的地方
最后 2 个步骤可以通过制作一个辅助结构来保证(并简化),其中 returns 较大的类型 (ab) 使用 std::numerical_limits<T>::digits
它巧妙地完成了我们想要的(也关于有符号/无符号)因为:
std::numerical_limits<int>::digits
-> 31
std::numerical_limits<unsigned>::digits
-> 32
这将相应地适用于所有算术类型。
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;
有了这个,我们可以启用结构 arithmetic_superior_type
(遵循上述一般规则)从整数 and/or 浮点数中找到高级类型:
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;
因此arithmetic_seperior_type_t<T, U>
returns+
、-
、*
和/
介于T
和[之间的类型=30=] 会产生:
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
现在就像你说的,光有这个类型是不够的。溢出是可能的,因此 promote_superior_type
是从 T
和 U
接收高级类型提升的第 2 步 - 一个将定义任何加法结果的类型:
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;
终于可以添加add<T, U>
功能了,第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);
}
这就是所有需要的。 static_asserting
考虑每种可能的类型匹配以获得正确的预期输出:
//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>, "");
有一个不确定因素:
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>, "");
根据您的规则,也可以是:
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>, "");
完整代码:
#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>, "");
}
我想在不同类型之间创建模板化操作(假设这是列表:int8_t、int16_t、int32_t、int64_t、uint8_t, uint16_t, uint32_t, uint64_t, float, double)。
然后我想允许 saturate_cast<>()
函数获取输入值,检查它是否在输出类型限制内,并在需要时饱和到这些限制。
问题是如果我对两个 int32_t
求和,默认的 C++ 操作在溢出的情况下有未定义的行为,所以我想将操作提升到 int64_t
并使用它键入以执行操作。
暂定的解决方案可能是:
#include <cstdint>
#include <limits>
template<typename T1, typename T2> struct type_which_fits { using type = decltype(T1() + T2()); };
template<> struct type_which_fits<int32_t, int32_t> { using type = int64_t; };
template<typename T1, typename T2>
auto TAdd(T1 lhs, T2 rhs) {
using type = typename type_which_fits<T1, T2>::type;
return static_cast<type>(lhs) + static_cast<type>(rhs);
}
template<typename ODT, typename IDT>
ODT saturate_cast(const IDT& v) {
if (v > std::numeric_limits<ODT>::max()) {
return std::numeric_limits<ODT>::max();
}
if (v < std::numeric_limits<ODT>::min()) {
return std::numeric_limits<ODT>::min();
}
return static_cast<ODT>(v);
}
int main()
{
auto x = saturate_cast<int8_t>(TAdd(1, 1u));
return 0;
}
不幸的是,这样我需要进一步指定所有可能的类型组合,我只需要这些规则(按给定顺序验证):
- 如果其中一种类型是浮点数,则使用默认提升
- 如果其中一种类型是 64 位,则提升为双精度
- 如果其中一种类型是 32 位,则提升为 64 位
- 否则使用默认促销
而且在saturate_cast<>()
中弹出一堆signed/unsigned警告,当两个类型不一样时"signedness"。
同样,这可以通过专注于所有可能的组合来解决,但不知何故感觉 "wrong"。
当我需要更多类型时,你能提出一个更灵活的解决方案吗?
这是我的方法,执行以下步骤:
找到上级类型(执行操作时产生的类型)
根据您的规则将类型提升为下一个类型1-4
执行加法,同时将两侧都转换为提升的类型。
可以通过以下一般规则找到高级类型:
浮点数 + 任意 -> 浮点数
如果左右有相同的位大小选择较大的(无符号比有符号)
else 选择
sizeof()
产量最大的地方
最后 2 个步骤可以通过制作一个辅助结构来保证(并简化),其中 returns 较大的类型 (ab) 使用 std::numerical_limits<T>::digits
它巧妙地完成了我们想要的(也关于有符号/无符号)因为:
std::numerical_limits<int>::digits
-> 31std::numerical_limits<unsigned>::digits
-> 32
这将相应地适用于所有算术类型。
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;
有了这个,我们可以启用结构 arithmetic_superior_type
(遵循上述一般规则)从整数 and/or 浮点数中找到高级类型:
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;
因此arithmetic_seperior_type_t<T, U>
returns+
、-
、*
和/
介于T
和[之间的类型=30=] 会产生:
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
现在就像你说的,光有这个类型是不够的。溢出是可能的,因此 promote_superior_type
是从 T
和 U
接收高级类型提升的第 2 步 - 一个将定义任何加法结果的类型:
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;
终于可以添加add<T, U>
功能了,第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);
}
这就是所有需要的。 static_asserting
考虑每种可能的类型匹配以获得正确的预期输出:
//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>, "");
有一个不确定因素:
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>, "");
根据您的规则,也可以是:
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>, "");
完整代码:
#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>, "");
}