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:

template<int MassDim, int LengthDim, int TimeDim> 
class Quantity 
{
    Quantity(double val) : value(val) {};      // constructor
    double value;                              // the actual value
    // further stuff, we just consider the basics here...
};   

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:

// Addition operator
template <int M, int L, int T>
Quantity<M, L, T> operator+(const Quantity<M, L, T>& lhs, const Quantity<M, L, T>& rhs)
{
    return Quantity<M, L, T>(lhs.value + rhs.value);
}

// Subtraction operator
template <int M, int L, int T>
Quantity<M, L, T> operator-(const Quantity<M, L, T>& lhs, const Quantity<M, L, T>& rhs)
{
    return Quantity<M, L, T>(lhs.value - rhs.value);
}

// Multiplication operator
template <int M1, int L1, int T1, int M2, int L2, int T2>
Quantity<M1+M2, L1+L2, T1+T2> operator*(const Quantity<M1, L1, T1>& lhs, const Quantity<M2, L2, T2>& rhs)
{
    return Quantity<M1+M2, L1+L2, T1+T2>(lhs.value * rhs.value);
}

// Division operator
template <int M1, int L1, int T1, int M2, int L2, int T2>
Quantity<M1-M2, L1-L2, T1-T2> operator/(const Quantity<M1, L1, T1>& lhs, const Quantity<M2, L2, T2>& rhs)
{
    return Quantity<M1-M2, L1-L2, T1-T2>(lhs.value / rhs.value);
}

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

typedef Quantity<1,0,0> Mass;
typedef Quantity<0,1,0> Length;
typedef Quantity<0,0,1> Time;
typedef Quantity<0,1,-1> Velocity;

the system is already ready for elementary dimension checking. Consider the following simple example:

Mass someMass(4.2);
Length someLength(2.3);
Time someTime(3.4);
Velocity badDimensions = someMass - someLength;     // yields a compile-time error
Velocity someSpeed = someLength / someTime;
Length distanceTraveled = someSpeed * Time(8.7);

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:

// Build GCD with Euclid's recursive algorithm
template < unsigned long Value1, unsigned long Value2 >
struct static_gcd_helper_t
{
private:
    BOOST_STATIC_CONSTANT( unsigned long, new_value1 = Value2 );
    BOOST_STATIC_CONSTANT( unsigned long, new_value2 = Value1 % Value2 );

    #ifndef __BORLANDC__
    #define BOOST_DETAIL_GCD_HELPER_VAL(Value) static_cast<unsigned long>(Value)
    #else
    typedef static_gcd_helper_t  self_type;
    #define BOOST_DETAIL_GCD_HELPER_VAL(Value)  (self_type:: Value )
    #endif

    typedef static_gcd_helper_t< BOOST_DETAIL_GCD_HELPER_VAL(new_value1),
     BOOST_DETAIL_GCD_HELPER_VAL(new_value2) >  next_step_type;

    #undef BOOST_DETAIL_GCD_HELPER_VAL

public:
    BOOST_STATIC_CONSTANT( unsigned long, value = next_step_type::value );
};

// Non-recursive case
template < unsigned long Value1 >
struct static_gcd_helper_t< Value1, 0UL >
{
    BOOST_STATIC_CONSTANT( unsigned long, value = Value1 );
};

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:

typedef std::ratio<1,3> one_third;          // remember: those are NOT variables
typedef std::ratio<2,4> two_fourths;

std::cout << "one_third= " << one_third::num << "/" << one_third::den << std::endl;
std::cout << "two_fourths= " << two_fourths::num << "/" << two_fourths::den << std::endl;

typedef std::ratio_add<one_third,two_fourths> sum;      // addition of rational numbers

std::cout << "sum= " << sum::num << "/" << sum::den;
std::cout << " (which is: " << ( double(sum::num) / sum::den ) << ")" << std::endl;

std::cout << "1 kilogram has " << ( std::kilo::num / std::kilo::den ) << " grams";
std::cout << std::endl;

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.

