SFINAE有符号和无符号之间的区别

SFINAE differentiation between signed and unsigned

本文关键字:之间 区别 无符号 符号 SFINAE      更新时间:2023-10-16

我有将不同算术类型转换为半精度浮点类型的函数(只有最低级别的uint16_t),我有使用SFINAE和std::enable_if:的整数和浮点源类型的不同函数

template<typename T>
uint16_t to_half(typename std::enable_if<
                 std::is_floating_point<T>::value,T>::type value)
{
    //float to half conversion
}
template<typename T>
uint16_t to_half(typename std::enable_if<
                 std::is_integral<T>::value,T>::type value)
{
    //int to half conversion
}

这些是通过显式实例化从通用模板构造函数内部调用的:

template<typename T>
half::half(T rhs)
    : data_(detail::conversion::to_half<T>(rhs))
{
}

这编译起来也很好。现在,我试图通过用以下两个函数替换第二个函数来区分有符号整数和无符号整数:

template<typename T>
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value &&
                 std::is_signed<T>::value,T>::type value)
{
    //signed to half conversion
}
template<typename T>
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value &&
                 std::is_unsigned<T>::value,T>::type value)
{
    //unsigned to half conversion
}

但一旦我试图编译这个VS2010给了我

错误C2995:"uint16_t math::detail::conversion::to_half( std::enable_if<std::tr1::is_integral<_Ty>::value && std::tr1::is_signed<_Ty>::value, T>::type )":已定义函数模板。

因此,它似乎无法消除这两个模板之间的歧义,但它显然在积分版本和浮点版本之间没有问题。

但由于我不是一个模板魔术师,我可能只是错过了一些明显的东西(或者它实际上应该工作,只是一个VS2010错误)。那么,为什么这不起作用,以及如何在尽可能少的编程开销和仅限标准功能的限制下(如果可能的话)使其起作用呢?

就我个人而言,我会尽可能避免在这里使用SFINAE,因为您可以通过重载完成相同的事情:

template<typename T>
uint16_t to_half_impl(T val, std::true_type, std::true_type)
{
    // is_integral + is_signed implementation
}
template<typename T>
uint16_t to_half_impl(T val, std::true_type, std::false_type)
{
    // is_integral + is_unsigned implementation
}
template<typename T>
uint16_t to_half_impl(T val, std::false_type, std::true_type)
{
    // is_floating_point implementation
}
template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value, uint16_t>::type to_half(T val)
{
    return to_half_impl(val, std::is_integral<T>(), std::is_signed<T>());
}

如果这不起作用,则编译器出错。

如果包含表达式的两个函数定义满足一个定义规则,则包含模板参数的两个表达式被认为是等价的。。。

这是这里要考虑的最重要的规则(省略了"…"的细节)。您的两个模板不满足ODR,因为它们的令牌序列不同。

如果两个函数模板在同一范围内声明、具有相同名称、具有相同的模板参数列表,并且具有等效的返回类型和参数列表(使用上述规则来比较涉及模板参数的表达式),则它们是等效的。

因此,您的两个模板定义了不同的模板,并且不会发生冲突。您现在可以检查您的模板是否"功能等效"。如果对于任何可能的模板参数集,enable_if表达式总是产生相同的值,则会出现这种情况。但由于is_unsignedis_signed的情况并非如此,因此情况也并非如此。如果是这样,那么您的代码将是格式错误的,但不需要诊断(这实际上意味着"未定义的行为")。

更常见的习惯用法是对返回类型而不是参数类型使用SFINAE否则,模板类型T可能无法推导。带

// from C++14
template<bool C, typename T> using enable_if_t = typename std::enable_if<C,T>::type;
template<typename T>
enable_if_t<std::is_integral<T>::value &&  std::is_signed<T>::value, uint16_t>
to_half(T value)
{
    //signed to half conversion
}
template<typename T>
enable_if_t<std::is_integral<T>::value && !std::is_signed<T>::value,  int16_t>
to_half(T value)
{
    //unsigned to half conversion
}

以下语句中的类型T

auto y=to_half(x);    // T is deduced from argument, no need for <T> 

是可推导的(即使很琐碎),但对于您的原始代码来说,它不是!事实上,当通过clang在to_half()实现中运行此语句时,会给出

test.cc:24:11: error: no matching function for call to 'to_half'
  auto x= to_half(4);
          ^~~~~~~
test.cc:7:10: note: candidate template ignored: couldn't infer template argument 'T'
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value &&
         ^
test.cc:15:10: note: candidate template ignored: couldn't infer template argument 'T'
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value &&
         ^

当然,如果显式地提供了模板参数(正如您所做的那样),则不会出现此问题。所以你的代码没有错(但编译器错了),但如果你传递模板参数类型,SFINAE有什么意义?