Function checking if an integer type can fit a value of possibly different (integer) type

Using numeric_limits and types defined in stdint.h

More compact that my first solution, same efficiency.

Drawback: one additional header to be included.

#include <limits>
#include <stdint.h>

using std::numeric_limits;

template <typename T, typename U>
    bool CanTypeFitValue(const U value) {
        const intmax_t botT = intmax_t(numeric_limits<T>::min() );
        const intmax_t botU = intmax_t(numeric_limits<U>::min() );
        const uintmax_t topT = uintmax_t(numeric_limits<T>::max() );
        const uintmax_t topU = uintmax_t(numeric_limits<U>::max() );
        return !( (botT > botU && value < static_cast<U> (botT)) || (topT < topU && value > static_cast<U> (topT)) );        
    }

Assembly code generated (you can change T and U types)

Correctness test


Note: a constexpr version was written, but apparently it has some problems. See here and here.


Using the features of C++14 (leave out constexpr for C++11 compatibility) and use of templates, this is what I came up with:

https://ideone.com/3fml6j (updated version: now also accepts unsigned to signed, short and beautiful)

This basically uses std::enable_if extensively with type_traits std::is_unsigned and std::is_integral. Best to read from bottom up (as the decision tree builds up from there).

Obviously this is nearly all done compile time, so assembly should be fairly small.

This solution can handle integral and floating point target types as well as integral and floating point original types.

If the check isn't trivial (i.e. bounds of data type have to be checked), the actual_type value n is casted to typename std::common_type<target, actual_type>::type statically.

Every decision is_integral and is_unsigned and is_same is done at compile time, so no overhead from this at runtime. The check boils down to some lower_bound(target) <= value and / or value <= upper_bound(target) after the types are casted to a common type (to avoid warnings and prevent overflows).

#include <cmath> // necessary to check for floats too
#include <cstdint> // for testing only
#include <iomanip> // for testing only
#include <iostream> // for testing only
#include <limits> // necessary to check ranges
#include <type_traits> // necessary to check type properties (very efficient, compile time!)

// the upper bound must always be checked
template <typename target_type, typename actual_type>
constexpr bool test_upper_bound(const actual_type n)
{
   typedef typename std::common_type<target_type, actual_type>::type common_type;
   const auto c_n = static_cast<common_type>(n);
   const auto t_max = static_cast<common_type>(std::numeric_limits<target_type>::max());
   return ( c_n <= t_max );
}

// the lower bound is only needed to be checked explicitely in non-trivial cases, see the next to functions
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!(std::is_unsigned<target_type>::value), bool>::type
test_lower_bound(const actual_type n)
{
   typedef typename std::common_type<target_type, actual_type>::type common_type;
   const auto c_n = static_cast<common_type>(n);
   const auto t_min = static_cast<common_type>(std::numeric_limits<target_type>::lowest());
   return ( c_n >= t_min );
}

// for unsigned target types, the sign of n musn't be negative
// but that's not an issue with unsigned actual_type
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<std::is_integral<target_type>::value &&
                        std::is_unsigned<target_type>::value &&
                        std::is_integral<actual_type>::value &&
                        std::is_unsigned<actual_type>::value, bool>::type
test_lower_bound(const actual_type)
{
   return true;
}

// for unsigned target types, the sign of n musn't be negative
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<std::is_integral<target_type>::value &&
                        std::is_unsigned<target_type>::value &&
                        (!std::is_integral<actual_type>::value ||
                         !std::is_unsigned<actual_type>::value), bool>::type
test_lower_bound(const actual_type n)
{
   return ( n >= 0 );
}

// value may be integral if the target type is non-integral
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!std::is_integral<target_type>::value, bool>::type
test_integrality(const actual_type)
{
   return true;
}

// value must be integral if the target type is integral
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<std::is_integral<target_type>::value, bool>::type
test_integrality(const actual_type n)
{
   return ( (std::abs(n - std::floor(n)) < 1e-8) || (std::abs(n - std::ceil(n)) < 1e-8) );
}

// perform check only if non-trivial
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!std::is_same<target_type, actual_type>::value, bool>::type
CanTypeFitValue(const actual_type n)
{
   return test_upper_bound<target_type>(n) &&
          test_lower_bound<target_type>(n) &&
          test_integrality<target_type>(n);
}


