One of the first things that every theoretical physics student learns is how to properly match the physical dimension on both sides of an equation. This is a very simple method to check for errors in a computation. If a length comes out to be equal to an area, or a mass quantity suddenly has a few seconds to its value, there is a pretty good chance that an error happened somewhere in the middle of the paperwork. In the end, it is good practice to keep track of such dimensions.
A clever application of C++ templates allows to keep track of the dimension of physical quantities. With the help of the newly introduced C++11 string literals feature as well as the
std::ratio
template prototype, different unit measures and fractional dimensions can easily be used and conveniently integrated.
Investigating the problem at hand
Compile-time vs. run-time checks
When we deal with numbers in a computer (or rather a programming language), there is no fundamental concept of a physical dimension. One finds integer numbers and floating-point numbers in different shapes and sizes, but dimensions are not a fundamental built-in concept that a computer understands. Of course, one can add such meta-information by hand and create derived objects together with customized operators, that mimic the natural mathematical operations and keep track of the unit dimension. But such approaches—if not done properly—easily result in a quite significant run-time overhead. On a modern CPU and FPU a single elementary mathematical operation (addition, multiplication and so on) usually only takes a single CPU clock cycle to finish, such that even an apparently minimal overhead leads to a significant decrease in the run-time performance. Furthermore, keeping track of dimensional errors at run-time for static equations that are written in the source code is a somewhat futile approach. While there is a certain value in a detailed exception popping up, that highlights the precise dimension mismatch, it would be much more convenient to have the erroneous code segment blowing up already at compile-time.
The C++ programming language has a very distinctive and incredibly powerful language feature which allows to facilitate such a compile-time dimension check: template meta programming. Templates allow to write C++ code sections without specifying a type for some function parameters or variables, which in effect allows to reuse the same code segment. For example, think about some mathematical operation that is both well-defined for integer and floating-point types. Most of the C++ standard library containers, which represent frequently used data structures, are implemented using templates, such that a doubly-linked list can be created for strings, integers, characters or any other (appropriate) data type using the same STL container management code.
Naturally, the compiler has to keep track of the specific data type that is being chosen once the template is instantiated, i.e. a list of strings is different from a list of integers once all the memory management code, constructors, destructors and overloaded comparison operators have been properly resolved. One can therefore use templates to create a new “templated floating-point data type” that keeps track of the physical dimensions, which are being resolved and checked at compile-time.
The need for fractional dimensions
This idea is not new and implementations have been around for quite some time. However, most simple implementations only keep track of integer exponents for the physical unit dimensions. At first, this seems to be more than enough, as something like the “square root of a meter” has no physical representation and rather appears to be a hint for an error in the computation. While this is true, it is nevertheless important to keep track of fractional dimensions as well. Think about the computation of the Euclidean distance for between two points in any dimension, which involves computing the square root of the sum of the squares. The square root operation essentially halves the physical dimension of the quantity, such that in general it gives a rational exponent. For example, if you take the square root of a length, the resulting quantity formally has the dimension of one half of length. While this is obviously an error, the usage of plain integer exponents would easily hide this obvious error due to rounding up (exponent 1, giving us a length again) or rounding down (exponent 0, representing a scalar).
For real-world computations and applications using physical quantities the inclusion of rational exponents therefore is a crucial requirement. Representing rational numbers using templates, on the other hand, is not a straightforward task. While one can easily break down a rational number into two integers—which are supported as build-in template parameters—one has to deal with the redundancy issue in the representation of rational numbers. The well-known Boost library actually makes the effort to provide such compile-time rational numbers and unit dimension checks in the boost::units
module1. It uses the static_rational
template meta-class, but this is a rather painful exercise considering the variety of compiler-specific code customization. In my opinion, the entire Boost unit system is rather complicated and heavily relies on the optimizer to remove all the intermediate overhead.
Therefore, I would like to present a much more convenient and lightweight approach based on two great features that were added in the C++11 update of the language: string literals, which are operators that kind of “extend” certain build-in data types and the std::ratio
template that provides compile-time rational numbers just in the right way. At the end of this document a complete copy&paste ready implementation2 of the material can be found. Feel free to skip the explanation if all you are looking for is a working piece of C++11 code (which requires a somewhat modern compiler to work).
Numerical units dimensions
Before we get started with the coding, let us do a brief review on the SI units and the other “dimensions” that we would like to keep tracked in our system for dimensional analysis.
SI units
After centuries of inconsistent definitions and a certain level of redundancy in the way we measure things, the international system of units (SI units) was defined in 1960. Essentially, all of physics can be boiled down to seven fundamental units of measure, whose precise base-line definition has only been updated a few times since the initial definition due to advances in the respective measurement techniques.
name | unit symbol | unit name | definition (in 2015, from Wikipedia) |
---|---|---|---|
length | m | metre | The distance travelled by light in vacuum in \(\frac{1}{299792458}\) second. |
mass | kg | kilogram | The mass of the international prototype kilogram. |
time | s | second | The duration of 9192631770 periods of the radiation corresponding to the transition between the two hyperfine levels of the ground state of the caesium-133 atom. |
electric current | A | ampere | The constant current which, if maintained in two straight parallel conductors of infinite length, of negligible circular cross-section, and placed 1 m apart in vacuum, would produce between these conductors a force equal to \(2\cdot 10^{−7}\) newtons per metre of length. |
temperature | K | kelvin | \(\frac{1}{273.16}\) of the thermodynamic temperature of the triple point of water. |
amount of substance | mol | mole | The amount of substance of a system which contains as many elementary entities as there are atoms in 0.012 kilogram of carbon-12. |
luminosity | cd | candela | The luminous intensity, in a given direction, of a source that emits monochromatic radiation of frequency \(540\cdot10^{12}\) hertz and that has a radiant intensity in that direction of \(\frac{1}{683}\) watt per steradian. |
All other physical units are essentially constructed from both positive and negative powers of those units, i.e. any physical unit can be expressed by \[ \mathrm{unit} = \mathrm{m}^a \cdot \mathrm{kg}^b \cdot \mathrm{s}^c \cdot \mathrm{A}^d \cdot \mathrm{K}^e \cdot \mathrm{mol}^f \cdot \mathrm{cd}^g \] where \(a,b,c,d,e,f,g\in\mathbb{Z}\) are seven signed integer exponents. In other words, if we want to introduce some sort of generic unit dimension check, we essentially just have to keep track of this vector containing the seven unit exponents. However, in many applications related to mechanics only the length, mass and time quantities are relevant, so we will restrict us to this simpler case. The system that we are going to develop can trivially be expanded to all of the seven units.
Angles
Aside from physical unit dimensions, it can be helpful to introduce a sort of artificial “physical” dimension that represents angles. This can help to avoid awkward mismatches between angles in different unit systems and highlight errors when applying trigonometric functions to non-angular quantities. Furthermore, there are two important unit systems:
- Radians are the natural, mathematical unit system to describe angles. It derives from the arc length that is being traversed on a unit circle (i.e. a circle of radius \(r=1\)), such that a full rotation angle is describes by \(2\pi\) Radians, i.e. the circumference of the unit circle. If the unit of Radians has to be specified explicitly, one often writes \(1.22\pi\,\mathrm{rad}\) for clarity.
- Degrees are a commonly used unit system, which divide a full circle into 360 equidistant segments. Conversely, a full turnaround is described by a \(360°\) rotation. The notation \(42\,\mathrm{deg}\) highlights the use of the Degree unit system explicitly.
Both Radians and Degrees are linear unit systems, such that both are related simply by a constant factor of \(\frac{2\pi}{360}\) or \(\frac{360}{2\pi}\), respectively. Usually angles are treated as dimensionless quantities, but in practice it can be very helpful to define Radians as the “angle quantity base unit” for the internal data handling.
Relevant C++11 language features
Classic C++ templates
Like mentioned in the problem description at the beginning, templates provide an abstraction level, that allows to reuse code portions without specifying certain variable and data types. The compiler in the end has to sort out the specific types of variable instances in order to identify the proper code implementation, which leads to explicit compile-time template type checks. This feature did stay in the shadows for quite some time during the early years of the C++ language, and received a lot of critique due to bad compiler and optimizer performance. Nowadays, C++ templates have become the corner-stone of modern high-performance and modular, reusable software implementations. They are extensively used in the C++ standard library.
C++ template parameters can either be type identifies or typed values, for example integer values. In order to introduce the underlying idea3 of our template-based dimensional analysis let us begin with a naive approach:
This essentially defines a templated “dummy class” around the double
floating-point data type, that serves as a container for the meta information contained in the LengthDim
, MassDim
and TimeDim
parameters. The key idea is to define overloaded operators for the arithmetic operations in order to restrict which template instances are allowed to interact. A crucial feature is the possibility to perform some arithmetic due to the integer type template parameters, which can be evaluated at compile-time. For example, the four basic arithmetic operators can be overloaded as follows:
There are countless implementations of this approach readily available, so just Google for it. This approach is just mentioned here to highlight the underlying idea. The key aspect of compile-time arithmetic is being shown in the overloaded multiplication and division operators, such that a dimension checked arithmetic for physical quantity dimensions can be defined.
But using awkward template definitions and numerical parameters is still a bit cumbersome to work with. However, after defining “natural” physical base quantity types via
the system is already ready for elementary dimension checking. Consider the following simple example:
It should be noted, that at this point only the physical dimensions are being tracked and checked. So far there is still no concept of measurement units for the physical quantities.
The C++11 std::ratio STL template
Basic arithmetic with integer exponents is all nice and shiny as long as one sticks to elementary mathematics. In real-world applications that go to a somewhat more sophisticated level, one quickly has to reach outside of the domain of integer exponents. As mentioned in the introduction, it is not enough to restrict to integer exponents, once square roots and similar operations come into play. The square root, for example, simply halves the dimension exponents, i.e.
\[
\sqrt{\mathrm{unit}} = \mathrm{m}^{\frac{a}{2}} \cdot \mathrm{kg}^{\frac{b}{2}} \cdot \mathrm{s}^{\frac{c}{2}} \cdot \mathrm{A}^{\frac{d}{2}} \cdot \mathrm{K}^{\frac{e}{2}} \cdot \mathrm{mol}^{\frac{f}{2}} \cdot \mathrm{cd}^{\frac{g}{2}} ,
\]
such that for a proper dimension analysis we need to be able to keep track of such fractions. One could envision a system, where each integer is replaced by two integers like Ln
and Ld
for the numerator and denominator of the dimension, representing the quotient \(\frac{Ln}{Ld}\). However, this simple approach does not take into account the representation redundancy \(\frac{Ln\cdot k}{Ld \cdot k} = \frac{Ln}{Ld}\) for any \(k\in\mathbb{Z}\) that naturally occurs in rational numbers, i.e. common multiples of the numerator and denominator.
The problem of computing the greatest common divisor in order to determine the unique reduced representation of the rational number boils down to an application of the Euclidean algorithm, which recursively computes the remainder of mutual divisions of the two numbers. It can be described by
\[
\mathrm{gcd}(a,0) = a, \qquad
\mathrm{gcd}(a,b) = \mathrm{gcd}(b, a\bmod b).
\]
In order to facilitate this computation at compile-time, this recursion has to be represented by an infinitely nested C++ template construction, which is partially overwritten by the terminating special case that represents \(\mathrm{gcd}(a,0)=a\). For educational purposes, let us look at the relevant code portion from the Boost library, which is located in the common_factor_ct.hpp
file:
There are actually several implementations in the common_factor_ct.hpp
header file to define the nested C++ template in a standard-compliant fashion to perform the Euclidean algorithm. The big issue of this approach is that it heavily relies on the compiler to sort out all the meta-definitions and reduce this to a single number. Due to the fact that the computation is facilitated by the recursive specialization of a temporary struct
, a bad optimizer may actually create the nested temporary struct
to perform the computation, which leads to a huge performance degradation. Once the greatest common divisor is computed, one can easily define a template that represents a rational number in a reduced form at compile-time, which brings us back to the dimensional analysis with fractional exponents.
Fortunately, the C++11 update added such a compile-time rational number template to the standard library, where it is found in the std::ratio
namespace. This is beneficial from two points of view: First, it reduces a lot of compiler-specific code to implement the template nesting, and second the chances of an efficient optimization of an STL template by the compiler are better compared to an user-defined structure such as this—after all, the STL implementation is provided by the same people that develop the compiler. Along with the std::ratio< numerator, denominator >
template to represent a rational number \(q=\frac{a}{b}\in\mathbb{Q}\), several templates “functions” like ratio_add
, ratio_subtract
, ratio_multiply
and ratio_divide
, as well as comparison template “operators” are defined to provide basic rational number arithmetic. Let us take a look at a simple example:
The usage of typedef
instead of a variable type looks a bit awkward at first, but in the end, this is rather easy to use. Coming back to the original issue of dimensional analysis, one can now refine the implementation by using std::ratio
instead of int
template parameters.
Using this refined quantity class with rational exponents, RQuantity
allows to properly define mathematical operations that go beyond the elementary arithmetic. Consider for example the implementation of the dimension-controlled square root function:
Unfortunately, carrying out the proper dimension arithmetic clutters the C++ code somewhat. However, using this square root Qsqrt
, the computation of an Euclidean norm can now be carried out as follows:
This approach can naturally be expanded to all other kinds of roots and other mathematical operations that produce rational dimensional exponents.
C++11 string literals
At this point we can use physical quantity types and write compile-time dimension checked code allowing for rational exponents. However, there is still the issue of converting between different measurement units for the same type of physical quantity. For example, there are various units of length, all of which are conceptually equivalent and are mutually related by constant factors. While it is certainly a good approach to use the SI units as base units, it would nevertheless be helpful to have a simple method to instantiate the correct quantity type and perform the unit conversion automatically.
Along comes the string literal feature, that has been introduced with the C++11 standard. It allows to define new suffixes for certain elementary data types by overloading the new string literal operator, i.e. operator""
. For example, while 3.4
will imply a double
variable, the suffix in 3.4f
creates a float
data type. Overloading the string literal operator for the appropriate parameter data type allows to custom define such suffixes. The two data types of interest here are long double
and unsigned long long int
, both of which are the “largest” variant of the respective type. The generic structure is as follows:
Given the RQuantity
template class for dimension checking and the derived quantity types like Length
or Mass
, this allows to define simple string literal operators for the common units of measure. The conversion into the chosen standard measurement unit is being performed as well:
This allows to perform direct assignments like Length someLength = 12.43_km;
or Mass someMass = 7_kg + 0.23_kg;
, where the entire template type instantiation and unit conversion is being carried out by the string literal operator. Note that it is necessary to overload the string literal operator both for the unsigned long long int
and long double
data type, if one wants to be able to write something like 7_kg + 0.23_kg
, i.e. automatically convert an integer as well. This makes the usage of dimensional analysis like discussed before particularly convenient.
The constexpr keyword
Another ingredient is pretty helpful in this context: the constexpr
keyword, which was introduced in the C++11 language update as well. According to the reference documentation, the constexpr
specifier declares that it is possible to compute the value of the function or variable at compile-time. This implies, that variables and functions declared in this fashion can be used in places where only compile-time constant expressions are allowed—at least, as long as appropriate constant function arguments are used. Essentially, it states that the value of a variable or function can be used in constant expressions.
Using the constexpr
keyword both for the RQuantity
template arithmetic operators and the unit string literals encourages the compiler to perform the computations involving constants at compile-time wherever appropriate. Furthermore, it allows the assignment of quantities with a dimension as constant initializers using the string literals, e.g. const Length myLen = 7.2_mi - 9.8_km;
.
Important: In the C++11 documentation, it is mentioned that the constexpr
specifier used in an object declaration also implies the const
specifier. However, this has been revoked in the C++14 update to the language standard. There are other, subtle changes between the C++11 and C++14 definition of this keyword, such that I would recommend reading the constexpr
reference documentation and this insightful blog post on the issue.
Complete implementation
For your convenience, a complete implementation of all the mentioned ideas is provided below,2 which has been tested in Microsoft Visual Studio 2015. It provides a dimension analysis with rational exponents for the three common mechanical quantities mass, length and time as well as angles. Various string literals for common physical quantities are defined as well. A double
variable is automatically converted to an RQuantity
. Furthermore, the ConvertTo
macro uses the string literals—using the macro string concatenation operator ##
and the constant value 1.0—to allow for a convenient unit conversion, e.g. ConvertTo(4.22_mi, km)
or ConvertTo(3_lb, kg)
.
-
A nice introduction and overview to the
boost::units
module can be found on CodeProject. ↩ -
The code is based on some good practices and ideas found in an Code Project article on C++11 string literals by Mikhail Semenov. ↩ ↩2
-
The original idea, using integer exponents, goes back to an early application example “Dimensional Analysis in C++” on template meta programming by Scott Meyers, in which he refers to this as Barton’s and Nackman’s approach to dimensional analysis. ↩