Introduction To valarray

valarray is std:: vector’s  unpopular nerdy little brother.  It was designed for multi-dimensional numeric calculations.  While it may sound like a niche tool for scientific applications, It actually can be useful in other kinds of software.

Consider a vector representing a shopping list items prices,  and another vector representing the discount for each item:

#include <iostream>
#include <vector>
#include <string>
#include <sstream>

template <typename C>
std::string  seqTostring(C c)
{
    std::stringstream ss;
    ss << '(';
    for (auto it = std::begin(c); it != std::end(c); it++)
    {
        ss << *it;
        if (std::next(it) != end(c)) ss << ",";
    }
    ss << ')';
    return ss.str();
}

void calculatePrice()
{
    std::vector<double> itemPrice{ 11 , 44, 5.2, 1.99 };
    std::vector<double> itemDiscount{0 , 0.2, 0.4, 0 };
    std::vector<double> finalPrice(itemPrice.size());

    for (size_t i = 0; i < itemPrice.size(); i++)
    {
        finalPrice[i] = itemPrice[i] * (1.0 - itemDiscount[i]);
    }
    std::cout << "List of final prices: " << seqTostring(finalPrice) << '\n';
}

int main()
{
    calculatePrice();
    return 0;
}

List of final prices: (11,35.2,3.12,1.99)

Doing the same calculation with  valarray  is as straightforward as can be:


#include <valarray>
void calculatePrice()
{
   std::valarray<double> itemPrice{ 11 , 44, 5.2, 1.99 };
   std::valarray<double> itemDiscount{0 , 0.2, 0.4, 0 };
   std::valarray<double> finalPrice = itemPrice * (1.0 - itemDiscount);
   std::cout << "List of final prices: " << seqTostring(finalPrice) << '\n';
}

List of final prices: (11,35.2,3.12,1.99}

Notice how each of the elements in the itemDiscount array is individually subtracted from 1.0 before being multiplied by the corresponding element in the itemPrice array. In addition to producing a loop-free code, valarray numeric specialization allows compilers to emit highly optimized machine code under the assumption the elements in valarray are usually processed as numerical values.

For primitive numerical values such as int and double,  valarray supports, out of the box, any  type of arithmetic and logical operation:

int main()
{
 	std::valarray<unsigned int> values{ 1 /*1000*/, 13 /*1101*/ , 8 /*0001 */ };
	std::valarray<unsigned int> andResult = 8U & values;
	std::cout << "Logical AND of " << seqTostring(values) << " with 0001b : "
		<< seqTostring(andResult) << '\n';
	return 0;
}

Logical AND of (1,13,8) with 0001b : (0,8,8)

In addition, some mathematical functions, such as tan and sqrt,  are overloaded to accept a valarry:


int main()
{
	std::valarray<int> vals{ 144 , 16, 4, 81 };
	std::cout << "sqrt of  " << seqTostring(vals) << " = " << seqTostring(sqrt(vals)) << '\n';
	return 0;
}

sqrt of  (144,16,4,81) = (12,4,2,9)

However, if the function you need is not included in the standard library   you can define your own operation with the apply() method:

int main()
{
	std::valarray<int> vals{ 97,54,84,76 };
	std::valarray<int> applyResult = vals.apply([](int val)	{
		auto s = std::to_string(val);
		return std::stoi(s + s);});
	std::cout << seqTostring(vals) << " converted to " << seqTostring(applyResult) << '\n';
}

Contrary to what you would expect, apply accepts a lambda but will not accept a function object! this is the first hint of the somewhat skewed way valarray integrates with the rest of the C++ standard library.  It appears as if valarry started on a different evolutionary branch, apart from the rest of the library.  Methods such as begin(), end(),  clear()    we have gotten used to finding in a container class are missing.   If you wish to add more elements than the array capacity you must call valarray‘s  resize()  member manually with the new size  (invalidating all contained elements as a side effect )  than re-insert all old elements and add new elements  (a sort of manual reallocation).  It also means calling resize(0) effectively clear the array.

An empty valarry cause undefined behavior in simple workflows such as trying to call sum().  So you should be extra careful when working with empty arrays.
Another peculiarity of valarray implementation is an occasional compiler’s  failure to cast correctly returned types when valarrys are involved.  rewriting the previous apply() example as below compile’s fine in Visual Studio 2015, but not in GCC 4.9.2 :

int main()
{
	std::valarray<int> vals{ 97,54,84,76 };
	std::cout << seqTostring(vals) << " converted to " <<
		seqTostring(vals.apply(doubleTheNumbers)) << '\n';
}

Getting this and other instances of code involving valarrays require liberal use of static_cast<varlarry>  in order to point the compiler in the right direction.

C++ 11  took a few steps to bridge the gap,  among several updates: valarray got its own overload for the begin()/end() functions allowing it play better with the STL containers and algorithms:

std::valarray<int> vals = { 54,94,82,52,3,91,57,37,77,38 };
	std::sort(begin(vals), end(vals));
	std::cout << " sorted array: " << seqTostring(vals) << '\n';

(97,54,84,76) converted to (9797,5454,8484,7676)

Instantiating valarray with a user types  T  works in a similar way to other containers. The standart requires that constructing T with a copy constructor must be the same as instantiating T with a default constructor and using the assignment operator to assign it.  For example,  given:


struct valType
{
	int i;
	int j;
};

than

struct valType
{
	int i;
	int j;
};

valType val1{ I1,I2 }; 
valType  val2;
val2 = val1;

valType val3(val1);
assert (val2 == val3); // never fails for any I1, I2

For the full list of requirements of T see  C++ 11 standard, ISO/IEC 14882:2011(E)  section 26.2

Once you do come up with the proper type,  you will have to overload any functions you might need:

std::ostream& operator<<(std::ostream& os, const valType& vt)
{
	os << '{' << vt.i << ',' << vt.j << '}';
	return os;
}

valType sqrt(valType vt)
{
	valType res;
	res.i = sqrt(vt.i);
	res.j = sqrt(vt.j);
	return res;
}

int main()
{
	std::valarray<valType> vals{ { 4,4 },{ 16,9 },{ 256,1 },{ 1,64 } };
	std::valarray<valType> vals_sqrt = sqrt(vals);
	std::cout << seqTostring(vals) << " sqrt: " << seqTostring(vals_sqrt) << '\n';
	std::cout << seqTostring(vals) << " sum:  " << vals.sum() << '\n';
}

({4,4},{16,9},{256,1},{1,64}) sqrt: ({2,2},{4,3},{16,1},{1,8})
({4,4},{16,9},{256,1},{1,64}) sum: {277,78}

In spite of valarray’s quirks,  it can be a powerful tool when  applied with care.

In the next post, I’ll discuss the valarray [] operator, it is overloaded with som much functionality it warrants its own post.

for further reading see cppreference.com   and chapter 26.6 of the C++ 11 standard = ISO/IEC 14882:2011(E)