![]() |
Quantify
An extensible C++ units library
|
A quantity combines a numerical value with an associated unit. The unit provides meaning to the value, allowing it to be interpreted within a physical or conceptual domain.
In Quantify values are managed by the quantify::quantity class template. This class implements unit related logic delegating the ownership of the value itself to the specified data type.
The quantify::quantity class template needs two arguments for its specialization:
The constructor takes a value of specified underlying data type and stores a copy inside the quantity. Here is an example:
Using a type alias, one can write the same in a more readable way:
This is specially useful because it decouples the unit from the underlying type. It avoids having to specify the type for each quantity, which may be error-prone in large code bases. It also makes it easy to change the type later if needed. Some use cases may even create more than one alias for different purposes. For instance, a physics engine dealing with both continuous and discrete algorithms may use two aliases as follows:
More often than not, quantities will be the result of operations involving other quantities. There are several ways to approach this depending on how much the algorithm needs to be tied down to specific units and types.
The first option is to explicitly state the unit and type:
No surprises here, the result will be a quantity with units [m/s] and of type float. Since the input quantities are of different units, there will be a temporary quantity with units [mi/h] which will be converted to [m/s] on construction of vel.
Alternatively, the unit scale can be specified instead using the quantify::Quantity concept. This has the result of accepting any quantity of any unit as long as it belongs to that scale. It avoids unnecessary unit conversions while still fully checking for consistency:
The associated unit for vel will be the same as that for the temporary value being passed to the constructor, which in this case is [mi/h]. On the other hand, the variable nonsense will not compile because it is expecting a ratio of time over mass and cannot be initialized with a quantity of speed.
This works the same way for function parameters, allowing for fully unit agnostic functions like this one:
The underlying value is a public field, so it can be accessed directly:
The method value_as_base_unit() returns a copy of the value as converted to the corresponding base unit. For instance, calling it on any quantity of distance will return the equivalent amount in Meters.
This class implements value semantics, that is, the lifetime of the value is tied to the lifetime of the quantity. A value cannot be moved out of a quantity. Moreover, most operators return a new instance of quantity instead of modifying the operated quantity in place, similar to how arithmetic types behave in C++. The notable exception to this rule are naturally in place operators such as += or =.
Quantify does not make the choice as to how or where the numerical value of a quantity is stored. This is deferred to the underlying data type, which is specified as a template argument to the quantify::quantity class template.
The underlying data type must be default constructible as well as copy and move constructible. It must be copy assignable using the = operator. It should also handle most, if not all, arithmetic and comparison operators. Since all arithmetic and comparison methods in quantify::quantity are method templates, there is no requirement for the underlying data type to implement every single operator. If one, such as +, < or ==, is not defined in some type T, then the corresponding quantify::quantity specialization quantity<Unit,T> will be missing that definition as well. No compilation error or warning will be reported unless said missing operator is attempted to be used.
In general, operating on quantities with differing underlying data types will result in unpredictable type conversions. It is recommended that all the quantities used in an expression be of the same type. In order to cast a quantity to a different type, one must create a new quantity with the desired type.