8

我有将不同的算术类型转换为半精度浮点类型的函数(只是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 错误)。那么为什么这不起作用,如何在尽可能少的编程开销和仅标准功能的限制(如果可能的话)的情况下使其工作?

4

3 回答 3

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>());
}
于 2012-02-14T23:28:11.653 回答
3

如果这不起作用,那么您的编译器就会出错。

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

这是这里要考虑的最重要的规则(省略“...”的细节)。您的两个模板不满足 ODR,因为它们的标记序列不同。

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

所以你的两个模板定义了不同的模板并且不冲突。您现在可以检查您的模板是否“功能等效”。如果对于任何可能的模板参数集,您的enable_if表达式将始终产生相同的值。但由于 和 不是这样is_unsignedis_signed所以也不是这样。如果是这样,那么您的代码将是格式错误的,但不需要诊断(这实际上意味着“未定义的行为”)。

于 2012-02-14T23:27:56.853 回答
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> 

是可推断的(甚至是微不足道的),但对于您的原始代码,它不是!实际上,当to_half()通过 clang 使用您的实现运行此语句时

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 有什么意义呢?

于 2014-04-21T14:42:49.850 回答