函数检查整数类型是否适合可能不同(整数)类型的值

时间:2013-06-20 21:37:34

标签: c++ templates suppress-warnings

是否可以创建一个模板化函数来检查原始数据类型是否适合可能不同的原始数据类型的值?我们暂时将范围限制为整数类型。

更准确地说:是否有可能创建一个“一个适合所有”的模板化函数而不会得到编译器警告(布尔表达式总是真/假,有符号/无符号比较,未使用的变量)并且不禁用编译器警告检查?这些函数还应该在运行时尽可能地限制检查(在编译时应排除所有琐碎的情况)。如果可能的话,我宁愿避免使用C ++ 11之类的扩展(除非“旧”C ++的“快速”替换存在)。

注意:“value”在编译时是未知的,只是其类型。

预期行为的示例:

int main(int argc, char** argv) {
    for (int i = 1; i < argc; i++) {
        const int value = atoi(argv[i]);
        std::cout << value << ": ";
        std::cout << CanTypeFitValue<int8_t>(value) << " ";
        std::cout << CanTypeFitValue<uint8_t>(value) << " ";
        std::cout << CanTypeFitValue<int16_t>(value) << " ";
        std::cout << CanTypeFitValue<uint16_t>(value) << " ";
        std::cout << CanTypeFitValue<int32_t>(value) << " ";
        std::cout << CanTypeFitValue<uint32_t>(value) << " ";
        std::cout << CanTypeFitValue<int64_t>(value) << " ";
        std::cout << CanTypeFitValue<uint64_t>(value) << std::endl;
        }

}



./a.out 6 1203032847 2394857 -13423 9324 -192992929

6: 1 1 1 1 1 1 1 1

1203032847: 0 0 0 0 1 1 1 1

2394857: 0 0 0 0 1 1 1 1

-13423: 0 0 1 0 1 0 1 0

9324: 0 0 1 1 1 1 1 1

-192992929: 0 0 0 0 1 0 1 0

测试您的代码herehere

检查生成的程序集here

这个问题的灵感来自this post

7 个答案:

答案 0 :(得分:8)

使用stdint.h中定义的numeric_limits和类型

我的first solution更紧凑,效率相同。

缺点:要包含一个额外的标题。

#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(您可以更改T和U类型)

Correctness test

<小时/> 注意:写了一个constexpr version,但显然它有一些问题。请参阅herehere

答案 1 :(得分:6)

使用C++11的功能(是的,我知道你没有要求,但它很有趣)和模板的使用,这就是我提出的:

http://ideone.com/lBxnAW(更新版本:现在也接受unsigned签名,简短且漂亮)

这基本上使用了std::enable_if与type_traits std::is_unsignedstd::is_integral。最好从下往上阅读(因为决策树从那里开始建立)。

显然这几乎都是在编译时完成的,因此汇编应该相当小。

此解决方案可以处理整数和浮点目标类型以及整数和浮点原始类型。

如果检查不重要(即必须检查数据类型的边界),则actual_typen将静态转换为typename std::common_type<target, actual_type>::type

每个决策is_integralis_unsigned以及is_same都是在编译时完成的,因此在运行时不会产生任何开销。在将类型转换为通用类型后,检查会归结为某些lower_bound(target) <= value和/或value <= upper_bound(target)(以避免出现警告并防止溢出)。

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

这个(新)版本快速决定(在编译时!)是否需要检查(关于上限,下限和完整性)并使用正确的版本(以避免关于愚蠢的&gt; = 0与无符号类型的比较的警告) (也在编译时间)。例如。如果目标是浮点数,则不需要检查完整性,如果两种类型都是无符号等,则不需要检查下限。

最明显的优化(具有相同的类型)是使用std::is_same完成的。

此方法还可以使用专用模板扩展到使用定义的类型。 std::is_integral等检查在这些类型上将为否定。

您可以检查汇编程序输出是否相当小(浮动的明显情况除外)here或使用-S调用g ++。

