Examples

Compile-time unit inference

See xo-unit/examples/ex1 for code below.

Units propagate through familiar arithmetic expressions:

 1 #include "xo/unit/quantity.hpp"
 2 #include "xo/unit/quantity_iostream.hpp"
 3 #include <iostream>
 4
 5 int
 6 main () {
 7     namespace q = xo::qty::qty;
 8     namespace su = xo::qty::su;
 9     using namespace std;
10
11     constexpr auto t = q::minutes(2);
12     constexpr auto d = q::kilometers(2.5);
13
14     constexpr auto t2 = t*t;
15     constexpr auto a = d / (t*t);
16
17     cerr << "t: " << t << ", d: " << d
18          << ", t^2: " << t2
19          << ", d.t^-2: " << a
20          << endl;
21 }

with output:

t: 2min, d: 2.5km, t^2: 4min^2, d.t^-2: 0.625km.min^-2

We can use static asserts to prove that units are being computed at compile-time

 1 static_assert(std::same_as<decltype(t)::repr_type, int>);
 2 static_assert(sizeof(t) == sizeof(double));
 3 static_assert(t.scale() == 2);
 4 static_assert(t.abbrev() == flatstring("min"));
 5
 6 static_assert(std::same_as<decltype(d)::repr_type, double>);
 7 static_assert(sizeof(d) == sizeof(double));
 8 static_assert(d.scale() == 2.5);
 9 static_assert(d.abbrev() == flatstring("km"));
10
11 static_assert(std::same_as<decltype(t2)::repr_type, int>);
12 static_assert(sizeof(t2) == sizeof(double));
13 static_assert(t2.scale() == 4);
14 static_assert(t2.abbrev() == flatstring("min^2"));
15
16 static_assert(std::same_as<decltype(a)::repr_type, double>);
17 static_assert(sizeof(a) == sizeof(double));
18 static_assert(a.scale() == 0.625);
19 static_assert(a.abbrev() == flatstring("km.min^-2"));

Remarks:

  • xo::qty::quantity performs unit consistency checking and propagation at compile time. Runtime space/time overhead is zero.

  • Units are sticky: since we expressed t in minutes, (t*t) and d/(t*t) also use minutes.

  • Unit ordering is sticky. Distance appears on the left of time in printed value of d/(t*t) because it was on the left-hand side of operator/

  • xo-unit copies representation from the argument to factory functions q::minutes, q::kilometers etc.

  • Binary operators take representation from the ‘most precise’ argument, as prescribed by std::common_type_t.

  • Unit abbreviations (such as kg.min^-2 above) are computed at compile time. See xo::flatstring for constexpr string implementation.

  • See xo::xquantity for parallel implementation that defers unit checking until runtime.

Explicit scale conversion

Can convert between compatible units explictly, using:

  1. xo::qty::with_units (template function)

  2. quantity.rescale_ext (template method)

  3. xo::qty::with_units_from (template function)

See xo-unit/examples/ex2 for code below.

 1 #include "xo/unit/quantity.hpp"
 2 #include "xo/unit/quantity_iostream.hpp"
 3 #include <iostream>
 4
 5 int
 6 main () {
 7     namespace q = xo::qty::qty;
 8     namespace u = xo::qty::u;
 9     using xo::qty::with_units_from;
10     using xo::qty::with_units;
11     using xo::qty::quantity;
12     using xo::flatstring;
13     using namespace std;
14
15     constexpr auto t = q::minutes(2);
16     constexpr auto d = q::kilometers(2.5);
17
18     constexpr auto t2 = t*t;
19     constexpr auto a = d / (t*t);
20
21     cerr << "t: " << t << ", d: " << d
22          << ", t^2: " << t2
23          << ", d.t^-2: " << a
24          << endl;
25
26     constexpr auto a2 = with_units<u::meter / (u::second * u::second)>(a);
27
28     static_assert(a2.abbrev() == flatstring("m.s^-2"));
29
30     cerr << "a2: " << a2 << endl;
31
32     constexpr auto a3 = a.rescale_ext<u::meter / (u::second * u::second)>();
33
34     static_assert(a3.abbrev() == flatstring("m.s^-2"));
35
36     cerr << "a3: " << a3 << endl;
37
38     constexpr auto au = q::meter / (q::second * q::second);
39     constexpr auto a4 = with_units_from(a, au);
40
41     static_assert(a4.abbrev() == flatstring("m.s^-2"));
42
43     cerr << "a4: " << a4 << endl;
44 }

with output:

a2: 0.173611m.s^-2
a3: 0.173611m.s^-2
a4: 0.173611m.s^-2

Implicit Scale conversion

Another way to convert units is to assign to a variable with desired units – this works because the units are encoded as part of the assigned variable’s type.

See xo-unit/example/ex3 for code below

 1 int
 2 main () {
 3     namespace q = xo::qty::qty;
 4     namespace u = xo::qty::u;
 5     using xo::qty::quantity;
 6
 7     constexpr quantity<u::second> t = q::minutes(2);
 8     constexpr quantity<u::meter> d = q::kilometers(2.5);
 9
10     constexpr auto t2 = t*t;
11     constexpr auto a = d / (t*t);
12
13     std::cerr << "t: " << t << ", d: " << d
14               << ", d.t^-2: " << a
15               << std::endl;
16 }

