SFINAE区分签名和未签名

时间:2012-02-14 23:16:21

标签: c++ templates c++11 sfinae

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

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))
{
}

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

  

错误C2995: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错误)。那么为什么这不起作用呢?如何使用尽可能少的编程开销以及仅限标准的功能(如果可能的话)?

3 个答案:

答案 0 :(得分:8)

就个人而言,我会尽可能避免使用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>());
}

答案 1 :(得分:3)

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

  

如果包含表达式的两个函数定义满足一个定义规则...

,则认为涉及模板参数的两个表达式是等效的

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

  

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

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

答案 2 :(得分:1)

更常见的习惯用法是在返回类型上使用 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的重点是什么?