Использование boost:: numeric_cast <>
Когда я хочу конвертировать между разными целыми типами, кажется, лучший синтаксис заключается в использовании boost::numeric_cast<>()
:
int y = 99999;
short x = boost::numeric_cast<short>(y); // will throw an exception if y is too large
Я никогда не использовал это; однако синтаксис довольно прост, так что все хорошо.
Теперь предположим, что я хочу сделать что-то более продвинутое: вместо того, чтобы бросать исключение, я бы хотел, чтобы он возвращал min или max целевого типа (насыщенность). Я не мог понять способ выразить это, но документация предполагает, что это возможно (возможно, используя политику RawConverter
). Все, что я мог придумать, следующее уродливое:
short x = numeric_cast<short>(max(min(y, SHORT_MAX), SHORT_MIN);
Итак, как я могу выразить "насыщающий прилив" с помощью boost numeric_cast
?
Ответы
Ответ 1
Возможно, вы могли бы сделать что-то вроде этого:
#include <limits>
template<typename Target, typename Source>
Target saturation_cast(Source src) {
try {
return boost::numeric_cast<Target>(src);
}
catch (const boost::negative_overflow &e) {
return std::numeric_limits<Target>::lowest();
/* Or, before C++11:
if (std::numeric_limits<Target>::is_integer)
return std::numeric_limits<Target>::min();
else
return -std::numeric_limits<Target>::max();
*/
}
catch (const boost::positive_overflow &e) {
return std::numeric_limits<Target>::max();
}
}
(Для типов, которые поддерживают его, ошибки могут также возвращать -inf/+ inf).
Таким образом вы позволяете Boost numeric_cast
определять, находится ли значение вне границ и может затем соответствующим образом реагировать.
Ответ 2
Hm... Если это будет сделано, общее решение, вероятно, должно было бы сделать что-то вроде:
template<typename TypeFrom, typename TypeTo>
TypeTo saturated_cast(TypeFrom value) {
TypeTo valueMin = std::numeric_limits<TypeTo>::min();
TypeTo valueMax = std::numeric_limits<TypeTo>::max();
return boost::numeric_cast<TypeTo>( std::max(std::min(value,valueMax),valueMin) );
}
Надеюсь, у меня все получилось... Во всяком случае, у вас есть концепция:)
.... BTW: Я думаю, вы могли бы использовать static_cast
здесь вместо этого, потому что после выполнения ограничения вы больше не можете переполнять диапазон, поэтому вам не нужна дополнительная проверка numeric_cast
.
Ответ 3
Если у вас все в порядке с С++ 17, но вы не хотите, чтобы ваш алгоритм приведения вызывал исключения внутри, вы можете использовать std::clamp
с небольшим переносом для обработки значений за пределами.
template <typename TTo, typename TFrom>
constexpr TTo clamp_cast(const TFrom& src) noexcept
{
using to_limits = std::numeric_limits<TTo>;
using larger_type = std::conditional_t<(sizeof(TFrom) < sizeof(TTo)), TTo, TFrom>;
if constexpr (std::is_same_v<TTo, TFrom>)
{
// don't bother if it is the same type
return src;
}
else if constexpr (std::is_unsigned_v<TFrom>)
{
// if source is unsigned, we only need to worry about upper bound
return TTo(std::min(larger_type(src), larger_type(to_limits::max())));
}
else if constexpr (std::is_unsigned_v<TTo>)
{
// source is signed, but destination is not
if (src < TFrom(0))
return TTo(0);
else
return TTo(std::min(larger_type(src), larger_type(to_limits::max())));
}
else
{
// both are signed -- use regular clamping rules
return TTo(std::clamp(larger_type(src),
larger_type(to_limits::min()),
larger_type(to_limits::max())
)
);
}
}
Использование - в основном то, что вы ожидаете:
static_assert(uint16_t(213) == clamp_cast<uint16_t>(213));
static_assert(uint16_t(65535) == clamp_cast<uint16_t>(9872431));
static_assert(uint16_t(0) == clamp_cast<uint16_t>(-98721));