18
\$\begingroup\$

In order to detect run-time integer casting problems, I've created this function*:

template<typename TTo, typename TFrom>
static inline TTo int_cast(TFrom value)
{
    TTo result = static_cast<TTo>(value);
    if (static_cast<TFrom>(result) != value || (result >= TTo()) != (value >= TFrom()))
    {
        throw std::out_of_range("numeric_cast: value out of range");
    }
    return result;
}

Two questions:

  1. Are there any loopholes in this function I have not accounted for (e.g. undefined behavior)?

  2. What would be the best way to extend this to perform double-to-float and floating-to-integral conversions?

*Note: Yes, I realize Boost has a function like this. But I want my own. :)

\$\endgroup\$
4
  • \$\begingroup\$ If you are worried about casting problems why not stick to plain ints? It would be safer and faster. \$\endgroup\$
    – Pubby
    Oct 22, 2011 at 10:36
  • \$\begingroup\$ For those arriving from search engines, Boost provides boost::numeric_cast. \$\endgroup\$ May 2, 2017 at 23:25
  • \$\begingroup\$ I don't think it is appropriate to use exceptions in this case and would recommend using assertions instead. If your algorithm is designed correctly then there will be no invalid casts as long as only correct data is supplied. If you are checking your data before using the algorithm then the only reason for a bad cast would be due to a bug in your algorithm. Exceptions are not meant to handle software bugs. \$\endgroup\$
    – Chris_F
    Nov 14, 2018 at 2:01
  • \$\begingroup\$ @Chris_F: The issue is that overflow is not always a bug, but potentially a limitation. Instead of getting wrong answers you might prefer an error. \$\endgroup\$
    – user541686
    Nov 14, 2018 at 2:17

2 Answers 2

10
\$\begingroup\$

Are there any loopholes in this function I have not accounted for (e.g. undefined behavior)?

No. Static cast will not result in undefined behavior.
If it compiles then the cast is good (assuming integer/float types).

Though some of your cast may potentially result in implementation defined behavior (depending on the sign-dness and size of the types).

signed x = int_cast<signed,unsigned>(-1); // implementation defined behavior.

Since this is only supposed to work on int(s). Then I would make it be a compile time error if you tried to use it with other types. You may also be able to do some compile time range checking when the inputs are literals.

In the following:

(result >= TTo()) != (value >= TFrom())

I am assuming this is some form of signdness test (to make sure both source and destination have the same sign). If you are going to do this please comment the code explaining exactly how you think it works.

There are integer traits that allow you to pull the signdess and size of the template type from the input/output types. You could potentially look at this to help identify things.

\$\endgroup\$
9
\$\begingroup\$

If you are able to use C++11 in your application, then you can make use of the enable_if template to provide several overloads of your checked narrowing conversion:

#include <limits>
#include <stdexcept>
#include <type_traits>

template <typename I, typename J>
static typename std::enable_if<std::is_signed<I>::value && std::is_signed<J>::value, I>::type narrow_check(J value) {
    if (value < std::numeric_limits<I>::min() || value > std::numeric_limits<I>::max()) {
        throw std::out_of_range("out of range");
    }
    return static_cast<I>(value);
}

template <typename I, typename J>
static typename std::enable_if<std::is_signed<I>::value && std::is_unsigned<J>::value, I>::type narrow_check(J value) {
    if (value > static_cast<typename std::make_unsigned<I>::type>(std::numeric_limits<I>::max())) {
        throw std::out_of_range("out of range");
    }
    return static_cast<I>(value);
}

template <typename I, typename J>
static typename std::enable_if<std::is_unsigned<I>::value && std::is_signed<J>::value, I>::type narrow_check(J value) {
    if (value < 0 || static_cast<typename std::make_unsigned<J>::type>(value) > std::numeric_limits<I>::max()) {
        throw std::out_of_range("out of range");
    }
    return static_cast<I>(value);
}

template <typename I, typename J>
static typename std::enable_if<std::is_unsigned<I>::value && std::is_unsigned<J>::value, I>::type narrow_check(J value) {
    if (value > std::numeric_limits<I>::max()) {
        throw std::out_of_range("out of range");
    }
    return static_cast<I>(value);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.