20

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

更准确地说:是否可以创建一个“一刀切”的模板化函数,但不会收到编译器警告(布尔表达式总是真/假,有符号/无符号比较,未使用的变量)并且没有禁用编译器警告检查?这些函数还应该在运行时限制尽可能多的检查(所有微不足道的情况都应该在编译时排除)。如果可能的话,我宁愿避免使用 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

在此处此处测试您的代码。

检查此处生成的程序集。

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

4

7 回答 7

13

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

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

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

#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 版本,但显然它有一些问题。见这里这里

于 2013-06-22T14:50:04.807 回答
9

使用C++14(省略constexprC++11 兼容性)的特性和模板的使用,这就是我想出的:

https://ideone.com/OSc9CI(更新版:现在也接受未签名转签名,短小精悍)

这基本上std::enable_if与 type_traitsstd::is_unsignedstd::is_integral. 最好从下往上阅读(因为决策树是从那里建立起来的)。

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

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

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

每个决定is_integraland is_unsignedandis_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++来检查汇编器输出是否相当小(除了明显的浮点情况) 。

于 2013-06-27T11:39:31.407 回答
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      )

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

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

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

于 2013-06-20T23:40:53.173 回答
4

我过去使用过类似的东西来确定是否T可以准确地表示u类型的值U(替换constexprinlinemake this 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)));
}

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

于 2013-06-20T23:08:39.750 回答
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);
        }
    }

在这里测试(抱歉使用 atoi :))。

于 2013-06-20T21:37:34.190 回答
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都是标准的,而不是一些“扩展”

于 2021-07-14T01:46:56.627 回答
-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 等。

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

于 2013-06-24T21:04:30.457 回答