Watw
Watw

Reputation: 143

Using .sum() and += on std::valarray<T>

I am using the type std::valarray<std::valarray<double>> and wish to sum each of the contained valarrays element wise, to leave a std::valarray<double>.

The C++ documentation states that the operator .sum() can be applied to std::valarray<T> so long as the operator += is defined for type T. My code below (method1) tries to apply this to std::valarray<std::valarray<double>>, but the result appears to be nonsense.

However if I perform this manually, using the += operator (method2), I get the result I want. But the fact that method2 works seems to imply that the operator += is defined for the type std::valarray<double>, and hence that method1, using .sum(). should work. I really can't understand what is happening here...

My code:

#include <iostream>
#include <valarray>

// Attempt to use .sum() operator
std::valarray<double> method1(const std::valarray<std::valarray<double>>& data) {
  return data.sum();
}

// Manual summation using += operator
std::valarray<double> method2(const std::valarray<std::valarray<double>>& data) {
  std::valarray<double> sum(data[0].size());
  for (size_t i{0}; i < data.size(); i++) {
    sum += data[i];
  }
  return sum;
}

// Display size and elements
void showData(const std::valarray<double> data) {
  std::cout << "Size = " << data.size() << "\n";
  std::cout << "Data = ";
  for (size_t i{0}; i < data.size(); i++) {
    std::cout << data[i] << " ";
  }
  std::cout << "\n\n";
}

int main() {
  std::valarray<std::valarray<double>> data{{1,2},{3,4}};  
  showData(method1(data));
  showData(method2(data));
}

My output:

Size = 0
Data = 

Size = 2
Data = 4 6

Upvotes: 4

Views: 690

Answers (1)

Nelfeal
Nelfeal

Reputation: 13269

The sum method of std::valarray requires operator+= to be defined for its value type (in your case, std::valarray), but std::valarray also requires it to be default-constructible (from the "Numeric" concept requirement). This allows the sum method to work without operator+, by first default-constructing an element, and then adding each contained element with operator+=.

Although it isn't defined anywhere, as far as I know, it probably works something like this.

T sum() const {
    T result;
    for (auto& it : elements) {
        result += it;
    }
    return result;
}

The problem with a valarray of valarrays (std::valarray<std::valarray>) is that a default-constructed valarray is empty. And when operator+= is applied with an empty valarray and a non-empty one, it results in undefined behavior ("The behavior is undefined if size() != v.size()"). What you are likely to get is an empty valarray as a result (but you could potentially get anything).

What you could use instead is std::accumulate. It requires an initial value as third parameter, which takes care of the problem.

std::accumulate(std::begin(data), std::end(data), std::valarray<double>(data[0].size()))

Live on Coliru.

PS: don't ask me why std::valarray has no method begin and end.

Upvotes: 2

Related Questions