开发者

Using boost::numeric_cast<>

开发者 https://www.devze.com 2023-01-30 00:10 出处:网络
When I want to convert between different integer types, it seems the best syntax is to use boost::numeric_cast<>():

When I want to convert between different integer types, it seems the best syntax is to use boost::numeric_cast<>():

int y = 99999;
short x = boost::numeric_cast<short>(y); // will throw an exception if y is too large

I have never used that; however the syntax is pretty straightforward, so all is well.

Now suppose I want to do something a bit more advanced: instead of throwing an exception, I'd like it to return the min or max of the target type (saturation). I couldn't figure out a way to express that, but the documentation suggests that it is possible (probably using RawConverter policy). All I could come up with is the following ugly:

short x = numeric_cast<short>(max(min(y, SHORT_MAX), SHORT_MIN);

So how can I express "saturating cast" using boost开发者_如何学Python's numeric_cast?


You could probably do something like this:

#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();
   }
}

(For types that support it the error cases could also return -inf/+inf).

This way you let Boost's numeric_cast determine if the value is out of bounds and can then react accordingly.


Hm... If the above works, a general solution would probably be to make something like:

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) );
}

Hope I got it right... Anyway, you've got the concept :)

.... BTW: I think you could use static_cast here instead because after performing the limitation you cannot overflow the range any more, so you don't need additional checking of numeric_cast.


If you're okay with C++17 but don't want your casting algorithm throwing exceptions internally, you can use std::clamp with a bit of wrapping to handle out-of-bounds values.

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())
                             )
                  );
    }
}

Usage is basically what you'd expect:

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));
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号