我有很多地方我希望使用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时警告我。
我是否遗漏了目前在图书馆中的内容,或者我应该自己写一下?
(我已经知道如何写它。它很简单。只是想确保我不重新发明轮子。)
答案 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 ) )>
{
};
关于我为什么做我在这里所做的事情的一些注意事项:
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;
}