【问题标题】:SFINAE differentiation between signed and unsignedSFINAE 区分有符号和无符号
【发布时间】:2012-03-06 08:07:39
【问题描述】:

我有将不同的算术类型转换为半精度浮点类型的函数(只是最低级别的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&lt;std::tr1::is_integral&lt;_Ty&gt;::value &amp;&amp; std::tr1::is_signed&lt;_Ty&gt;::value, T&gt;::type )":函数模板已定义。

所以看起来这两个模板之间无法消除歧义,但整数版本和浮点版本显然没有问题。

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

【问题讨论】:

  • 不清楚is_signed/is_unsigned 是否互斥(你好char?)。尝试将第二个版本改为 !std::is_signed&lt;T&gt;::value
  • 您可以尝试将std::is_signed&lt;T&gt;::value 用于其中一个成员,将!std::is_signed&lt;T&gt;::value 用于另一个成员吗?这只是为了确保不只是某些类型的 is_signedis_unsigned 设置不一致。
  • @KerrekSB & Dietmar 哈,做到了!不敢相信有那么容易。如果有人将其添加为答案,我会接受。
  • @Kerrek char 既不是有符号整数类型也不是无符号整数类型。但是 IIRC is_signedis_unsigned 会处理这一点:只有其中一个会为 char 报告 true
  • @JohannesSchaub-litb:你说得对,char 没问题。但是,枚举和指针总是错误的;我猜是因为它们不是算术类型。

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


【解决方案1】:

就我个人而言,我会尽可能避免使用 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 不错的选择。但作为旁注,我认为浮点版本的最后一个参数应该是std::true_type,因为浮点总是被签名的(至少是通常的实现,对于非 IEEE,我的转换代码无论如何都不起作用)。
  • @Christian:你完全正确;我基于 is_signed 的 MSDN 文档的逻辑,这恰好是错误的(惊喜,惊喜)。固定。
【解决方案2】:

如果这不起作用,则说明您的编译器出错了。

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

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

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

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

【讨论】:

  • is_unsigned 替换为!is_signed(反之亦然)是可行的,所以我猜(虽然不是很精通语言规范的深度,更不用说模板)有某种既已签名又未签名的类型。还是因为这些模板的默认版本对于非算术类型都评估为false?但话又说回来,还有is_integral 可以消除歧义(对于整数类型,它应该是互斥的,不是吗?)。
  • @Christian 我不知道他们在做什么,但这绝对是错误的行为。尝试!!is_unsigned 而不是!is_signed。看到它“工作”我也不会感到惊讶 :)
  • 哈,这也有效。好吧,现在我觉得这真的有点荒谬了。你可能是对的,编译器在这里出错了。
  • “如果两个包含表达式的函数定义满足一个定义规则,则两个涉及模板参数的表达式被认为是等价的……”您能否通过添加示例进一步解释这一点在你的帖子里?
  • 你的答案只对了一半。编译器拒绝代码是正确的(见下面我的回答),但它给出了错误的理由。
【解决方案3】:

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

【讨论】:

  • 这不应该和参数类型中的 SFINAE 有同样的歧义问题吗?是否有一些规则可以使这项工作有效但参数版本没有?否则,它似乎并不能解决这个问题。
  • 有一个非常好的理由不要在参数类型上使用 SFINAE,请参阅编辑后的答案。
猜你喜欢
  • 1970-01-01
  • 2016-11-14
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 2017-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多