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

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

本文关键字:整数 类型 是否 检查 函数      更新时间:2023-10-16

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

更确切地说:是否有可能创建一个";一个适合所有";模板化的函数却没有得到编译器警告(布尔表达式总是true/false,有符号/无符号比较,未使用的变量),也没有禁用编译器警告检查?函数还应在运行时限制尽可能多的检查(编译时应排除所有琐碎的情况)。如果可能的话,我宁愿避免使用C++11等的扩展(除非存在"旧C++"的"快速"替换)。

注意:";值";在编译时未知,只知道其类型。

预期行为示例:

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

在此处或此处测试代码。

检查此处生成的程序集。

这个问题的灵感来自这篇文章

使用stdint.h 中定义的数字限制和类型

比我的第一个解决方案更紧凑,效率相同。

缺点:将包括一个额外的页眉。

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

生成的汇编代码(您可以更改T和U类型)

正确性测试


注意:已经编写了constexpr版本,但显然它有一些问题。看看这里和这里。

使用C++14的功能(为了C++11的兼容性,省略了constexpr)和模板的使用,这就是我想到的:

https://ideone.com/OSc9CI(更新版本:现在也接受未签名到签名,短而漂亮)

这基本上将std::enable_if与type_traits std::is_unsignedstd::is_integral一起广泛使用。最好从下往上阅读(因为决策树是从那里建立起来的)。

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

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

如果检查不是琐碎的(即必须检查数据类型的边界),则actual_typen被静态地投射到typename std::common_type<target, actual_type>::type

每个决策is_integralis_unsignedis_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>
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";
   }
}

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

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

这种方法也可以扩展到使用专用模板的已定义类型。像std::is_integral这样的检查对于这些类型将是否定的。

您可以在这里或通过用-S调用g++来检查汇编程序输出是否相当小(除了明显的浮点情况)。

当然是

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之间双向提供转换运算符。

这是它通过你在问题中发布的测试的证据。

我过去也使用过类似的方法来确定T是否可以准确地表示类型为U的值u(用inline替换constexpr,使其成为C++03):

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

这应该适用于整数类型之间的转换,但整数和浮点类型之间的转化——或者从浮点类型到窄浮点类型的转化——充斥着未定义的行为,需要大量的范围检查。

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

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

CCD_ 28命名空间中的任何东西都是标准的,并且而不是一些";扩展"

我提出了一个使用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);
        }
    }

在此处测试(很抱歉使用atoi:)。

最明确的方法可能是为每种类型使用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等

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