with output:

t: 120s, d: 2500m, d.t^-2: 0.17e611m.s^-2

Remarks:

  • Assignment to t converted to representation double. We could have instead used quantity<u::second, int> to propagate right-hand-side representation

Scale conversion and arithmetic

When representing a particular quantity, xo-unit uses at most one scale for each basis dimension associated with the unit for that quantity. When an arithmetic operator encounters basis units involving two different scales, the operator will adopt the scale provided by the left-hand argument:

See xo-unit/example/ex4 for code below

 1 #include "xo/unit/quantity.hpp"
 2 #include <iostream>
 3
 4 int main() {
 5     namespace q = xo::qty::qty;
 6
 7     auto t1 = qty::milliseconds(1);
 8     auto t2 = qty::minutes(1);
 9     auto p = t1 * t2;
10
11     std::cerr << "t1: " << t1 << ", t2: " << t2 << ", p: " << p << std::endl;
12 }

with output:

t1: 1ms, t2: 1min, t1*t2: 60000ms^2

Dimensionless quantities unwrap implicitly

Conversely, compiler rejects attempt to implictly unwrap a dimensioned quantity.

See xo-unit/examples/ex4 for code below.

 1 #include "xo/unit/quantity.hpp"
 2 #include "xo/unit/quantity_iostream.hpp"
 3 #include <iostream>
 4
 5 int
 6 main () {
 7     namespace q = xo::qty::qty;
 8
 9     auto t1 = q::milliseconds(1);
10     auto t2 = q::minutes(1);
11
12     auto r1 = t1 / with_repr<double>(t2);
13
14     static_assert(r1.is_dimensionless());
15     static_assert(!t2.is_dimensionless());
16
17     static_assert(std::same_as<static_cast<double>(r1), double>);
18
19     // r1_value: assignment compiles,  since r1 dimensionless
20     double r1_value = r1;
21
22     // r2_value: bad assignment won't compile,  'cannot convert' error
23     //double r2_value = t2;
24
25     std::cerr << "t1: " << t1 << ", t2: " << t2 << ", t1/t2: " << r1_value << std::endl;
26 }

with output:

t1: 1ms, t2: 1min, t1/t2: 1.66667e-05

Fractional dimension

Fractional dimensions have limited support. Prior to c++26 we can only support fractional dimensions with denominator 2, such as powers -3/2, -1/2, +1/2, +3/2 etc.

c++26 will enable support for support fractional dimensions involving other ratios, by offering constexpr ::pow()

See xo-unit/examples/ex6 for code below

 1 #include "xo/unit/quantity.hpp"
 2 #include "xo/unit/quantity_iostream.hpp"
 3 #include <iostream>
 4
 5 int
 6 main () {
 7     namespace u = xo::unit::units;
 8     namespace q = xo::unit::qty;
 9     using namespace std;
10
11     /* 20% volatility over 250 days (approx number of trading days in one year) */
12     auto q1 = q::volatility_250d(0.2);
13     /* 10% volatility over 30 days */
14     auto q2 = q::volatility_30d(0.1);
15
16     auto sum = q1 + q2;
17     auto prod = q1 * q2;
18
19     static_assert(sum.abbrev() == flatstring("yr360^(-1/2)"));
20     static_assert(prod.abbrev() == flatstring("yr360^-1"));
21
22     std::cerr << "q1: " << q1 << std::endl;
23     std::cerr << "q2: " << q2 << std::endl;
24     std::cerr << "q1+q2: " << sum << std::endl;
25     std::cerr << "q1*q2: " << prod << std::endl;
26 }

with output:

q1: 0.2yr360^(-1/2)
q2: 0.1mo^(-1/2)
q1+q2: 0.54641yr360^(-1/2)
q1*q2: 0.069282yr360^-1

Dynamic dimension

If the dimension (or units) associated with a quantity are not known at compile-time, use xo::qty::xquantity instead of xo::qty::quantity.

See xo-unit/example/ex8 for code below

 1 #include "xo/unit/xquantity.hpp"
 2 #include "xo/unit/xquantity_iostream.hpp"
 3 #include <iostream>
 4
 5 int
 6 main () {
 7     using namespace xo::qty;
 8     namespace u = xo::qty::u;
 9
10     xquantity qty1(7, u::foot);
11     xquantity qty2(6.0, u::inch);
12     xquantity qty3 = qty1 + qty2;
13
14     std::cerr << "qty1: " << qty1 << std::endl;
15     std::cerr << "qty2: " << qty2 << std::endl;
16     std::cerr << "qty3: " << qty3 << std::endl;
17
18     /* rescale to mm */
19     xquantity res = qty3.rescale(xo::qty::nu::millimeter);
20
21     /* 2286mm */
22     std::cerr << "res: " << res << std::endl;
23 }

Here u::foot and u::inch are literals, but they could have been read from console input or another runtime-only context.