【问题标题】:Why specializing a type_trait could result in undefined behaviour?为什么特化 type_trait 会导致未定义的行为?
【发布时间】:2014-10-10 07:33:06
【问题描述】:

讨论

根据标准§20.10.2/1 Header <type_traits> synopsis [meta.type.synop]:

1 除非另有说明,否则为本子条款中定义的任何类模板添加特化的程序的行为是未定义的。

这个特定的子句与 STL 应该是可扩展的一般概念相矛盾,并阻止我们扩展类型特征,如下例所示:

namespace std {
template< class T >
struct is_floating_point<std::complex<T>> : std::integral_constant
         <
         bool,
         std::is_same<float, typename std::remove_cv<T>::type>::value  ||
         std::is_same<double, typename std::remove_cv<T>::type>::value ||
         std::is_same<long double, typename std::remove_cv<T>::type>::value
         > {};
}

LIVE DEMO

其中std::is_floating_point 被扩展为处理complex 具有底层浮点类型的数字。

问题

  1. 是什么原因使标准化委员会决定不应专门化类型特征。
  2. 未来是否有取消此限制的计划。

【问题讨论】:

  • @billz 怎么样?该程序发布了展品 UB。
  • 为什么不struct is_floating_point&lt;std::complex&lt;T&gt;&gt; : public std::is_floating::point&lt;T&gt; {};?
  • @Manu343726 是的排序更好,但也无关紧要,因为您的版本也表现出未定义的行为。
  • 即使它被允许,特化 std::is_floating_point 并使您的整个翻译单元将 std::complex 视为浮点类型,这样您就可以检查“是 std::complex 还是浮点数“在你的一个功能中,就像炸毁你的房子来呼吸新鲜空气一样。类型特征类查询类型的基本属性,是模板元程序的基本构建块。允许你为他们添加专业化是没有意义的。

标签: c++ c++11 language-lawyer undefined-behavior c++14


【解决方案1】:

对于主要类型类别,is_floating_point 是一个,有一个设计不变量:

对于任何给定的类型T,恰好有一个主要类型类别具有 计算结果为 true 的值成员。

参考:(20.10.4.1 Primary type categories [meta.unary.cat])

当检查一些未知的泛型类型T时,程序员可以依赖泛型代码中的这个不变量:即如果is_class&lt;T&gt;::value 是true,那么我们不需要检查is_floating_point&lt;T&gt;::value。我们保证后者是false。

这是一个图表 表示主要和复合类型特征(此图顶部的叶子是主要类别)。

http://howardhinnant.github.io/TypeHiearchy.pdf

如果允许(例如)std::complex&lt;double&gt; 对is_class 和is_floating_point 都回答为真,那么这个有用的不变量将被破坏。如果is_floating_point&lt;T&gt;::value == true,那么T 必须是float、double 或long double 之一,程序员将不再能够依赖这一事实。

现在有一些特征,标准确实“另有规定”,并且允许对用户定义类型进行专门化。 common_type&lt;T, U&gt; 就是这样一个特点。

对于主要和复合类型特征,没有计划放宽对这些特征的专业化限制。这样做会损害这些特征对可以在 C++ 中生成的每一种类型进行精确和唯一分类的能力。

【讨论】:

  • 还有一个“不变量”,主要类型类别特征也反映了事实。也就是说,标准中具体描述了诸如“浮点类型”之类的类别——它们非常不可扩展或用户可定制。因此,用户定义的专业化要么重述显而易见的事实,要么撒谎。
  • @Deduplicator:好的,我已经删除了嵌入!。
  • 如何修改标准中的错误?就像制作std::complextrivially_default_constructible(我们知道应该是)godbolt.org/z/lQlMTy。
【解决方案2】:

补充霍华德的答案(举例)。

如果允许用户专门化类型特征,他们可能会撒谎(有意或无意地),标准库将无法再保证其行为正确。

例如,当复制std::vector&lt;T&gt; 类型的对象时,流行的实现所做的优化是调用std::memcpy 来复制所有元素只要T 可以简单地复制构造。他们可能会使用std::is_trivially_copy_constructible&lt;T&gt; 来检测优化是否安全。如果不是,那么实现会退回到安全但速度较慢的方法,该方法循环遍历元素并调用T 的复制构造函数。

现在,如果有人像这样将std::is_trivially_copy_constructible 专门用于T = std::shared_ptr&lt;my_type&gt;:

namespace std {
    template <>
    class is_trivially_copy_constructible<std::shared_ptr<my_type>> : std::true_type {
    };
}

然后复制std::vector&lt;std::shared_ptr&lt;my_type&gt;&gt; 将是灾难性的。

这不是标准库实现的错,而是专业化编写者的错。在某种程度上,这就是 OP 提供的报价所说的:“这是你的错,不是我的。”

【讨论】:

  • 这个例子非常有指导意义,很好地解决了问题。
  • 我认为这个例子不能正确地说明这个问题。这个例子产生了损坏的程序,不是因为我们特化了一个我们不应该特化的类型,而是因为我们提供了关于一个类型的错误信息(共享指针)。如果我们从 false_type 继承,程序的行为同样是未定义的,而实际上它可以正常工作,因为专业化会提供正确的信息。作为旁注,还必须为库正确实施复制 ctor 以保证正确的矢量复制,但实施它并不违法。
猜你喜欢
  • 2021-09-01
  • 2012-01-08
  • 2016-07-18
  • 2014-08-11
  • 2016-09-04
  • 1970-01-01
  • 1970-01-01
  • 2011-11-21
  • 2018-12-31
相关资源
最近更新 更多