// trivial case: actual_type == target_type
template <typename actual_type>
constexpr bool CanTypeFitValue(const actual_type)
{
   return true;
}

int main()
{
   int ns[] = {6, 1203032847, 2394857, -13423, 9324, -192992929};
   for ( const auto n : ns )
   {
      std::cout << std::setw(10) << n << "\t";
      std::cout << " " << CanTypeFitValue<int8_t>(n);
      std::cout << " " << CanTypeFitValue<uint8_t>(n);
      std::cout << " " << CanTypeFitValue<int16_t>(n);
      std::cout << " " << CanTypeFitValue<uint16_t>(n);
      std::cout << " " << CanTypeFitValue<int32_t>(n);
      std::cout << " " << CanTypeFitValue<uint32_t>(n);
      std::cout << " " << CanTypeFitValue<int64_t>(n);
      std::cout << " " << CanTypeFitValue<uint64_t>(n);
      std::cout << " " << CanTypeFitValue<float>(n);
      std::cout << " " << CanTypeFitValue<double>(n);
      std::cout << "\n";
   }
   std::cout << "\n";
   unsigned long long uss[] = {6, 1201146189143ull, 2397, 23};
   for ( const auto n : uss )
   {
      std::cout << std::setw(10) << n << "\t";
      std::cout << " " << CanTypeFitValue<int8_t>(n);
      std::cout << " " << CanTypeFitValue<uint8_t>(n);
      std::cout << " " << CanTypeFitValue<int16_t>(n);
      std::cout << " " << CanTypeFitValue<uint16_t>(n);
      std::cout << " " << CanTypeFitValue<int32_t>(n);
      std::cout << " " << CanTypeFitValue<uint32_t>(n);
      std::cout << " " << CanTypeFitValue<int64_t>(n);
      std::cout << " " << CanTypeFitValue<uint64_t>(n);
      std::cout << " " << CanTypeFitValue<float>(n);
      std::cout << " " << CanTypeFitValue<double>(n);
      std::cout << "\n";
   }
   std::cout << "\n";
   float fs[] = {0.0, 0.5, -0.5, 1.0, -1.0, 1e10, -1e10};
   for ( const auto f : fs )
   {
      std::cout << std::setw(10) << f << "\t";
      std::cout << " " << CanTypeFitValue<int8_t>(f);
      std::cout << " " << CanTypeFitValue<uint8_t>(f);
      std::cout << " " << CanTypeFitValue<int16_t>(f);
      std::cout << " " << CanTypeFitValue<uint16_t>(f);
      std::cout << " " << CanTypeFitValue<int32_t>(f);
      std::cout << " " << CanTypeFitValue<uint32_t>(f);
      std::cout << " " << CanTypeFitValue<int64_t>(f);
      std::cout << " " << CanTypeFitValue<uint64_t>(f);
      std::cout << " " << CanTypeFitValue<float>(f);
      std::cout << " " << CanTypeFitValue<double>(f);
      std::cout << "\n";
   }
}

This (new) version quickly decides (at compile time!) if checks are needed (concerning upper bound, lower bound and integrality) and uses the correct version (to avoid warnings about stupid >= 0 comparisons with unsigned types) (also at compile time). E.g. the integrality does not need to be checked if the target is float, the lower bound does not need to be checked if both types are unsigned etc.

The most obvious optimization (having equal types), is done with std::is_same.

This approach can also be extended to used-defined types with specialized templates. Checks such as std::is_integral will be negative on those types.

You can check that the assembler output is fairly small (except for the obvious case of floats) here or by invoking g++ with -S.


Certainly

template <typename T, typename U>
constexpr bool CanTypeFitValue(const U value)
{return ((value>U(0))==(T(value)>T(0))) && U(T(value))==value;}

//      (         part1         ) && (      part2      )

Basically, this has two parts. The first part confirms that if a sign change happens (casting unsigned to signed or vice-versa, that the sign information isn't lost. The second part simply checks if value is cast to a T and back, that it retains it's value, and no bits have been lost.

FYI I'm not certain this is enough to tell if the value is maintained, but can't immediately think of a case with primitives that would fail. Both my answer and Casey's answer should work on user-defined numeric-like types so long as they provide conversion operators both ways between T and U.

Here's proof that it passes the tests you post in the question.