template<typename MassDim, typename LengthDim, typename TimeDim> 
class RQuantity 
{
    RQuantity(double val) : value(val) {};       
    double value;     
    // further stuff, we still just consider the basics here...
};   

// Addition operator
template <typename M, typename L, typename T>
RQuantity<M, L, T> operator+(const RQuantity<M, L, T>& lhs, const RQuantity<M, L, T>& rhs)
{
    return RQuantity<M, L, T>(lhs.value + rhs.value);
}

// Multiplication operator
template <typename M1, typename L1, typename T1, typename M2, typename L2, typename T2>
RQuantity<std::ratio_add<M1, M2>, std::ratio_add<L1, L2>, std::ratio_add<T1, T2>> 
    operator*(const RQuantity<M1, L1, T1>& lhs, const RQuantity<M2, L2, T2>& rhs)
{
    return RQuantity<std::ratio_add<M1, M2>, std::ratio_add<L1, L2>, std::ratio_add<T1, T2>>
                    (lhs.value * rhs.value);
}

typedef RQuantity<std::ratio<1>, std::ratio<0>, std::ratio<0>> Mass;
typedef RQuantity<std::ratio<0>, std::ratio<1>, std::ratio<0>> Length;
typedef RQuantity<std::ratio<0>, std::ratio<0>, std::ratio<1>> Time;
typedef RQuantity<std::ratio<0>, std::ratio<1>, std::ratio<-1>> Velocity;

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:

template <typename M, typename L, typename T>
RQuantity<std::ratio_divide<M, std::ratio<2>>, 
          std::ratio_divide<L, std::ratio<2>>, 
          std::ratio_divide<T, std::ratio<2>>> 
    Qsqrt(const RQuantity<M, L, T>& num)
{
    return gncRQuantity<std::ratio_divide<M, std::ratio<2>>, 
                        std::ratio_divide<L, std::ratio<2>>, 
                        std::ratio_divide<T, std::ratio<2>>>( sqrt(num.value) );
}

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:

Length x(2.3), y(3.2), z(8.2);

// valid and compile-time dimension checked
Length diagonal = Qsqrt(x*x + y*y + z*z);

// gives a compile-time error
Length dimMismatch = Qsqrt(x + y + z);

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:

return_type operator"" literal_suffix(long double parameter)
{ 
    // Some computation or else 
    return some_value_of_return_type; 
};

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:

Mass operator"" _kg(long double x) { return Mass(static_cast<double>(x)); }
Mass operator"" _kg(unsigned long long int x) { return Mass(static_cast<double>(x)); }

Length operator"" _m(long double x) { return Length(static_cast<double>(x)); }
Length operator"" _mi(long double x) { return Length(static_cast<double>(x) * 1609.344); }

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).

// "RQuantity.h" header file

// The "RQuantity" class is the prototype template container class, that just holds a double value. The
// class SHOULD NOT BE INSTANTIATED directly by itself, rather use the quantity types defined below.
template<typename MassDim, typename LengthDim, typename TimeDim, typename AngleDim>
class RQuantity
{
private:
    double value;

public:
    constexpr RQuantity() : value(0.0) {}
    constexpr RQuantity(double val) : value(val) {}
    constexpr RQuantity(long double val) : value(static_cast<double>(val)) {}

    // The intrinsic operations for a quantity with a unit is addition and subtraction
    constexpr RQuantity const& operator+=(const RQuantity& rhs)
    {
        value += rhs.value;
        return *this;
    }
    constexpr RQuantity const& operator-=(const RQuantity& rhs)
    {
        value -= rhs.value;
        return *this;
    }

    // Returns the value of the quantity in multiples of the specified unit
    constexpr double Convert(const RQuantity& rhs) const
    {
        return value / rhs.value;
    }

    // returns the raw value of the quantity (should not be used)
    constexpr double getValue() const
    {
        return value;
    }
};


