【问题标题】:implicit promotion to avoid overflow in std::partial_sum隐式提升以避免 std::partial_sum 中的溢出
【发布时间】:2010-02-03 02:36:43
【问题描述】:

此代码存在溢出问题,因为中间结果的类型不依赖于目标类型:

vector< uint8_t > increments;
…
vector< uint32_t > increasing( increments.size() );
partial_sum( increments.begin(), increments.end(), increasing.begin() );

但是,这也是如此(GCC 4.2):

partial_sum( increments.begin(), increments.end(), increasing.begin(),
             plus< uint32_t >() );

不应该plus&lt; uint32_t &gt; 提升其操作数并避免溢出吗?

编辑:我太沉迷了。短暂休息后,我坐下来检查实施情况。它这样做:

  /* input_iterator::value_type */ __value = __binary_op(__value, *__first);
  *++__result = __value;

我认为这不合规,所以我会检查最新版本并可能会提交错误……我们开始吧:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=42943

【问题讨论】:

  • 我认为你是对的 - 问题不在于 plus 是否转换其参数(它当然会),问题在于 26.4.3/1 表示分配给第二个输出是binary_op(*first, *(first+1)),而不是InputIterator::value_type(binary_op(*first, *(first+1))。但是应该如何实施呢?如果 BinaryOperation 有一个result_type,那么它可能会使用它,但函数没有 result_type,当然你可以将函数指针传递给 partial_sum?也许对我来说太晚了......
  • 啊,函数指针。我找不到操作对象的任何具体要求,除了 §25/8 (sorta),它只是可以用两个参数调用。模板可以从函数中提取返回类型,尽管这会使更改产生更大的影响。
  • 与其从BinaryFunction推导出类型,不如用OutputIterator的value_type代替InputIterator的类型?
  • @visitor:我假设是因为例如 OutputIterator 可能具有非常狭窄的值类型,在这种情况下,您也不希望在实际分配之前丢弃信息。当 BinaryFunction 是模加法时没有区别,因为提前进行转换不会改变结果,但对于其他函数会。但更糟糕的是,OutputIterator 的值类型甚至可能无法转换为 binary_op 的参数类型。标准并没有说它必须是,所以实现不能假设它。
  • 我的意思是,为了避免溢出,您宁愿从较小的类型转到较大的类型,而不是尝试将结果存储在更小的类型中。最终,如果类型不同,则要么存在转换,要么您以某种方式得到编译器错误。还有为什么不能给它一个可以接受输出类型的binary_op呢?

标签: c++ stl overflow


【解决方案1】:

根据http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#539partial_sum自n3000(最新版本)以来已经完全重新定义:

效果:设 VT 为 InputIterator 的 值类型。对于非空范围, 初始化 acc 类型的累加器 VT 与 *first 并执行 *result = 符合。对于 [first + 1,最后)按顺序,则 acc 是 由 acc = acc + *i 或 acc = 修改 binary_op(acc, *i) 并分配给 *(结果 + (i - first))。

“扩大”行为可以是 通过编写自定义代理获得 迭代器,这有点涉及。

我真的看不出以这种方式做事的好处。阅读缺陷报告,我没有看到任何理由除了

算法的目的是使用 输入迭代器的类型。

啊。

编辑:我继续实施了一个扩大的输入迭代器。像宣传的那样工作。

template< class Base, class Wider >
struct widen_iter : iterator< input_iterator_tag, Wider > {
    Base b;
    widen_iter( Base const &inb = Base() ) : b( inb ) {}
    Wider operator*() const { return Wider( *b ); }
    Wider const *operator->() const { Wider t( *b ), *ta = &t; return ta; }
    widen_iter &operator++() { ++ b; return *this; }
    widen_iter operator++(int) { widen_iter t = *this; ++ b; return t; }
    bool operator==( widen_iter const &r ) const { return b == r.b; }
    bool operator!=( widen_iter const &r ) const { return b != r.b; }
};
template< class Wider, class Base >
widen_iter< Base, Wider >
widener( Base b ) { return widen_iter< Base, Wider >( b ); }

如果有一个通用的按函子过滤输入迭代器,会短很多。

【讨论】:

  • 使用 std::accumulate 代替,init 值存储和递增输出迭代器并使用更广泛的类型来存储运行总数?累积保证加法是按顺序执行的。
  • +1 不明白为什么避免溢出比实现溢出更难。作为解决方案:编写自己的 partial_sum?
  • @Steve:这可能是一个更短的解决方案。 init 只需实现 operator+operator=。不过非常具体。 @Uncle:是的,看起来这也是一个更实用的解决方案,尽管正确执行(接受 fn ptrs)需要模板自省。
  • @Potatoswatter:您可以编写自己的partial_sum_convert,这与标准的相同,只是多了一个模板参数AccumulatorType。将其设为第一个参数,这样您在调用它时就不必指定迭代器类型。然后不需要自省,只需调用partial_sum_convert&lt;uint32_t&gt;(first, last, out, op);。如果 op 的返回类型不能赋值给 AccumulatorType,或者 op 不能以 (AccumulatorType, InputIterator::value_type) 作为参数,那就是调用者的问题。
  • ... 仍然非常具体,但是这样做是为了解决使用 partial_sum 的特定困难。如果您的扩展迭代器还有其他用途,那么总体而言这可能是一个更好的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-23
  • 1970-01-01
  • 2012-04-28
  • 2017-08-28
  • 2018-04-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多