Monday, March 6, 2017

C++ tips, 2017 Week 7 (13-Feb - 19-Feb-2017)


This is part of my weekly C++ posts based on the daily C++ tips I make at my work. I strongly recommend this practice. If you don't have it in your company start it. 
List of all weekly posts can be found here.

1. Behavior

The C++ Standard defines several levels of behaviors that one should be aware of. If the program is well-formed (it follows the Standard syntax and compiles):
  • Specified - well... as the name suggests you can expect things to happen exactly as described in the Standard
  • Implementation defined - the Standard describes the outcome but how the input will be transformed into the output is up to the implementers. This is why for example
    std::map implementations differ between compiler vendors - it's up to them how to implement it.
  • Unspecified - Similar to implementation defined but the Committee intentionally left it undocumented. It is well-formed but you as a C++ user should not care exactly what it is. Take the returned type of std::bind for example. You can create a variable from what std::bind returns using auto and you can wrap it in std::functions type-erasing it but what it is exactly should not concern you.
  • Undefined behavior - a.k.a. the mother of all confusion. Basically, means that you should not make assumptions about what will happen in an undefined behavior situation and plan accordingly. It is usually for a reason - often we do not want to enforce too strict requirements on behavior [that introduce unnecessary functionality or disable compiler optimizations]. I think of it as an extreme variant of "don't pay for what you don't use". Reading from uninitialized local variables, for example, is UB. Yes, the Standard could have required all variables to be default initialized but that is often unnecessary and forces the compiler to default initialize them thus reducing the performance.

There are a lot of nice overviews on UB - here, here and here.

2. Std::valarray

std::valarray is a little-known container that is designed to act as an ordered set of values in more algebraic sense. If you add (using operator+) two valarrays with the same length you will get another valarray that has elements equal to the sum of the corresponding elements in the original two containers.

Actually, it is kind of a sad story. The people who proposed it left the Committee before it was standardized and nobody took ownership of it (Nicolai M. Josuttis, The C++ Standard Library, chapter 17.4). Also, it appears that people needing algebraic calculations usually make their own libraries.

Nevertheless, it is in STL so one should at least know of its existence but still - it has interesting design ideas.

std::valarray is not std::vector or std::array you can not insert or add elements into it. You have to set the size first and then fill with values - if you resize it it invalidates everything and you have to fill the values again. This is required for the support of taking subsets (for example slices - similar to BLAS (something I probably studied in the university but I won't pretend I remember)) that use lazy evaluation and return references to the original valarray thus you can emulate two-dimentional matrix and work with rows and columns. Look at operator[] - the workhorse behind this container.

Let's take a look at an example:
If you want to know more Josuttis has a free supplementary chapter on his site that includes valarrays.

3. Explicit

Explicit keyword specifies that a constructor or conversion operator should not be called silently and convert one object into another. Since I'm always not sure which is which I found that the best way to remember it is to think about non-explicit constructors/operators instead - they are the converting ones. They are allowed to convert an object into another one when there is opportunity - passing one object to a function that expects the other type, assigning, etc. The normal constructors can insert an additional behavior. Example:
And it is usually better to make all single argument constructors and conversion operators explicit ones - CppCheck fires a warning when they are not.

4.

I'm really behind schedule insert random excuses and because of that next several issues will be with three tips until I catch up.