Using boost::numeric_cast<>
Asked Answered
A

3

15

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's numeric_cast?

Alt answered 12/12, 2010 at 21:34 Comment(0)
N
15

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.

Naomanaomi answered 12/12, 2010 at 21:41 Comment(13)
Exception-dependent :( Some projects don't use C++ exceptions, good to remember that. BTW: Would it still work for a signed/unsigned cast?Suffering
@Kos: If a project doesn't use all the language features, that's that project's problem. Nobody should write code for a language not using some features because some other person might not like that feature.Kalman
Exceptions, besides all their advantages, also make the control flow more complex and may also introduce overheads. Generally, it's not called a "problem", it's called a "coding standard". Many (not all, ofc) C++ projects have good reasons to exist without exceptions and they are even compiled without exception support.Suffering
@Kos: Assuming that boosts numeric_cast handles signed/unsigned (and I sure hope it does), this will still handle it correctly. And if you don't want to use any exceptions you probably shouldn't use numeric_cast in the first place, since that function's whole point seems to be that is does throw on error.Naomanaomi
@sth, The OP clearly wanted a solution which never throws an exception (but saturates the value instead). And if we have the choice make the internal implementation either with or without exceptions, it just seems better to avoid them when they are not necessary (for the reasons mentioned)- that's all :).Suffering
@Kos: what bizarre sort of project does not use C++ exceptions but does use Boost libraries, especially for something as perceived-to-be-trivial as casting between numeric types? o_OCotswolds
@Kos: Coding standards which remove language features are not coding standards that other people should be held to, end of story. And there are no exceptions in the interface, therefore it is fine. Exceptions in the implementation are perfectly valid.Kalman
Removing language features is basically what coding standards are often about. If you have no need for such, that's totally OK, just be aware that other conventions than yours do exist.Suffering
@Kos: No, coding standards are about how you like to place your parentheses and how you like to document your code, spaces or tabs. Removing language features, definitely not a coding standard that any of us should have to conform to.Kalman
In a perfect world, that'd be the case. :) Generally there exist well-documented coding standards (for example, safety-critical) which focus primarily on removing language features in order to make code correctness validation easier. C without pointer arithmetic? You bet. And like 9 in 10 C++ programmers are not experts in making exception-safe C++ code. Can you afford to employ only the ones who are? Good for you.Suffering
@DeadMG: actually, if you ever read C++ Coding Standards by Sutter you should realize that real coding standards are NOT about placing parenthesis (those are naming conventions or formatting standards). Coding Standards are about enforcing good practices and steering the developers onto the a better path.Popery
@Kos: Exception Safety is largely a false debate. Once you have enforced RAII to prevent leaks, you're only left with functional issues (half-modified objects) and those shall be caught by unit tests. Most projects have seen use exceptions nowadays, the only notable exception being Clang / LLVM, but compilers are somewhat special. That said, I never worked in embedded.Popery
Your usage of numeric_limits::min is incorrect for floating point types. you need to use -max()Ulla
T
2

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));
Twill answered 25/9, 2019 at 5:44 Comment(0)
S
1

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.

Suffering answered 12/12, 2010 at 21:41 Comment(1)
same issue that GregHill, the conversion should take place before the comparison, otherwise there is no benefit in using numeric_cast, since it'll perform the very same comparisons anyway to check that the range is right. Also, std::min and std::max can only be called for two arguments of the same type, this code would not compile.Popery

© 2022 - 2024 — McMap. All rights reserved.