
#include <cmath>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <limits>

// 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 three functions
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!(std::is_unsigned<target_type>::value) && !(std::is_unsigned<actual_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_as_t = std::numeric_limits<target_type>::lowest();
   const auto t_min = static_cast<common_type>(t_min_as_t);
   return (c_n >= t_min);
}

// for signed target types where the actual type is unsigned, the lower bound is trivially satisfied.
template <typename target_type, typename actual_type>
constexpr typename std::enable_if<!(std::is_unsigned<target_type>::value) && (std::is_unsigned<actual_type>::value), bool>::type
test_lower_bound(const actual_type n)
{
	return true;
}

// 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";
   }
}
