【问题标题】:valarray divide its elementvalarray 划分它的元素
【发布时间】:2016-02-25 13:20:42
【问题描述】:

当我将 valarray 除以其第一个元素时,只有第一个元素变为 1,其他元素保持其原始值。

#include <iostream>
#include <valarray>
using namespace std;

int main() {
    valarray<double> arr({5,10,15,20,25});
    arr=arr/arr[0]; // or arr/=arr[0];

    for(int value:arr)cout << value << ' ';
    return 0;
}

实际输出为:

1 10 15 20 25

预期的输出是:

1 2 3 4 5

为什么实际输出不如预期?

我使用 g++(4.8.1) 和 -std=c++11

【问题讨论】:

  • 似乎operator/ 使用了对arr[0]的引用
  • @tobi303 我认为你是对的,operator[] 返回对 arr[0] 的引用,并且引用直接传递给 operator/。谢谢
  • @tobi303 您介意发布答案吗?我觉得你的评论正是我想要的。
  • 我会,如果我能理解的话,但是operator/ 有签名template &lt;class T&gt; std::valarray&lt;T&gt; operator/ (const std::valarray&lt;T&gt;&amp; lhs, const std::valarray&lt;T&gt;&amp; rhs);(注意常量),因此不应该修改输入,而是创建一个临时存储它的结果(之前退货)。也许是valarray 的魔法……你真的和//= 一样吗??
  • 现在我很好奇,一旦有时间我会把它作为一个练习来实现操作符,也许我会得到它。

标签: c++ c++11 valarray


【解决方案1】:

这个有效:

#include <iostream>
#include <valarray>
using namespace std;

int main() {
    valarray<double> arr({5,10,15,20,25});
    auto v = arr[0];
    arr=arr/v; // or arr/=arr[0];

    for(int value:arr)cout << value << ' ';
    return 0;
}

问题是您正在尝试使用同时修改的数组中的值 (arr[0]) (arr)。
直观地说,一旦您通过执行arr[0]/arr[0] 更新了arr[0],它包含什么值?
嗯,这就是从现在开始用来划分其他值的值...
请注意,这同样适用于arr/=arr[0](首先,arr[0]/arr[0] 发生在 for 循环或类似的东西中,而不是其他所有)。
还要注意documentation 中的operator[]std::valarray 返回T&amp;。这证实了上面的假设:它作为迭代的第一步转向1,然后所有其他操作都无用。
只需复制它即可解决问题,如示例代码所示。

【讨论】:

  • 我知道我可以做到这一点,但我想知道为什么 arr/=arr[0] 不起作用
  • @appleapple 我已经写在答案中了。我强烈怀疑它是依赖于实现的,但直觉上,如果确实要求开发该方法,我会采用这种方式。
  • arr = arr/arr[0] 中不是先评估arr/arr[0] 然后将结果分配给arr 吗?
  • @tobi303 请参见here将复合赋值运算符应用于数值数组中的每个元素。因为arr[0]是通过引用返回的,其余的如答案中所述。
  • arr.operator=(arr.operator/(arr[0])); 不是首先评估arr.operator/(arr[0]),然后将其结果传递给operator=
【解决方案2】:

为什么会发生这种情况的细节是由于valarray 中使用的实现技巧来提高性能。 libstdc++ 和 libc++ 都使用expression templates 作为valarray 操作的结果,而不是立即执行操作。这是 C++ 标准中的 [valarray.syn] p3 明确允许的:

任何返回 valarray&lt;T&gt; 的函数都可以返回另一种类型的对象,前提是所有 valarray&lt;T&gt; 的 const 成员函数也适用于这种类型。

在您的示例中发生的情况是 arr/arr[0] 不会立即执行除法,而是返回像 _Expr&lt;__divide, _Valarray, _Constant, valarray&lt;double&gt;, double&gt; 这样的对象,该对象具有对 arr 的引用和对 arr[0] 的引用。当该对象被分配给另一个valarray 时,将执行除法运算并将结果直接存储到分配的左侧(这避免了创建临时valarray 来存储结果然后将其复制到左侧-手边)。

因为在您的示例中,左侧是同一个对象 arr,这意味着一旦 arr 中的第一个元素被删除,存储在表达式模板中的对 arr[0] 的引用将引用不同的值结果更新。

换句话说,最终的结果是这样的:

valarray<double> arr{5, 10, 15, 20, 25};
struct DivisionExpr {
  const std::valarray<double>& lhs;
  const double& rhs;
};
DivisionExpr divexpr = { arr, arr[0] };
for (int i = 0; i < size(); ++i)
  arr[i] = divexpr.lhs[i] / divexpr.rhs;

for循环的第一次迭代将arr[0]设置为arr[0] / arr[0],即arr[0] = 1,然后所有后续迭代将设置arr[i] = arr[i] / 1,这意味着值不会改变。

我正在考虑对 libstdc++ 实现进行更改,以便表达式模板将直接存储 double 而不是保存引用。这意味着arr[i] / divexpr.rhs 将始终评估arr[i] / 5 而不会使用arr[i] 的更新值。

【讨论】:

  • 当 rhs 是表达式模板时,该标准是否也(明确地)允许就地完成 operator=
  • 这就是使用表达式模板的重点,以避免创建任何临时 valarray 来存储结果。
  • 你想解释一下this code吗?太奇怪了……我刚试过arr = arr / arr[0] + arr,它给了我6 11 17 23 29! (如果我应该提出新问题,请告诉我。谢谢)
  • 解释是一样的:一些操作的评估是延迟的,稍后就地执行。您通过在赋值表达式的两侧以及在单个算术表达式中的多个位置使用 valarray 来滥用它。这使编译器的假设无效,即valarray 操作数不会相互别名。
  • 是的,完全一样。我可能还不够。谢谢。
猜你喜欢
  • 1970-01-01
  • 2017-10-09
  • 2014-06-05
  • 2020-03-27
  • 1970-01-01
  • 1970-01-01
  • 2016-04-23
  • 1970-01-01
  • 2018-12-10
相关资源
最近更新 更多