// Predefined (physical unit) quantity types:
// ------------------------------------------
#define QUANTITY_TYPE(_Mdim, _Ldim, _Tdim, _Adim, name) \
    typedef RQuantity<std::ratio<_Mdim>, std::ratio<_Ldim>, std::ratio<_Tdim>, std::ratio<_Adim>> name;

// Replacement of "double" type
QUANTITY_TYPE(0, 0, 0, 0, Number);

// Physical quantity types
QUANTITY_TYPE(1, 0, 0, 0, QMass);
QUANTITY_TYPE(0, 1, 0, 0, QLength);
QUANTITY_TYPE(0, 2, 0, 0, QArea);
QUANTITY_TYPE(0, 3, 0, 0, QVolume);
QUANTITY_TYPE(0, 0, 1, 0, QTime);
QUANTITY_TYPE(0, 1, -1, 0, QSpeed);
QUANTITY_TYPE(0, 1, -2, 0, QAcceleration);
QUANTITY_TYPE(0, 1, -3, 0, QJerk);
QUANTITY_TYPE(0, 0, -1, 0, QFrequency);
QUANTITY_TYPE(1, 1, -2, 0, QForce);
QUANTITY_TYPE(1, -1, -2, 0, QPressure);

// Angle type:
QUANTITY_TYPE(0, 0, 0, 1, Angle);


