避免使用C ++ type_traits缩小转换次数

时间:2016-03-28 20:01:34

标签: c++ typetraits

我有很多地方我希望使用std :: enable_if某些模板,只有从模板类型A到模板类型B(两者都是数字)的简单静态转换不会导致任何数据丢失。但是我不确定现有的type_traits,如果有的话,我应该使用或者我应该自己编写。

例如,从uint16_t到uint32_t,从float到double,甚至从int到double的转换都不会丢失任何精度或负号。但是从double转换为int或int转换为uint32_t显然会有问题。

我有点喋喋不休,测试is_trivially_constructible,is_assignable,is_constructible等等但是我没有看到一个会在我试图从float变为int时警告我。

我是否遗漏了目前在图书馆中的内容,或者我应该自己写一下?

(我已经知道如何写它。它很简单。只是想确保我不重新发明轮子。)

3 个答案:

答案 0 :(得分:4)

我正在回答我自己的问题,因为有人要求我发布我的特质并且评论似乎没有格式化。

template <class T, class F>
struct is_safe_numeric_conversion 
    : pred_base <( ( ( ( std::is_integral<T>::value && std::is_integral<F>::value ) || ( std::is_floating_point<T>::value && std::is_floating_point<F>::value ) ) &&
                     sizeof(T) >= sizeof(F) ) ||
                     ( std::is_floating_point<T>::value && std::is_integral<F>::value ) ) &&
                 ( ( std::is_signed<T>::value && std::is_signed<F>::value ) || ( std::is_unsigned<T>::value && std::is_unsigned<F>::value ) )>
{
};

关于我为什么做我在这里所做的事情的一些注意事项:

  • 我最终使用sizeof来检查类型的实际大小,而不是numeric_limits :: max / lowest。我不喜欢这样,并且宁愿使用numeric_limits,但Visual C ++让我适应了这一点。我很想知道它是不是因为它们的constexpr实现在我使用的某些版本中不起作用。
  • 我使用自己的小&#34; pred_base&#34;只是为了减少冗长的事情。我意识到我可以使用integral_constant
  • 就在今天,我意识到这不允许从小型无符号类型(例如,uint8_t)到大型有符号签名类型(比如int64_t)的有效转换,即使后者可以轻松保存前者的所有可能值。我需要解决这个问题,但这是次要的,在这一点上,我认为我是唯一仍然对此感兴趣的人...

最终版本(编辑3-FEB-2018)

StackOverflow告诉我今天有人给了我点数。所以我猜人们可能真的在使用它。在这种情况下,我想我应该提出我的整个当前版本,它解决了我上面提到的缺陷。

我确定有更好的方法可以做到这一点,我知道C ++ 14/17 /等允许我这么做,而不是那么冗长,但我被迫在VS版本上完成这项工作到VS2012所以我无法利用别名模板等。

因此,我通过编写一些辅助特征然后编写了我的最终&#34; is_safe_numeric_cast&#34;他们的特质。我认为它使事情更具可读性。

// pred_base selects the appropriate base type (true_type or false_type) to
// make defining our own predicates easier.

template<bool> struct pred_base : std::false_type {};
template<>     struct pred_base<true> : std::true_type {};

// same_decayed
// -------------
// Are the decayed versions of "T" and "O" the same basic type?
// Gets around the fact that std::is_same will treat, say "bool" and "bool&" as
// different types and using std::decay all over the place gets really verbose

template <class T, class O>
struct same_decayed 
    : pred_base <std::is_same<typename std::decay<T>::type, typename std::decay<O>::type>::value>
{};


// is_numeric.  Is it a number?  i.e. true for floats and integrals but not bool

template<class T>
struct is_numeric
    : pred_base<std::is_arithmetic<T>::value && !same_decayed<bool, T>::value>
{
};


// both - less verbose way to determine if TWO types both meet a single predicate

template<class A, class B, template<typename> class PRED>
struct both
    : pred_base<PRED<A>::value && PRED<B>::value>
{
};

// Some simple typedefs of both (above) for common conditions

template<class A, class B> struct both_numeric  : both<A, B, is_numeric>                { };    // Are both A and B numeric        types?
template<class A, class B> struct both_floating : both<A, B, std::is_floating_point>    { };    // Are both A and B floating point types?
template<class A, class B> struct both_integral : both<A, B, std::is_integral>          { };    // Are both A and B integral       types
template<class A, class B> struct both_signed   : both<A, B, std::is_signed>            { };    // Are both A and B signed         types
template<class A, class B> struct both_unsigned : both<A, B, std::is_unsigned>          { };    // Are both A and B unsigned       types


// Returns true if both number types are signed or both are unsigned
template<class T, class F>
struct same_signage
    : pred_base<(both_signed<T, F>::value) || (both_unsigned<T, F>::value)>
{
};

// And here, finally is the trait I wanted in the first place:  is_safe_numeric_cast

template <class T, class F>
struct is_safe_numeric_cast 
    : pred_base <both_numeric<T, F>::value &&                                                                         // Obviously both src and dest must be numbers
                 ( std::is_floating_point<T>::value && ( std::is_integral<F>::value || sizeof(T) >= sizeof(F) ) ) ||  // Floating dest: src must be integral or smaller/equal float-type
                 ( ( both_integral<T, F>::value ) &&                                                                  // Integral dest: src must be integral and (smaller/equal+same signage) or (smaller+different signage)
                   ( sizeof(T) > sizeof(F) || ( sizeof(T) == sizeof(F) && same_signage<T, F>::value ) ) )>
{
};

答案 1 :(得分:1)

另一种利用 SFINAE 的可能解决方案(std::void_t 需要 C++17):

namespace detail
{
  template<typename From, typename To, typename = void>
  struct is_narrowing_conversion_impl : std::true_type {};

  template<typename From, typename To>
  struct is_narrowing_conversion_impl<From, To, std::void_t<decltype(To{std::declval<From>()})>> : std::false_type {};
}  // namespace detail

template<typename From, typename To>
struct is_narrowing_conversion : detail::is_narrowing_conversion_impl<From, To> {};

brace initialization 可以隐式地使用窄化转换规则。当初始化需要缩小强制转换时,编译器将报告错误,例如uint8_t{int(1337)}

decltype(To{std::declval<From>()}) 中的表达式 is_narrowing_conversion_impl 在缩小转换的情况下格式错误,将导致为 is_narrowing_conversion::value 设置正确的值:

// all following assertions hold:
static_assert(!is_narrowing_conversion<std::int8_t, std::int16_t>::value);
static_assert(!is_narrowing_conversion<std::uint8_t, std::int16_t>::value);
static_assert(!is_narrowing_conversion<float, double>::value);
static_assert( is_narrowing_conversion<double, float>::value);
static_assert( is_narrowing_conversion<int, uint32_t>::value);

使用 clang、gcc 和 msvc 进行测试 示例:godbolt

答案 2 :(得分:0)

我认为标题<limits>为您提供构建完整特征所需的基元。

这是一个特性,用于检查一个积分在转换为另一个时是否会缩小(具有相似的签名):

#include <iostream>
#include <limits>

template<class IntFrom, class IntTo> static constexpr auto WouldNarrow = std::numeric_limits<IntFrom>::max() > std::numeric_limits<IntTo>::max();

int main()
{
    using namespace std;
    cout << WouldNarrow<int, short> << endl;
    return 0;
}