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() > 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() 的实现中是不是不正确?的主要内容,如果未能解决你的问题,请参考以下文章
在哪里可以找到有关 D_GLIBCXX_DEBUG 和 DNDEBUG 标志的更多信息?
使用 -D_GLIBCXX_USE_CXX11_ABI=0 构建提升