// Standard arithmetic operators:
// ------------------------------
template <typename M, typename L, typename T, typename A>
constexpr RQuantity<M, L, T, A> 
    operator+(const RQuantity<M, L, T, A>& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return RQuantity<M, L, T, A>(lhs.getValue() + rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr RQuantity<M, L, T, A> 
    operator-(const RQuantity<M, L, T, A>& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return RQuantity<M, L, T, A>(lhs.getValue() - rhs.getValue());
}
template <typename M1, typename L1, typename T1, typename A1, 
          typename M2, typename L2, typename T2, typename A2>
constexpr RQuantity<std::ratio_add<M1, M2>, std::ratio_add<L1, L2>, 
                    std::ratio_add<T1, T2>, std::ratio_add<A1, A2>> 
    operator*(const RQuantity<M1, L1, T1, A1>& lhs, const RQuantity<M2, L2, T2, A2>& rhs)
{
    return RQuantity<std::ratio_add<M1, M2>, std::ratio_add<L1, L2>, 
                     std::ratio_add<T1, T2>, std::ratio_add<A1, A2>>
                    (lhs.getValue()*rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr RQuantity<M, L, T, A> 
    operator*(const double& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return RQuantity<M, L, T, A>(lhs*rhs.getValue());
}
template <typename M1, typename L1, typename T1, typename A1, 
          typename M2, typename L2, typename T2, typename A2>
constexpr RQuantity<std::ratio_subtract<M1, M2>, std::ratio_subtract<L1, L2>,
                    std::ratio_subtract<T1, T2>, std::ratio_subtract<A1, A2>> 
    operator/(const RQuantity<M1, L1, T1, A1>& lhs, const RQuantity<M2, L2, T2, A2>& rhs)
{
    return RQuantity<std::ratio_subtract<M1, M2>, std::ratio_subtract<L1, L2>, 
                     std::ratio_subtract<T1, T2>, std::ratio_subtract<A1, A2>>
                    (lhs.getValue() / rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr RQuantity<std::ratio_subtract<std::ratio<0>, M>, std::ratio_subtract<std::ratio<0>, L>,
                    std::ratio_subtract<std::ratio<0>, T>, std::ratio_subtract<std::ratio<0>, A>> 
    operator/(double x, const RQuantity<M, L, T, A>& rhs)
{
    return RQuantity<std::ratio_subtract<std::ratio<0>, M>, std::ratio_subtract<std::ratio<0>, L>, 
                     std::ratio_subtract<std::ratio<0>, T>, std::ratio_subtract<std::ratio<0>, A>>
                    (x / rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr RQuantity<M, L, T, A> 
    operator/(const RQuantity<M, L, T, A>& rhs, double x)
{
    return RQuantity<M, L, T, A>(rhs.getValue() / x);
}


// Comparison operators for quantities:
// ------------------------------------
template <typename M, typename L, typename T, typename A>
constexpr bool operator==(const RQuantity<M, L, T, A>& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return (lhs.getValue() == rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr bool operator!=(const RQuantity<M, L, T, A>& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return (lhs.getValue() != rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr bool operator<=(const RQuantity<M, L, T, A>& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return (lhs.getValue() <= rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr bool operator>=(const RQuantity<M, L, T, A>& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return (lhs.getValue() >= rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr bool operator< (const RQuantity<M, L, T, A>& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return (lhs.getValue()<rhs.getValue());
}
template <typename M, typename L, typename T, typename A>
constexpr bool operator> (const RQuantity<M, L, T, A>& lhs, const RQuantity<M, L, T, A>& rhs)
{
    return (lhs.getValue()>rhs.getValue());
}


// Predefined units:
// -----------------

// Predefined mass units:
constexpr QMass kg(1.0);                            // SI base unit
constexpr QMass gramme = 0.001 * kg;
constexpr QMass tonne = 1000 * kg;
constexpr QMass ounce = 0.028349523125 * kg;
constexpr QMass pound = 16 * ounce;
constexpr QMass stone = 14 * pound;

// Predefined length-derived units
constexpr QLength metre(1.0);                   // SI base unit
constexpr QLength decimetre = metre / 10;
constexpr QLength centimetre = metre / 100;
constexpr QLength millimetre = metre / 1000;
constexpr QLength kilometre = 1000 * metre;
constexpr QLength inch = 2.54 * centimetre;
constexpr QLength foot = 12 * inch;
constexpr QLength yard = 3 * foot;
constexpr QLength mile = 5280 * foot;

constexpr QArea kilometre2 = kilometre*kilometre;
constexpr QArea metre2 = metre*metre;
constexpr QArea decimetre2 = decimetre*decimetre;
constexpr QArea centimetre2 = centimetre*centimetre;
constexpr QArea millimetre2 = millimetre * millimetre;
constexpr QArea inch2 = inch*inch;
constexpr QArea foot2 = foot*foot;
constexpr QArea mile2 = mile*mile;

constexpr QVolume kilometre3 = kilometre2*kilometre;
constexpr QVolume metre3 = metre2*metre;
constexpr QVolume decimetre3 = decimetre2*decimetre;
constexpr QVolume litre = decimetre3;
constexpr QVolume centimetre3 = centimetre2*centimetre;
constexpr QVolume millimetre3 = millimetre2 * millimetre;
constexpr QVolume inch3 = inch2*inch;
constexpr QVolume foot3 = foot2*foot;
constexpr QVolume mile3 = mile2*mile;

// Predefined time-derived units:
constexpr QTime second(1.0);                        // SI base unit
constexpr QTime minute = 60 * second;
constexpr QTime hour = 60 * minute;
constexpr QTime day = 24 * hour;

constexpr QFrequency Hz(1.0);

// Predefined mixed units:
constexpr QAcceleration G = 9.80665 *  metre / (second*second);

constexpr QForce newton(1.0);
constexpr QForce poundforce = pound*G;
constexpr QForce kilopond = kg*G;

constexpr QPressure Pascal(1.0);
constexpr QPressure bar = 100000 * Pascal;
constexpr QPressure psi = pound*G / inch2;


// Physical unit literals:
// -----------------------

// literals for length units
constexpr QLength operator"" _mm(long double x) { return static_cast<double>(x)*millimetre; }
constexpr QLength operator"" _cm(long double x) { return static_cast<double>(x)*centimetre; }
constexpr QLength operator"" _m(long double x) { return static_cast<double>(x)*metre; }
constexpr QLength operator"" _km(long double x) { return static_cast<double>(x)*kilometre; }
constexpr QLength operator"" _mi(long double x) { return static_cast<double>(x)*mile; }
constexpr QLength operator"" _yd(long double x) { return static_cast<double>(x)*yard; }
constexpr QLength operator"" _ft(long double x) { return static_cast<double>(x)*foot; }
constexpr QLength operator"" _in(long double x) { return static_cast<double>(x)*inch; }
constexpr QLength operator"" _mm(unsigned long long int x) { return static_cast<double>(x)*millimetre; }
constexpr QLength operator"" _cm(unsigned long long int  x) { return static_cast<double>(x)*centimetre; }
constexpr QLength operator"" _m(unsigned long long int  x) { return static_cast<double>(x)*metre; }
constexpr QLength operator"" _km(unsigned long long int  x) { return static_cast<double>(x)*kilometre; }
constexpr QLength operator"" _mi(unsigned long long int  x) { return static_cast<double>(x)*mile; }
constexpr QLength operator"" _yd(unsigned long long int  x) { return static_cast<double>(x)*yard; }
constexpr QLength operator"" _ft(unsigned long long int  x) { return static_cast<double>(x)*foot; }
constexpr QLength operator"" _in(unsigned long long int  x) { return static_cast<double>(x)*inch; }

// literals for speed units
constexpr QSpeed operator"" _mps(long double x) { return QSpeed(x); };
constexpr QSpeed operator"" _miph(long double x) { return static_cast<double>(x)*mile / hour; };
constexpr QSpeed operator"" _kmph(long double x) { return static_cast<double>(x)*kilometre / hour; };
constexpr QSpeed operator"" _mps(unsigned long long int x) 
                                { return QSpeed(static_cast<long double>(x)); };
constexpr QSpeed operator"" _miph(unsigned long long int x) 
                                 { return static_cast<double>(x)*mile / hour; };
constexpr QSpeed operator"" _kmph(unsigned long long int x) 
                                 { return static_cast<double>(x)*kilometre / hour; };

// literal for frequency unit
constexpr QFrequency operator"" _Hz(long double x) { return QFrequency(x); };
constexpr QFrequency operator"" _Hz(unsigned long long int x) 
                                   { return QFrequency(static_cast<long double>(x)); };

// literals for time units
constexpr QTime operator"" _s(long double x) { return QTime(x); };
constexpr QTime operator"" _min(long double x) { return static_cast<double>(x)*minute; };
constexpr QTime operator"" _h(long double x) { return static_cast<double>(x)*hour; };
constexpr QTime operator"" _day(long double x) { return static_cast<double>(x)*day; };
constexpr QTime operator"" _s(unsigned long long int x) { return QTime(static_cast<double>(x)); };
constexpr QTime operator"" _min(unsigned long long int x) { return static_cast<double>(x)*minute; };
constexpr QTime operator"" _h(unsigned long long int x) { return static_cast<double>(x)*hour; };
constexpr QTime operator"" _day(unsigned long long int x) { return static_cast<double>(x)*day; };

// literals for mass units
constexpr QMass operator"" _kg(long double x) { return QMass(x); };
constexpr QMass operator"" _g(long double x) { return static_cast<double>(x)*gramme; };
constexpr QMass operator"" _t(long double x) { return static_cast<double>(x)*tonne; };
constexpr QMass operator"" _oz(long double x) { return static_cast<double>(x)*ounce; };
constexpr QMass operator"" _lb(long double x) { return static_cast<double>(x)*pound; };
constexpr QMass operator"" _st(long double x) { return static_cast<double>(x)*stone; };
constexpr QMass operator"" _kg(unsigned long long int x) { return QMass(static_cast<double>(x)); };
constexpr QMass operator"" _g(unsigned long long int x) { return static_cast<double>(x)*gramme; };
constexpr QMass operator"" _t(unsigned long long int x) { return static_cast<double>(x)*tonne; };
constexpr QMass operator"" _oz(unsigned long long int x) { return static_cast<double>(x)*ounce; };
constexpr QMass operator"" _lb(unsigned long long int x) { return static_cast<double>(x)*pound; };
constexpr QMass operator"" _st(unsigned long long int x) { return static_cast<double>(x)*stone; };

// literals for acceleration units
constexpr QAcceleration operator"" _mps2(long double x) { return QAcceleration(x); };
constexpr QAcceleration operator"" _mps2(unsigned long long int x) 
                                        { return QAcceleration(static_cast<double>(x)); };
constexpr QAcceleration operator"" _G(long double x) { return static_cast<double>(x)*G; };
constexpr QAcceleration operator"" _G(unsigned long long int x) { return static_cast<double>(x)*G; }

// literals for force units
constexpr QForce operator"" _N(long double x) { return QForce(x); };
constexpr QForce operator"" _N(unsigned long long int x) { return QForce(static_cast<double>(x)); };
constexpr QForce operator"" _lbf(long double x) { return static_cast<double>(x)*poundforce; };
constexpr QForce operator"" _lbf(unsigned long long int x) { return static_cast<double>(x)*poundforce; };
constexpr QForce operator"" _kp(long double x) { return static_cast<double>(x)*kilopond; };
constexpr QForce operator"" _kp(unsigned long long int x) { return static_cast<double>(x)*kilopond; };

// literals for pressure units
constexpr QPressure operator"" _Pa(long double x) { return QPressure(x); };
constexpr QPressure operator"" _Pa(unsigned long long int x) 
                                  { return QPressure(static_cast<double>(x)); };
constexpr QPressure operator"" _bar(long double x) { return static_cast<double>(x)*bar; };
constexpr QPressure operator"" _bar(unsigned long long int x) { return static_cast<double>(x)*bar; };
constexpr QPressure operator"" _psi(long double x) { return static_cast<double>(x)*psi; };
constexpr QPressure operator"" _psi(unsigned long long int x) { return static_cast<double>(x)*psi; };


// Angular unit literals:
// ----------------------
constexpr long double operator"" _pi(long double x) 
    { return static_cast<double>(x) * 3.1415926535897932384626433832795; }
constexpr long double operator"" _pi(unsigned long long int x) 
    { return static_cast<double>(x) * 3.1415926535897932384626433832795; }

// Predefined angle units:
constexpr Angle radian(1.0);
constexpr Angle degree = static_cast<double>(2_pi / 360.0) * radian;

// literals for angle units
constexpr Angle operator"" _rad(long double x) { return Angle(x); };
constexpr Angle operator"" _rad(unsigned long long int x) { return Angle(static_cast<double>(x)); };
constexpr Angle operator"" _deg(long double x) { return static_cast<double>(x)*degree; };
constexpr Angle operator"" _deg(unsigned long long int x) { return static_cast<double>(x)*degree; };

// Conversion macro, which utilizes the string literals
#define ConvertTo(_x, _y) (_x).Convert(1.0_##_y)



// Typesafe mathematical operations:
// ---------------------------------
template <typename M, typename L, typename T, typename A>
constexpr RQuantity<std::ratio_divide<M, std::ratio<2>>, std::ratio_divide<L, std::ratio<2>>, 
                    std::ratio_divide<T, std::ratio<2>>, std::ratio_divide<A, std::ratio<2>>> 
    Qsqrt(const RQuantity<M, L, T, A>& num)
{
    return RQuantity<std::ratio_divide<M, std::ratio<2>>, std::ratio_divide<L, std::ratio<2>>, 
                     std::ratio_divide<T, std::ratio<2>>, std::ratio_divide<A, std::ratio<2>>>
                    (sqrt(num.getValue()));
}

// Typesafe trigonometric operations
inline double sin(const Angle &num)
{
    return sin(num.getValue());
}
inline double cos(const Angle &num)
{
    return cos(num.getValue());
}
inline double tan(const Angle &num)
{
    return tan(num.getValue());
}
  1. A nice introduction and overview to the boost::units module can be found on CodeProject

  2. 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

  3. 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. 

Updated: