glibcxx STL 在 std::valarray::sum() 的实现中是不是不正确?

Posted

技术标签:

【中文标题】glibcxx STL 在 std::valarray::sum() 的实现中是不是不正确?【英文标题】:Is the glibcxx STL incorrect in its implementation of std::valarray::sum()?glibcxx STL 在 std::valarray::sum() 的实现中是否不正确? 【发布时间】:2018-10-17 18:37:46 【问题描述】:

当我遇到我认为编译器的 STL 实现中的错误时,我正在玩弄 valarrays。这是我可以产生的最小示例:

#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <valarray>

using namespace std;

int main()

    valarray<int> Y(0xf00d, 1);
    valarray<valarray<int>> X(Y, 1);
    cout << "Y[0]           = " << std::hex << Y[0]       << '\n';
    cout << "X[0][0]        = " << std::hex << X[0][0]    << '\n';
    cout << "X[0].size()    = " << X[0].size()            << '\n';
    cout << "X.sum().size() = " << X.sum().size()         << '\n';

这将输出:

$ g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Y[0]           = f00d
X[0][0]        = f00d
X[0].size()    = 1
X.sum().size() = 0

你可以在coliru编译运行它

为什么我认为这是一个错误?因为按照标准(26.6.2.8)

T sum() const;

这个函数只能为类型 T 实例化 可以应用哪个运算符+=。这个函数返回所有的总和 数组的元素。如果数组的长度为 0,则行为 未定义。如果数组的长度为 1,则 sum() 返回 元素0。否则,通过应用计算返回值 operator+= 到数组元素和所有其他元素的副本 以未指定的顺序排列数组。

valarray 确实有一个+= operator

所以我希望X.sum()X[0] 具有相同的值。但显然不是这样,因为它的大小是 0 而不是 1。

我查看了sum() 的实现并将其追溯到这段代码:

  //
  // Compute the sum of elements in range [__f, __l)
  // This is a naive algorithm.  It suffers from cancelling.
  // In the future try to specialize
  // for _Tp = float, double, long double using a more accurate
  // algorithm.
  //
  template<typename _Tp>
    inline _Tp
    __valarray_sum(const _Tp* __f, const _Tp* __l)
    
      _Tp __r = _Tp();
      while (__f != __l)
        __r += *__f++;
      return __r;
    

而且我们知道问题出在哪里。代码将总和累加到__r,但不是用valarray 中的第一项初始化__r,而是默认构造。 valarray 的默认构造函数会创建一个大小为 0 的数组。所以最终的结果仍然是一个大小为 0 的 valarray。

我对标准的理解是否有效(并且 glibcxx STL 存在错误)?还是应该改正?

为了记录,我在cygwin下使用g++ 7.3.0,但它是在coliru上复制的,它可能不在cygwin下运行......

【问题讨论】:

是的,__r 应该是现有元素的副本。 clang 按您的预期工作:wandbox.org/permlink/Imd52JDct8hRFWSB. 也适用于 cl。 谢谢,我没有设置来测试其他编译器。我想我会开一个错误票。我害怕被公然错误... 【参考方案1】:

这对我来说是一个错误。 sum()

需要: size() &gt; 0。此函数只能针对可以应用operator+= 的类型T 实例化。

并且valarray 确实有operator +=,所以它符合条件。我是operator +=

需要: size() == v.size()。如果指定的运算符可以应用于类型 T 的两个操作数,则这些运算符中的每一个只能针对类型 T 实例化。 valarray 复合赋值运算符左侧元素的值不依赖于左侧的另一个元素。

因此,通过执行_Tp __r = _Tp();,它们会生成一个valarray,其size() 不等于元素的大小,因此它不能与operator += 一起使用。更正确的实现是

  template<typename _Tp>
    inline _Tp
    __valarray_sum(const _Tp* __f, const _Tp* __l)
    
      _Tp __r = *__f++; // this is okay as the function is requires size > 0.  It is the users responsibility to make sure that is the case
      while (__f != __l)
        __r += *__f++;
      return __r;
    

【讨论】:

谢谢,我已经创建了一个 bugzilla 问题:gcc.gnu.org/bugzilla/show_bug.cgi?id=87641 当在 GCC 方面确认错误时,我会接受你的回答。 @fjardon 酷。这是一个很快的转变。 现在在所有活动的 GCC 分支中修复,完全使用此处显示的解决方案(这不仅使函数正确,而且避免检查第一个元素的循环条件,这是多余的,因为 __f != __l 最初)。 @JonathanWakely 我的代码中有一个错误(多么讽刺,对)我有while (__f++ != __l),但它应该是while (__f != __l) 啊,好点子!所以我的解决方法正是你的回答现在,而不是十分钟前:)

以上是关于glibcxx STL 在 std::valarray::sum() 的实现中是不是不正确?的主要内容,如果未能解决你的问题,请参考以下文章

在 centos 上找不到 GLIBCXX_3.4.26

在哪里可以找到有关 D_GLIBCXX_DEBUG 和 DNDEBUG 标志的更多信息?

错误:未找到版本“GLIBCXX_3.4.21”

使用 -D_GLIBCXX_USE_CXX11_ABI=0 构建提升

_GLIBCXX_USE_CXX11_ABI 在 RHEL6 和 RHEL7 上禁用?

编写生成文件以在调试构建中使用 -D_GLIBCXX_DEBUG