4

When I divide a valarray by its first element, only the first element becomes 1 and others keep their original value.

#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;
}

The actual output is:

1 10 15 20 25

The expected output is:

1 2 3 4 5

Why is the actual output not as expected?

I use g++(4.8.1) with -std=c++11

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
apple apple
  • 10,292
  • 2
  • 16
  • 36
  • 1
    seems like `operator/` uses a reference to `arr[0]` – 463035818_is_not_an_ai Feb 25 '16 at 13:34
  • @tobi303 I think you are right, operator[] return the reference to arr[0] and the reference is directly pass to operator/. thank you – apple apple Feb 25 '16 at 13:41
  • @tobi303 Would you mind to post an answer? I think your comment is really what I want. – apple apple Feb 25 '16 at 13:56
  • I would, if I would understand it, but `operator/` has the signature `template std::valarray operator/ (const std::valarray& lhs, const std::valarray& rhs);` (note the const) and thus should not modify the input but instead create a temporary to store its result (before returning it). Maybe it is some `valarray` magic... do you really get the same with `/` and `/=` ?? – 463035818_is_not_an_ai Feb 25 '16 at 17:01
  • now i got curious, once I have time I will take it as an exercise to implement the operator, and maybe then I will get it. – 463035818_is_not_an_ai Feb 25 '16 at 17:02
  • @tobi303 oh! i miss that point! this is really strange. The first time i meet this problem is using the /= version, and I am try the / version... And got confused. – apple apple Feb 26 '16 at 10:25
  • And if I use VC++, the `operator/` version operate as expected. Maybe this is a bug in g++ – apple apple Feb 26 '16 at 10:41
  • for the `/=` operator this is what I would expect, because it calls `/=` in a loop for each element, but I am still confused about `=` together with `/` as I would expect the division to happen before any assignment takes place – 463035818_is_not_an_ai Feb 26 '16 at 12:11
  • If I use VC++, the `operator/` version operate as expected -- it's output `1 2 3 4 5`. so this might be a bug in g++(output '1 10 15 20 25') – apple apple Feb 27 '16 at 07:11

2 Answers2

3

This one works:

#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;
}

The problem is that you are trying to use a value (arr[0]) from an array that you are modifying at the same time (arr).
Intuitively, once you have updated arr[0] by doing arr[0]/arr[0], what value does it contain?
Well, that's the value that will be used from now on to divide the other values...
Please, note that the same applies for arr/=arr[0] (first of all, arr[0]/arr[0] takes place, than all the others, in a for loop or something like that).
Also note from the documentation that operator[] of a std::valarray returns a T&. This confirms the assumption above: it is turned to 1 as the first step of your iteration, then all the other operations are useless.
By simply copying it solves the issue, as in the example code.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • I know I can do this, but I want to know why the arr/=arr[0] doesn't work – apple apple Feb 25 '16 at 13:29
  • 1
    @appleapple I've written it in the answer. I strongly suspect that it is implementation dependent, but intuitively that's the way I'd have gone if had asked to develop that method indeed. – skypjack Feb 25 '16 at 13:31
  • in `arr = arr/arr[0]` isnt first `arr/arr[0]` evaluated and then the result assigned to `arr` ? – 463035818_is_not_an_ai Feb 25 '16 at 20:19
  • @tobi303 See [here](http://en.cppreference.com/w/cpp/numeric/valarray/operator_arith2): *Applies compound assignment operators to each element in the numeric array*. Because `arr[0]` is returned by reference, the rest is as explained in the answer. – skypjack Feb 25 '16 at 20:27
  • 1
    with `arr.operator=(arr.operator/(arr[0]));` isnt first `arr.operator/(arr[0])` evaluated and then the result of this passed to `operator=` ? – 463035818_is_not_an_ai Feb 25 '16 at 21:02
  • @user463035818 for most types, yes, but `valarray` is special. See [valarray.syn] p3 which allows `arr/arr[0]` to return an expression template that delays evaluation until the result is needed. See my answer for more details. – Jonathan Wakely Apr 05 '18 at 23:08
1

The details of why this happens are due to implementation tricks used in valarray to improve performance. Both libstdc++ and libc++ use expression templates for the results of valarray operations, rather than performing the operations immediately. This is explicitly allowed by [valarray.syn] p3 in the C++ standard:

Any function returning a valarray<T> is permitted to return an object of another type, provided all the const member functions of valarray<T> are also applicable to this type.

What happens in your example is that arr/arr[0] doesn't perform the division immediately, but instead it returns an object like _Expr<__divide, _Valarray, _Constant, valarray<double>, double> which has a reference to arr and a reference to arr[0]. When that object is assigned to another valarray the division operation is performed and the result stored directly into the left-hand side of the assignment (this avoids creating a temporary valarray to store the result and then copying it into the left-hand side).

Because in your example the left-hand side is the same object, arr, it means that the reference to arr[0] stored in the expression template refers to a different value once the first element in arr has been updated with the result.

In other words, the end result is something like this:

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;

The first iteration of the for-loop will set arr[0] to arr[0] / arr[0] i.e. arr[0] = 1, and then all subsequent iterations will set arr[i] = arr[i] / 1 which means the values don't change.

I'm considering making a change to the libstdc++ implementation so that the expression template will store a double directly instead of holding a reference. This would mean arr[i] / divexpr.rhs will always evaluate arr[i] / 5 and not use the updated value of arr[i].

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • does the standard also (explicitly) allow `operator=` to be done in-place when rhs is expression templates? – apple apple Apr 06 '18 at 09:03
  • That's kinda the point of using expression templates, to avoid creating any temporary valarray to store the result. – Jonathan Wakely Apr 06 '18 at 09:13
  • would you like to explain [this code](https://wandbox.org/permlink/OtvW3JxZaByPlvYz)? It's so strange... I just tried `arr = arr / arr[0] + arr` and it gives me `6 11 17 23 29` ! (if I should open new question, let me know. thanks) – apple apple Apr 06 '18 at 09:38
  • The explanation is the same: evaluation of some operations is delayed and performed in-place later. You're misusing `valarray` by using it on both side of an assignment expression, and in multiple places within a single arithmetic expression. This invalidates the compiler's assumption that `valarray` operands do not alias each other. – Jonathan Wakely Apr 06 '18 at 10:54
  • yes, It'a absolutely the same. I probably not though enough. thanks. – apple apple Apr 06 '18 at 11:51
  • I would accept this answer since it gives the reason, but I'm still curious is the compiler allowed to do this on assignment. (at least it does not prohibit, I think.) – apple apple Apr 06 '18 at 11:53
  • My interpretation is what I already said in the previous comment: the compiler can assume you don't use the same valarray on both sides of the assignment, and so your code is not using valarray correctly. So the compiler can do anything it likes. – Jonathan Wakely Apr 06 '18 at 12:34