答案 2 :(得分:5)

当然

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      )

基本上,这有两个部分。第一部分确认如果发生符号更改(将unsigned转换为signed或反之亦然,那么符号信息不会丢失。第二部分只是检查value是否已投出到T并返回,它保留了它的值,并且没有丢失任何位。

仅供参考我不确定这是否足以判断该值是否得到维护,但不能立即想到原语会失败的情况。我的答案和Casey的答案应该适用于用户定义的类数字类型,只要它们在TU之间提供转换运算符。

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

答案 3 :(得分:3)

我过去使用类似的东西确定T是否可以完全代表u类型的值U(删除constexpr来制作此C ++ 03):

template <typename T, typename U>
constexpr inline bool CanTypeRepresentValue(const U value) {
    return ((value > U()) == (static_cast<T>(value) > T())) &&
           (value == static_cast<U>(static_cast<T>(value)));
}

这应该至少适用于所有算术类型,以及具有适当转换的用户定义类型。 (test at ideone)。

答案 4 :(得分:1)

我建议使用numeric_limits

的解决方案
#include <limits>
using std::numeric_limits;

template <typename T, typename U>
    bool CanTypeFitValue(const U value) {
        if (numeric_limits<T>::is_signed == numeric_limits<U>::is_signed) {
            if (numeric_limits<T>::digits >= numeric_limits<U>::digits)
                return true;
            else
                return (static_cast<U>(numeric_limits<T>::min() ) <= value && static_cast<U>(numeric_limits<T>::max() ) >= value);
        }
        else {
            if (numeric_limits<T>::is_signed) {
                if (numeric_limits<T>::digits > numeric_limits<U>::digits) //Not >= in this case!
                    return true;
                else
                    return (static_cast<U>(numeric_limits<T>::max() ) >= value);
            }
            else ///U is signed, T is not
                if (value < static_cast<U> (0) )
                    return false;
                else
                    if (numeric_limits<T>::digits >= numeric_limits<U>::digits)
                        return true;
                    else
                        return (static_cast<U>(numeric_limits<T>::max() ) >= value);
        }
    }

经过测试here(抱歉使用atoi :))。

答案 5 :(得分:1)

在 C++20 中只需使用 std::in_range

std::cout << std::in_range<int8_t>(value) << " ";
std::cout << std::in_range<uint8_t>(value) << " ";
std::cout << std::in_range<int16_t>(value) << " ";
std::cout << std::in_range<uint16_t>(value) << " ";
std::cout << std::in_range<int32_t>(value) << " ";
std::cout << std::in_range<uint32_t>(value) << " ";
std::cout << std::in_range<int64_t>(value) << " ";
std::cout << std::in_range<uint64_t>(value) << std::endl;

std 命名空间中的任何内容都是标准的,不是某些“扩展”

答案 6 :(得分:-1)

最明确的方法可能是使用SFINAE和每种类型的函数。 像这样:

#include <limits>


template <typename T>
bool CanTypeFitValue(int) {
    return false;
}

template <typename T>
bool CanSignedNumericTypeFitValue(int value) {
    return (value >= std::numeric_limits<T>::min() && 
            value <= std::numeric_limits<T>::max());
}

template <typename T>
bool CanUnsignedNumericTypeFitValue(int value) {
    return (value >= 0 && 
            static_cast<unsigned>(value) <= std::numeric_limits<T>::max());
}

template <> bool CanTypeFitValue<int8_t>(int value) { 
    return CanSignedNumericTypeFitValue<int8_t>(value); 
}
template <> bool CanTypeFitValue<uint8_t>(int value) {
    return CanUnsignedNumericTypeFitValue<uint8_t>(value); 
}
// .....
//template <> bool CanTypeFitValue<SomeUserClass * > { 
//    return impl_details(value);
//};

它也常用于STL / Boost等。

主要思想是可以定义函数以及用户定义的类型。