我有很多地方希望使用
std::enable_if
来允许某些模板,前提是从模板类型 A
到模板类型 B
(两者都是数字)的简单静态转换不会导致任何结果数据丢失。但是我不确定我应该使用哪些现有的类型特征(如果有的话)或者我是否应该编写自己的类型特征。
例如,从
uint16_t
到 uint32_t
,从 float
到 double
,甚至从 int
到 double
都不会丢失任何精度或负号。但从 double
转换为 int
或 int
到 uint32_t
显然会有问题。
我胡思乱想了一下,测试了
std::is_trivially_constructible
、std::is_assignable
、std::is_constructible
等等,但如果我尝试从float
转到int
,我没有看到一个会警告我的。
我是否缺少当前图书馆中的某些内容,或者我应该自己编写它?
(我已经知道怎么写了。很简单。只是想确保我不会重新发明轮子)。
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> {};
缩小转换规则通过大括号初始化隐式可用。当初始化需要缩小转换时,编译器将报告错误,例如
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 进行测试 示例:神箭
我回答我自己的问题是因为有人要求我发布我的特质,而评论似乎没有格式。
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/etc 允许我更简洁地做到这一点,但我被迫在 VS 版本上一直工作到 VS2012,所以我无法利用别名模板等。
因此,我通过编写一些辅助特征来做到这一点,然后根据它们组成我的最终“is_safe_numeric_cast”特征。我认为这使事情更具可读性。
// 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 ) ) )>
{
};
我认为标题
<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;
}
我也遇到了同样的问题,@Quxflux 给出的答案很有帮助。以下是他使用适应 C++20 概念的大括号初始化规则的方法:
template <typename From, typename To>
concept is_narrowing_conversion = !requires(From from)
{
To{from};
};
用途:
// all following assertions hold:
static_assert(!is_narrowing_conversion<std::int8_t, std::int16_t>);
static_assert(!is_narrowing_conversion<std::uint8_t, std::int16_t>);
static_assert(!is_narrowing_conversion<float, double>);
static_assert( is_narrowing_conversion<double, float>);
static_assert( is_narrowing_conversion<int, uint32_t>);
由于尚未有人提及,因此提供了一种可能的解决方案 P0870 - 一项用于检测缩小转换的类型特征的提案,它具有详尽的实现。
在 C++20 中,我们还可以轻松检查表达式是否有效,并使用它来定义
concept
或特征:
template <typename From, typename To>
concept non_narrowing_convertible_to =
std::convertible_to<From, To> // Check whether implicit conversion is possible.
&& requires (From& f) { // Check whether this conversion is not narrowing
To{f}; // if it is made explicit.
};