<<

Implementing a Physical Units Library for C++

Mateusz Pusz April 12, 2019 Famous motivating example The Mars Climate Orbiter

• Robotic space probe launched by NASA on December 11, 1998

ACCU 2019 | Implementing a Physical Units Library for C++ 3 The Mars Climate Orbiter

• Robotic space probe launched by NASA on December 11, 1998 • Project costs: $327.6 million – spacecra development: $193.1 million – launching it: $91.7 million – mission operations: $42.8 million

ACCU 2019 | Implementing a Physical Units Library for C++ 3 The Mars Climate Orbiter

• Robotic space probe launched by NASA on December 11, 1998 • Project costs: $327.6 million – spacecra development: $193.1 million – launching it: $91.7 million – mission operations: $42.8 million • Mars Climate Orbiter began the planned orbital insertion maneuver on September 23, 1999 at 09:00:46 UTC

ACCU 2019 | Implementing a Physical Units Library for C++ 3

The Mars Climate Orbiter

• Space probe went out of radio contact when it passed behind Mars at 09:04:52 UTC, 49 seconds earlier than expected • Communication was never reestablished • The spacecra disintegrated due to atmospheric stresses

ACCU 2019 | Implementing a Physical Units Library for C++ 6 What went wrong?

ACCU 2019 | Implementing a Physical Units Library for C++ 7 What went wrong?

• The primary cause of this discrepancy was that – one piece of ground soware supplied by Lockheed Martin produced results in a United States customary unit, contrary to its Soware Interface Specification (SIS) – second system, supplied by NASA, expected those results to be in SI units, in accordance with the SIS

ACCU 2019 | Implementing a Physical Units Library for C++ 7 What went wrong?

• The primary cause of this discrepancy was that – one piece of ground soware supplied by Lockheed Martin produced results in a United States customary unit, contrary to its Soware Interface Specification (SIS) – second system, supplied by NASA, expected those results to be in SI units, in accordance with the SIS • Specifically – soware that calculated the total produced by thruster firings calculated results in pound- seconds – the trajectory calculation soware then used these results to update the predicted position of the spacecra and expected it to be in newton-seconds

ACCU 2019 | Implementing a Physical Units Library for C++ 7

Human error occurs all the time

Human error occurs all the time. But even so we have a tremendous success rate because we have systems that detect and correct the errors. The problem here is that our system failed to do that. -- Carl Pilcher science director at NASA

ACCU 2019 | Implementing a Physical Units Library for C++ 9 ANOTHER EXAMPLE

Why do I care? A long time ago in a galaxy far far away...

ACCU 2019 | Implementing a Physical Units Library for C++ 11 A long time ago in a galaxy far far away...

ACCU 2019 | Implementing a Physical Units Library for C++ 11 Tactical Flight Computer

ACCU 2019 | Implementing a Physical Units Library for C++ 12 Tactical Flight Computer

ACCU 2019 | Implementing a Physical Units Library for C++ 13 What is the correct order?

void DistanceBearing(double lat1, double lon1, double lat2, double lon2, double *Distance, double *Bearing);

ACCU 2019 | Implementing a Physical Units Library for C++ 14 What is the correct order?

void DistanceBearing(double lat1, double lon1, double lat2, double lon2, double *Distance, double *Bearing);

void FindLatitudeLongitude(double Lat, double Lon, double Bearing, double Distance, double *lat_out, double *lon_out);

ACCU 2019 | Implementing a Physical Units Library for C++ 14 What is the correct order?

void DistanceBearing(double lat1, double lon1, double lat2, double lon2, double *Distance, double *Bearing);

void FindLatitudeLongitude(double Lat, double Lon, double Bearing, double Distance, double *lat_out, double *lon_out);

ACCU 2019 | Implementing a Physical Units Library for C++ 14 double - an ultimate type to express quantity

double GlidePolar::MacCreadyAltitude(double emcready, double Distance, const double Bearing, const double WindSpeed, const double WindBearing, double *BestCruiseTrack, double *VMacCready, const bool isFinalGlide, double *TimeToGo, const double AltitudeAboveTarget, const double cruise_efficiency, const double TaskAltDiff);

ACCU 2019 | Implementing a Physical Units Library for C++ 15 We shouldn't write the code like that anymore

// Air Density(kg/m3) from relative humidity(%), // temperature(°C) and absolute pressure(Pa) double AirDensity(double hr, double temp, double abs_press) { return (1/(287.06*(temp+273.15))) * (abs_press - 230.617 * hr * exp((17.5043*temp)/(241.2+temp))); }

ACCU 2019 | Implementing a Physical Units Library for C++ 16 DID YOU EVER HAVE TO WRITE THE CODE THIS WAY?

ACCU 2019 | Implementing a Physical Units Library for C++ 17 Why do we write our code this way?

ACCU 2019 | Implementing a Physical Units Library for C++ 18 Why do we write our code this way?

• No support in the C++ Standard Library – std::chrono helped a lot for time and duration – date support comes in C++20 – still not enough for full

ACCU 2019 | Implementing a Physical Units Library for C++ 18 Why do we write our code this way?

• No support in the C++ Standard Library – std::chrono helped a lot for time and duration – date support comes in C++20 – still not enough for full dimensional analysis • Lack of good alternatives – poor user experience (i.e. compilation errors) – heavy dependencies (i.e. Boost.Units) – custom 3rd party libraries oen not allowed in production code

ACCU 2019 | Implementing a Physical Units Library for C++ 18 Why do we write our code this way?

• No support in the C++ Standard Library – std::chrono helped a lot for time and duration – date support comes in C++20 – still not enough for full dimensional analysis • Lack of good alternatives – poor user experience (i.e. compilation errors) – heavy dependencies (i.e. Boost.Units) – custom 3rd party libraries oen not allowed in production code • Implementing a good library by ourselves is hard

ACCU 2019 | Implementing a Physical Units Library for C++ 18 Why do we write our code this way?

• No support in the C++ Standard Library – std::chrono helped a lot for time and duration – date support comes in C++20 – still not enough for full dimensional analysis • Lack of good alternatives – poor user experience (i.e. compilation errors) – heavy dependencies (i.e. Boost.Units) – custom 3rd party libraries oen not allowed in production code • Implementing a good library by ourselves is hard

Let's do something about that!

ACCU 2019 | Implementing a Physical Units Library for C++ 18 TERMS AND DEFINITIONS

ACCU 2019 | Implementing a Physical Units Library for C++ 19 International System of Units (SI)

• 7 base units • 22 named units • 20 prefixes to the unit names and unit symbols

ACCU 2019 | Implementing a Physical Units Library for C++ 20 SI Base Units

QUANTITY DIMENSION SYMBOL UNIT SYMBOL UNIT NAME

time T s second

length L m metre

mass M kg kilogram

electric current I A ampere

thermodynamic temperature Θ K kelvin

amount of substance N mol mole

luminous intensity J cd candela

ACCU 2019 | Implementing a Physical Units Library for C++ 21 Examples of SI derived units expressed in SI Based Units

QUANTITY DIMENSION SYMBOL UNIT SYMBOL UNIT NAME

area A m2 square metre

volume V m3 cubic metre

−1 v m⋅ s metre per second

−2 acceleration a m⋅ s

−3 density ρ kg⋅ m kilogram per cubic metre

−1 magnetic field strength H A⋅ m ampere per metre

−2 luminance Lv cd⋅ m candela per square metre

ACCU 2019 | Implementing a Physical Units Library for C++ 22 Examples of SI derived units with special name

QUANTITY UNIT SYMBOL UNIT NAME IN OTHER SI UNITS IN SI BASE UNITS

frequency Hz hertz --- s-1

-2 force N newton --- kg⋅ m⋅ s

2 −1 −2 preassure Pa pascal N/m kg⋅ m ⋅ s

2 −2 ⋅ energy J N m kg⋅ m ⋅ s

2 −3 power W J/s kg⋅ m ⋅ s

electric charge C coulomb --- s⋅ A

2 −3 −1 voltage V volt W/A kg⋅ m ⋅ s ⋅ A

−1 −2 4 2 capacitance F farad C/V kg ⋅ m ⋅ s ⋅ A

2 −3 −2 resistance Ω ohm V/A kg⋅ m ⋅ s ⋅ A

ACCU 2019 | Implementing a Physical Units Library for C++ 23 Dimensional Analysis

Power = Energy / Time

ACCU 2019 | Implementing a Physical Units Library for C++ 24 Dimensional Analysis

Power = Energy / Time

• W

ACCU 2019 | Implementing a Physical Units Library for C++ 24 Dimensional Analysis

Power = Energy / Time

• W • J/s

ACCU 2019 | Implementing a Physical Units Library for C++ 24 Dimensional Analysis

Power = Energy / Time

• W • J/s

• N⋅ m/s −2 • kg⋅ m⋅ s ⋅ m/s 2 −2 • kg⋅ m ⋅ s /s 2 −3 • kg⋅ m ⋅ s

ACCU 2019 | Implementing a Physical Units Library for C++ 24 Dimensional Analysis

// simple numeric operations static_assert(10_km / 2 == 5_km);

ACCU 2019 | Implementing a Physical Units Library for C++ 25 Dimensional Analysis

// simple numeric operations static_assert(10_km / 2 == 5_km);

// unit conversions static_assert(1_h == 3600_s); static_assert(1_km + 1_m == 1001_m);

ACCU 2019 | Implementing a Physical Units Library for C++ 25 Dimensional Analysis

// simple numeric operations static_assert(10_km / 2 == 5_km);

// unit conversions static_assert(1_h == 3600_s); static_assert(1_km + 1_m == 1001_m);

// dimension conversions static_assert(1_km / 1_s == 1000_mps); static_assert(2_kmph * 2_h == 4_km); static_assert(2_km / 2_kmph == 1_h); static_assert(1000 / 1_s == 1_kHz); static_assert(10_km / 5_km == 2);

ACCU 2019 | Implementing a Physical Units Library for C++ 25 SI prexes

template struct ratio;

ACCU 2019 | Implementing a Physical Units Library for C++ 26 SI prexes

template struct ratio;

typedef ratio<1, 1000000000000000000> atto; typedef ratio<1, 1000000000000000> femto; typedef ratio<1, 1000000000000> pico; typedef ratio<1, 1000000000> nano; typedef ratio<1, 1000000> micro; typedef ratio<1, 1000> milli; typedef ratio<1, 100> centi; typedef ratio<1, 10> deci; typedef ratio< 10, 1> deca; typedef ratio< 100, 1> hecto; typedef ratio< 1000, 1> kilo; typedef ratio< 1000000, 1> mega; typedef ratio< 1000000000, 1> giga; typedef ratio< 1000000000000, 1> tera; typedef ratio< 1000000000000000, 1> peta; typedef ratio< 1000000000000000000, 1> exa;

ACCU 2019 | Implementing a Physical Units Library for C++ 26 More than one system of measurement

• United States customary units • Imperial units • ...

ACCU 2019 | Implementing a Physical Units Library for C++ 27 After Boost.Units

BASE DIMENSION • measurable entity of interest • essentially a tag type and provide no dimensional analysis functionality by itself • i.e. length [L], mass [M], time [T], etc.

ACCU 2019 | Implementing a Physical Units Library for C++ 28 After Boost.Units

BASE DIMENSION • measurable entity of interest • essentially a tag type and provide no dimensional analysis functionality by itself • i.e. length [L], mass [M], time [T], etc.

DIMENSION • a collection of zero or more base dimensions • each potentially raised to a dierent rational power • i.e. length = [L]1, area = [L]2, velocity = [L]1/[T]1, etc.

ACCU 2019 | Implementing a Physical Units Library for C++ 28 After Boost.Units

BASE UNIT • a specific measure of a dimension • conversions are defined using base units • i.e. [L] -> m (meter)

ACCU 2019 | Implementing a Physical Units Library for C++ 29 After Boost.Units

BASE UNIT • a specific measure of a dimension • conversions are defined using base units • i.e. [L] -> m (meter)

UNIT • a set of base units raised to rational exponents • i.e. m1, kg1, m1/s2, etc.

ACCU 2019 | Implementing a Physical Units Library for C++ 29 After Boost.Units

SYSTEM • a collection of base units representing all the measurable entities of interest for a specific problem

ACCU 2019 | Implementing a Physical Units Library for C++ 30 After Boost.Units

SYSTEM • a collection of base units representing all the measurable entities of interest for a specific problem

QUANTITY • a concrete amount of a unit • i.e. 10 meters

ACCU 2019 | Implementing a Physical Units Library for C++ 30 CURRENT STATE REVIEW OF EXISTING SOLUTIONS

ACCU 2019 | Implementing a Physical Units Library for C++ 31 Toy example

/* velocity */ avg_speed(/* length */ distance, /* time */ duration) { return distance / duration; }

ACCU 2019 | Implementing a Physical Units Library for C++ 32 Toy example

/* velocity */ avg_speed(/* length */ distance, /* time */ duration) { return distance / duration; }

const auto kmph = avg_speed(/* 220 km */, /* 2 hours */); std::cout << /* kmph */ << " km/h\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 32 Toy example

/* velocity */ avg_speed(/* length */ distance, /* time */ duration) { return distance / duration; }

const auto kmph = avg_speed(/* 220 km */, /* 2 hours */); std::cout << /* kmph */ << " km/h\n";

const auto mph = avg_speed(/* 140 miles */, /* 2 hours */); std::cout << /* mph */ << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 32 Toy example

/* velocity */ avg_speed(/* length */ distance, /* time */ duration) { return distance / duration; }

const auto kmph = avg_speed(/* 220 km */, /* 2 hours */); std::cout << /* kmph */ << " km/h\n";

const auto mph = avg_speed(/* 140 miles */, /* 2 hours */); std::cout << /* mph */ << " mph\n";

• Compile time safety to make sure that the result is of a correct dimension

ACCU 2019 | Implementing a Physical Units Library for C++ 32 Toy example

/* velocity */ avg_speed(/* length */ distance, /* time */ duration) { return distance / duration; }

const auto kmph = avg_speed(/* 220 km */, /* 2 hours */); std::cout << /* kmph */ << " km/h\n";

const auto mph = avg_speed(/* 140 miles */, /* 2 hours */); std::cout << /* mph */ << " mph\n";

• Compile time safety to make sure that the result is of a correct dimension • Support for multiple units and unit prefixes

ACCU 2019 | Implementing a Physical Units Library for C++ 32 Toy example

/* velocity */ avg_speed(/* length */ distance, /* time */ duration) { return distance / duration; }

const auto kmph = avg_speed(/* 220 km */, /* 2 hours */); std::cout << /* kmph */ << " km/h\n";

const auto mph = avg_speed(/* 140 miles */, /* 2 hours */); std::cout << /* mph */ << " mph\n";

• Compile time safety to make sure that the result is of a correct dimension • Support for multiple units and unit prefixes • No runtime overhead

ACCU 2019 | Implementing a Physical Units Library for C++ 32 Toy example

/* velocity */ avg_speed(/* length */ distance, /* time */ duration) { return distance / duration; }

const auto kmph = avg_speed(/* 220 km */, /* 2 hours */); std::cout << /* kmph */ << " km/h\n";

const auto mph = avg_speed(/* 140 miles */, /* 2 hours */); std::cout << /* mph */ << " mph\n";

• Compile time safety to make sure that the result is of a correct dimension • Support for multiple units and unit prefixes • No runtime overhead • I/O output is out-of-scope for now (waiting for std::format())

ACCU 2019 | Implementing a Physical Units Library for C++ 32 Existing solutions

• Boost.Units – authors: Matthias C. Schabel, Steven Watanabe – https://www.boost.org/doc/libs/1_69_0/doc/html/boost_units.html

ACCU 2019 | Implementing a Physical Units Library for C++ 33 Existing solutions

• Boost.Units – authors: Matthias C. Schabel, Steven Watanabe – https://www.boost.org/doc/libs/1_69_0/doc/html/boost_units.html • Units – author: Nic Holthaus – https://github.com/nholthaus/units

ACCU 2019 | Implementing a Physical Units Library for C++ 33 Existing solutions

• Boost.Units – authors: Matthias C. Schabel, Steven Watanabe – https://www.boost.org/doc/libs/1_69_0/doc/html/boost_units.html • Units – author: Nic Holthaus – https://github.com/nholthaus/units • std::chrono – author: Howard Hinnant

ACCU 2019 | Implementing a Physical Units Library for C++ 33 Boost.Units: Toy example

#include #include #include #include #include #include #include #include

ACCU 2019 | Implementing a Physical Units Library for C++ 34 Boost.Units: Toy example

#include #include #include #include #include #include #include #include

namespace bu = boost::units;

ACCU 2019 | Implementing a Physical Units Library for C++ 34 Boost.Units: Toy example

#include #include #include #include #include #include #include #include

namespace bu = boost::units;

using kilometer_base_unit = bu::make_scaled_unit>>::type; using length_kilometer = kilometer_base_unit::unit_type;

ACCU 2019 | Implementing a Physical Units Library for C++ 34 Boost.Units: Toy example

#include #include #include #include #include #include #include #include

namespace bu = boost::units;

using kilometer_base_unit = bu::make_scaled_unit>>::type; using length_kilometer = kilometer_base_unit::unit_type;

using length_mile = bu::us::mile_base_unit::unit_type; BOOST_UNITS_STATIC_CONSTANT(miles, length_mile);

ACCU 2019 | Implementing a Physical Units Library for C++ 34 Boost.Units: Toy example

#include #include #include #include #include #include #include #include

namespace bu = boost::units;

using kilometer_base_unit = bu::make_scaled_unit>>::type; using length_kilometer = kilometer_base_unit::unit_type;

using length_mile = bu::us::mile_base_unit::unit_type; BOOST_UNITS_STATIC_CONSTANT(miles, length_mile);

using time_hour = bu::metric::hour_base_unit::unit_type; BOOST_UNITS_STATIC_CONSTANT(hours, time_hour);

ACCU 2019 | Implementing a Physical Units Library for C++ 34 Boost.Units: Toy example

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d / t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 35 Boost.Units: Toy example

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d / t; }

const auto v = avg_speed(220 * bu::si::kilo * bu::si::meters, 2 * hours); using kilometers_per_hour = bu::divide_typeof_helper::type; bu::quantity kmph(v); std::cout << kmph.value() << " km/h\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 35 Boost.Units: Toy example

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d / t; }

const auto v = avg_speed(220 * bu::si::kilo * bu::si::meters, 2 * hours); using kilometers_per_hour = bu::divide_typeof_helper::type; bu::quantity kmph(v); std::cout << kmph.value() << " km/h\n";

const auto v = avg_speed(140 * miles, 2 * hours); using miles_per_hour = bu::divide_typeof_helper::type; bu::quantity mph(v); std::cout << mph.value() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 35 Boost.Units: Toy example

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d / t; }

const auto v = avg_speed(220 * bu::si::kilo * bu::si::meters, 2 * hours); using kilometers_per_hour = bu::divide_typeof_helper::type; bu::quantity kmph(v); std::cout << kmph.value() << " km/h\n";

const auto v = avg_speed(140 * miles, 2 * hours); using miles_per_hour = bu::divide_typeof_helper::type; bu::quantity mph(v); std::cout << mph.value() << " mph\n";

Works, but runtime does unnecessary operations, and I may loose some bits of information while doing that

ACCU 2019 | Implementing a Physical Units Library for C++ 35 Boost.Units: Toy example

• Too generic

template constexpr auto avg_speed(bu::quantity d, bu::quantity

ACCU 2019 | Implementing a Physical Units Library for C++ 36 Boost.Units: Toy example

• Too generic

template constexpr auto avg_speed(bu::quantity d, bu::quantity

• Is it really a velocity dimension?

template constexpr auto avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t) { return d / t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 36 Boost.Units: Toy example

• Too generic

template constexpr auto avg_speed(bu::quantity d, bu::quantity

• Is it really a velocity dimension?

template constexpr auto avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t) { return d / t; }

• Manually repeats built-in dimensional analysis logic

template constexpr bu::quantity, bu::unit>::type> avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t) { return d / t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 36 Boost.Units: Toy example

template constexpr auto avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t);

ACCU 2019 | Implementing a Physical Units Library for C++ 37 Boost.Units: Toy example

template constexpr auto avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t);

const auto kmph = avg_speed(220 * bu::si::kilo * bu::si::meters, 2 * hours); std::cout << kmph.value() << " km/h\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 37 Boost.Units: Toy example

template constexpr auto avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t);

const auto kmph = avg_speed(220 * bu::si::kilo * bu::si::meters, 2 * hours); std::cout << kmph.value() << " km/h\n";

const auto mph = avg_speed(140 * miles, 2 * hours); std::cout << mph.value() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 37 Boost.Units: Toy example

template constexpr auto avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t);

bu::quantity d = 220 * bu::si::kilo * bu::si::meters; bu::quantity t = 2 * hours; const auto kmph = avg_speed(d, t); std::cout << kmph.value() << " km/h\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 38 Boost.Units: Toy example

template constexpr auto avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t);

bu::quantity d = 220 * bu::si::kilo * bu::si::meters; bu::quantity t = 2 * hours; const auto kmph = avg_speed(d, t); std::cout << kmph.value() << " km/h\n";

bu::quantity d = 140 * miles; bu::quantity t = 2 * hours; const auto mph = avg_speed(d, t); std::cout << mph.value() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 38 Boost.Units: Summary

PROS • The widest adoption thanks to Boost • A wide range of systems and base units • High flexibility and extensibility • constexpr usage helps in compile-time • quantity can use any number-like type for its representation

ACCU 2019 | Implementing a Physical Units Library for C++ 39 Boost.Units: Summary

PROS CONS • The widest adoption thanks to Boost • Pre-C++11 design • A wide range of systems and base units • Heavily relies on macros and Boost.MPL • High flexibility and extensibility • Domain and C++ experts only • constexpr usage helps in compile-time – poor compile-time error messages • quantity can use any number-like type for its – no easy way to use non-SI units representation – spread over too many small headers (hard to compile a simple program) – designed around custom unit systems • Not possible to explicitly construct a quantity of known unit from a plain value (even if no truncation occurs)

ACCU 2019 | Implementing a Physical Units Library for C++ 39 Units: Toy example

#include "units.h"

using namespace units::literals;

ACCU 2019 | Implementing a Physical Units Library for C++ 40 Units: Toy example

#include "units.h"

using namespace units::literals;

template constexpr auto avg_speed(Length d, Time t) {

const auto v = d / t;

return v; }

ACCU 2019 | Implementing a Physical Units Library for C++ 40 Units: Toy example

#include "units.h"

using namespace units::literals;

template constexpr auto avg_speed(Length d, Time t) { static_assert(units::traits::is_length_unit::value); static_assert(units::traits::is_time_unit

ACCU 2019 | Implementing a Physical Units Library for C++ 40 Units: Toy example

#include "units.h"

using namespace units::literals;

template constexpr auto avg_speed(Length d, Time t) { static_assert(units::traits::is_length_unit::value); static_assert(units::traits::is_time_unit

• Not possible to define template arguments that will provide proper overload resolution because of unit nesting

using meter_t = units::unit_t, units::category::length_unit>>; using kilometer_t = units::unit_t, meter_t>, std::ratio<0, 1>, std::ratio<0, 1>>>;

ACCU 2019 | Implementing a Physical Units Library for C++ 40 Units: Toy example

template constexpr auto avg_speed(Length d, Time t);

ACCU 2019 | Implementing a Physical Units Library for C++ 41 Units: Toy example

template constexpr auto avg_speed(Length d, Time t);

const auto kmph = avg_speed(220_km, 2_hr); std::cout << kmph.value() << " km/h\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 41 Units: Toy example

template constexpr auto avg_speed(Length d, Time t);

const auto kmph = avg_speed(220_km, 2_hr); std::cout << kmph.value() << " km/h\n";

const auto mph = avg_speed(140_mi, 2_hr); std::cout << mph.value() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 41 Units: Toy example

template constexpr auto avg_speed(Length d, Time t);

const auto kmph = avg_speed(units::length::kilometer_t(220), units::time::hour_t(2)); std::cout << kmph.value() << " km/h\n";

const auto mph = avg_speed(units::length::mile_t(140), units::time::hour_t(2)); std::cout << mph.value() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 42 Units: Toy example

template constexpr auto avg_speed(Length d, Time t);

units::length::kilometer_t d(220); units::time::hour_t t(2); const auto kmph = avg_speed(d, t); std::cout << kmph.value() << " km/h\n";

units::length::mile_t d(140); units::time::hour_t t(2); const auto mph = avg_speed(d, t); std::cout << mph.value() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 43 Units: Toy example

template constexpr auto avg_speed(Length d, Time t);

units::length::kilometer_t d(220); units::time::hour_t t(2); const auto kmph = avg_speed(d, t); std::cout << kmph.value() << " km/h\n";

units::length::mile_t d(140); units::time::hour_t t(2); const auto mph = avg_speed(d, t); std::cout << mph.value() << " mph\n";

meter is a unit not a quantity! -- Walter Brown

ACCU 2019 | Implementing a Physical Units Library for C++ 43 Units: Summary

PROS • Single header file units.h • The conversions between units are defined as ratios at compile time • UDL support

ACCU 2019 | Implementing a Physical Units Library for C++ 44 Units: Summary

PROS CONS • Single header file units.h • Not possible to extend with own base units • The conversions between units are defined as • Poor compile-time error messages ratios at compile time • No types that represent dimensions (units only) • UDL support • Mixing quantities with units • Not easily suitable for generic programming

ACCU 2019 | Implementing a Physical Units Library for C++ 44 ISSUES WITH CURRENT SOLUTIONS

ACCU 2019 | Implementing a Physical Units Library for C++ 45 User experience: Compilation: Boost.Units

bu::quantity d = a * bu::si::kilo * bu::si::meters;

ACCU 2019 | Implementing a Physical Units Library for C++ 46 User experience: Compilation: Boost.Units

bu::quantity d = a * bu::si::kilo * bu::si::meters;

GCC-8 error: conversion from ‘quantity >, boost::units::dimensionless_type>, boost::units::list >, boost::units::dimensionless_type>, boost::units::list > >, boost::units::dimensionless_type> > >,[...]>,[...]>’ to non-scalar type ‘quantity > >, boost::units::list > > > > > > > > >,[...]>,[...]>’ requested bu::quantity d = a * bu::si::kilo * bu::si::meters; ~~~~~~~~~~~~~~~~~^~~~

ACCU 2019 | Implementing a Physical Units Library for C++ 46 User experience: Compilation: Boost.Units

bu::quantity d = a * bu::si::kilo * bu::si::meters;

CLANG-7 error: no viable conversion from 'quantity > >, dimensionless_type> > >, typename detail::make_heterogeneous_system >, dimensionless_type>, list > >, list > > > > > > > > >::type>::type>, [...]>' to 'quantity, [...]>' bu::quantity d = a * bu::si::kilo * bu::si::meters; ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

ACCU 2019 | Implementing a Physical Units Library for C++ 47 User experience: Compilation: Boost.Units

bu::quantity d = a * bu::si::kilo * bu::si::meters;

CLANG-7 error: no viable conversion from 'quantity > >, dimensionless_type> > >, typename detail::make_heterogeneous_system >, dimensionless_type>, list > >, list > > > > > > > > >::type>::type>, [...]>' to 'quantity, [...]>' bu::quantity d = a * bu::si::kilo * bu::si::meters; ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Can you guess what is wrong here?

ACCU 2019 | Implementing a Physical Units Library for C++ 47 User experience: Compilation: Boost.Units

bu::quantity d(a * bu::si::kilo * bu::si::meters);

ACCU 2019 | Implementing a Physical Units Library for C++ 48 User experience: Compilation: Boost.Units

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d * t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 49 User experience: Compilation: Boost.Units

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d * t; }

GCC-8 error: could not convert ‘boost::units::operator*(const boost::units::quantity&, const boost::units::quantity&) [with Unit1 = boost::units::unit >, boost::units::dimensionless_type>, boost::units::homogeneous_system > >, boost::units::list > > > > > > > > > >; Unit2 = boost::units::unit >, boost::units::dimensionless_type>, boost::units::homogeneous_system > >, boost::units::list > > > > > > > > > >; X = double; Y = double; typename boost::units::multiply_typeof_helper, boost::units::quantity >::type = ...

ACCU 2019 | Implementing a Physical Units Library for C++ 49 User experience: Compilation: Boost.Units

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d * t; }

GCC-8 (CONTINUED) ... boost::units::quantity >, boost::units::list >, boost::units::dimensionless_type> >, boost::units::homogeneous_system > >, boost::units::list > > > > > > > > >, void>, double>](t)’ from ‘quantity>,[...]>>,[...],[...]>,[...]>’ to ‘quantity>,[...]>>,[...],[...]>,[...]>’ return d * t; ~~^~~

ACCU 2019 | Implementing a Physical Units Library for C++ 50 User experience: Compilation: Boost.Units

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d * t; }

CLANG-7 error: no viable conversion from returned value of type 'quantity>, [...]>>, [...]>, [...]>' to function return type 'quantity>, [...]>>, [...]>, [...]>' return d * t; ^~~~~

ACCU 2019 | Implementing a Physical Units Library for C++ 51 User experience: Compilation: Boost.Units

constexpr bu::quantity avg_speed(bu::quantity d, bu::quantity t) { return d * t; }

CLANG-7 error: no viable conversion from returned value of type 'quantity>, [...]>>, [...]>, [...]>' to function return type 'quantity>, [...]>>, [...]>, [...]>' return d * t; ^~~~~

Sometimes shorter error message is not necessary better ;-)

ACCU 2019 | Implementing a Physical Units Library for C++ 51 User experience: Compilation: Units

template constexpr auto avg_speed(Length d, Time t) { return d * t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 52 User experience: Compilation: Units

template constexpr auto avg_speed(Length d, Time t) { return d * t; }

GCC-8 error: static assertion failed: Units are not compatible. static_assert(traits::is_convertible_unit::value, "Units are not compatible."); ^~~~~~

ACCU 2019 | Implementing a Physical Units Library for C++ 52 User experience: Compilation: Units

template constexpr auto avg_speed(Length d, Time t) { return d * t; }

GCC-8 error: static assertion failed: Units are not compatible. static_assert(traits::is_convertible_unit::value, "Units are not compatible."); ^~~~~~

static_assert's are oen not the best solution • do not influence the overload resolution process • for some compilers do not provide enough context

ACCU 2019 | Implementing a Physical Units Library for C++ 52 User experience: Compilation: Units

template constexpr auto avg_speed(Length d, Time t) { return d * t; }

CLANG-7 error: static_assert failed due to requirement 'traits::is_convertible_unit, base_unit, ratio<0, 1>, ratio<1, 1>, ratio<0, 1>, ratio<0, 1>, ratio<0, 1>, ratio<0, 1>, ratio<0, 1>, ratio<0, 1> >, ratio<0, 1>, ratio<0, 1> >, unit, base_unit, ratio<0, 1>, ratio<-1, 1>, ratio<0, 1>, ratio<0, 1>, ratio<0, 1>, ratio<0, 1>, ratio<0, 1>, ratio<0, 1> >, ratio<0, 1>, ratio<0, 1> > >::value' "Units are not compatible." static_assert(traits::is_convertible_unit::value, "Units are not compatible."); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

ACCU 2019 | Implementing a Physical Units Library for C++ 53 User experience: Debugging: Boost.Units

ACCU 2019 | Implementing a Physical Units Library for C++ 54 User experience: Debugging: Boost.Units

ACCU 2019 | Implementing a Physical Units Library for C++ 54 User experience: Debugging: Boost.Units

Breakpoint 1, (anonymous namespace)::avg_speed >, boost::units::dimensionless_type>, boost::units::list >, boost::units::dimensionless_type>, boost::units::list > >, boost::units::dimensionless_type> > >, boost::units::heterogeneous_system > >, boost::units::static_rational<1> >, boost::units::dimensionless_type>, boost::units::list >, boost::units::dimensionless_type>, boost::units::dimensionless_type> > > (d=..., t=...) at velocity_2.cpp:39 39 return d / t;

ACCU 2019 | Implementing a Physical Units Library for C++ 55 User experience: Debugging: Boost.Units

(gdb) ptype d type = class boost::units::quantity >, boost::units::dimensionless_type>, boost::units::heterogeneous_system >, boost::units::dimensionless_type>, boost::units::list >, boost::units::dimensionless_type>, boost::units::list > >, boost::units::dimensionless_type> > >, void>, double> [with Unit = boost::units::unit >, boost::units::dimensionless_type>, boost::units::heterogeneous_system >, boost::units::dimensionless_type>, boost::units::list >, boost::units::dimensionless_type>, boost::units::list > >, boost::units::dimensionless_type> > >, void>, Y = double] { ...

ACCU 2019 | Implementing a Physical Units Library for C++ 56 User experience: Debugging: Units

ACCU 2019 | Implementing a Physical Units Library for C++ 57 User experience: Debugging: Units

ACCU 2019 | Implementing a Physical Units Library for C++ 57 User experience: Debugging: Units

Breakpoint 1, (anonymous namespace)::avg_speed, units::unit, units::base_unit > >, std::ratio<0, 1>, std::ratio<0, 1> > >, units::unit_t, units::unit, units::unit, units::base_unit, std::ratio<0, 1>, std::ratio<1> > > > > > > (d=..., t=...) at velocity.cpp:28 28 const auto v = d / t;

ACCU 2019 | Implementing a Physical Units Library for C++ 58 User experience: Debugging: Units

(gdb) ptype d type = class units::unit_t, units::unit, units::base_unit, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1> >, std::ratio<0, 1>, std::ratio<0, 1> >, std::ratio<0, 1>, std::ratio<0, 1> >, double, units::linear_scale> [with Units = units::unit, units::unit, units::base_unit, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1>, std::ratio<0, 1> >, std::ratio<0, 1>, std::ratio<0, 1> >, std::ratio<0, 1>, std::ratio<0, 1> >, T = double] : public units::linear_scale, private units::detail::_unit_t { ...

ACCU 2019 | Implementing a Physical Units Library for C++ 59 Macros omnipresence: Boost.Units

BOOST_UNITS_DEFINE_CONVERSION_FACTOR(foot_base_unit, meter_base_unit, double, 0.3048);

BOOST_UNITS_DEFINE_CONVERSION_OFFSET(celsius_base_unit, fahrenheit_base_unit, double, 32.0);

BOOST_UNITS_DEFAULT_CONVERSION(my_unit_tag, SI::force);

BOOST_UNITS_DEFINE_CONVERSION_FACTOR_TEMPLATE((long N1)(long N2), currency_base_unit, currency_base_unit, double, get_conversion_factor(N1, N2)); and more...

ACCU 2019 | Implementing a Physical Units Library for C++ 60 Macros omnipresence: Units

#if !defined(DISABLE_PREDEFINED_UNITS) || defined(ENABLE_PREDEFINED_LENGTH_UNITS) UNIT_ADD_WITH_METRIC_PREFIXES(length, meter, meters, m, unit, units::category::length_unit>) UNIT_ADD(length, foot, feet, ft, unit, meters>) UNIT_ADD(length, mil, mils, mil, unit, feet>) UNIT_ADD(length, inch, inches, in, unit, feet>) UNIT_ADD(length, mile, miles, mi, unit, feet>) UNIT_ADD(length, nauticalMile, nauticalMiles, nmi, unit, meters>) UNIT_ADD(length, astronicalUnit, astronicalUnits, au, unit, meters>) UNIT_ADD(length, lightyear, lightyears, ly, unit, meters>) UNIT_ADD(length, parsec, parsecs, pc, unit, astronicalUnits, std::ratio<-1>>) UNIT_ADD(length, angstrom, angstroms, angstrom, unit, nanometers>) UNIT_ADD(length, cubit, cubits, cbt, unit, inches>) UNIT_ADD(length, fathom, fathoms, ftm, unit, feet>) UNIT_ADD(length, chain, chains, ch, unit, feet>) UNIT_ADD(length, furlong, furlongs, fur, unit, chains>) UNIT_ADD(length, hand, hands, hand, unit, inches>) UNIT_ADD(length, league, leagues, lea, unit, miles>) UNIT_ADD(length, nauticalLeague, nauticalLeagues, nl, unit, nauticalMiles>) UNIT_ADD(length, yard, yards, yd, unit, feet>)

UNIT_ADD_CATEGORY_TRAIT(length) #endif

ACCU 2019 | Implementing a Physical Units Library for C++ 61 Extensibility

• Adding derived dimensions is pretty easy in all the libraries • Adding base dimensions is hard or nearly impossible

ACCU 2019 | Implementing a Physical Units Library for C++ 62 Extensibility

• Adding derived dimensions is pretty easy in all the libraries • Adding base dimensions is hard or nearly impossible

BOOST.UNITS

struct length_base_dimension : base_dimension {}; struct mass_base_dimension : base_dimension {}; struct time_base_dimension : base_dimension {};

• Order is completely arbitrary as long as each tag has a unique enumerable value • Non-unique ordinals are flagged as errors at compile-time • Negative ordinals are reserved for use by the library • Two independent libraries can easily choose the same ordinal (i.e. 1)

ACCU 2019 | Implementing a Physical Units Library for C++ 62 Extensibility

• Adding derived dimensions is pretty easy in all the libraries • Adding base dimensions is hard or nearly impossible

UNITS

template, class Kilogram = std::ratio<0>, class Second = std::ratio<0>, class Radian = std::ratio<0>, class Ampere = std::ratio<0>, class Kelvin = std::ratio<0>, class Mole = std::ratio<0>, class Candela = std::ratio<0>, class Byte = std::ratio<0>> struct base_unit;

• Requires refactoring the engine, all existing predefined and users' unit types

ACCU 2019 | Implementing a Physical Units Library for C++ 63 MY UNITS LIBRARY (WIP!!!) HTTPS://GITHUB.COM/MPUSZ/UNITS

ACCU 2019 | Implementing a Physical Units Library for C++ 64 Requirements

• Safety and performance – strong types – template metaprogramming – constexpr all the things

ACCU 2019 | Implementing a Physical Units Library for C++ 65 Requirements

• Safety and performance – strong types – template metaprogramming – constexpr all the things • The best possible user experience – compiler errors – debugging

ACCU 2019 | Implementing a Physical Units Library for C++ 65 Requirements

• Safety and performance – strong types – template metaprogramming – constexpr all the things • The best possible user experience – compiler errors – debugging • No macros in the user interface • No external dependencies • Easy extensibility

ACCU 2019 | Implementing a Physical Units Library for C++ 65 Requirements

• Safety and performance – strong types – template metaprogramming – constexpr all the things • The best possible user experience – compiler errors – debugging • No macros in the user interface • No external dependencies • Easy extensibility • Possibility to be standardized as a freestanding part of the C++ Standard Library

ACCU 2019 | Implementing a Physical Units Library for C++ 65 Dimensions

• units::dimension - a type-list-like type that stores an ordered list of exponents of one or more base dimensions

template struct dimension;

ACCU 2019 | Implementing a Physical Units Library for C++ 66 Dimensions

• units::dimension - a type-list-like type that stores an ordered list of exponents of one or more base dimensions

template struct dimension;

• units::exp - a base dimension and its exponent in a derived dimension

template struct exp { using dimension = BaseDimension; static constexpr int value = Value; };

ACCU 2019 | Implementing a Physical Units Library for C++ 66 Dimensions

• BaseDimension is a unique sortable compile-time value

ACCU 2019 | Implementing a Physical Units Library for C++ 67 Dimensions

• BaseDimension is a unique sortable compile-time value

template using dim_id = std::integral_constant;

ACCU 2019 | Implementing a Physical Units Library for C++ 67 Dimensions

• BaseDimension is a unique sortable compile-time value

template using dim_id = std::integral_constant;

EXAMPLE

struct base_dim_length : dim_id<0> {}; struct base_dim_mass : dim_id<1> {}; struct base_dim_time : dim_id<2> {};

ACCU 2019 | Implementing a Physical Units Library for C++ 67 Dimensions

• BaseDimension is a unique sortable compile-time value

template using dim_id = std::integral_constant;

EXAMPLE

struct base_dim_length : dim_id<0> {}; struct base_dim_mass : dim_id<1> {}; struct base_dim_time : dim_id<2> {};

The same problem with extensibility as with Boost.Units. If two users will select the same ID for their types than we have problems

ACCU 2019 | Implementing a Physical Units Library for C++ 67 P0732 Class Types in Non-Type Template Parameters

• Allow non-union class types to appear in non-type template parameters • Require that types used as such, and all of their bases and non-static data members recursively, have a non-user-provided operator<=> returning a type that is implicitly convertible to std::strong_equality, and contain no references

ACCU 2019 | Implementing a Physical Units Library for C++ 68 P0732 Class Types in Non-Type Template Parameters

• Allow non-union class types to appear in non-type template parameters • Require that types used as such, and all of their bases and non-static data members recursively, have a non-user-provided operator<=> returning a type that is implicitly convertible to std::strong_equality, and contain no references

template class enity { /* ... */ }; entity<"hello"> e;

ACCU 2019 | Implementing a Physical Units Library for C++ 68 C++20 Exponent and Base Dimension

• base_dimension can be either directly fixed_string or a type that will include additional information (i.e. user's namespace name)

using base_dimension = fixed_string;

ACCU 2019 | Implementing a Physical Units Library for C++ 69 C++20 Exponent and Base Dimension

• base_dimension can be either directly fixed_string or a type that will include additional information (i.e. user's namespace name)

using base_dimension = fixed_string;

• exp with C++20 support

template struct exp { static constexpr base_dimension dimension = BaseDimension; static constexpr int value = Value; };

ACCU 2019 | Implementing a Physical Units Library for C++ 69 C++20 Exponent and Base Dimension

• base_dimension can be either directly fixed_string or a type that will include additional information (i.e. user's namespace name)

using base_dimension = fixed_string;

• exp with C++20 support

template struct exp { static constexpr base_dimension dimension = BaseDimension; static constexpr int value = Value; };

EXAMPLE

inline constexpr base_dimension base_dim_length("length"); inline constexpr base_dimension base_dim_time("time");

ACCU 2019 | Implementing a Physical Units Library for C++ 69 C++20 Exponent and Base Dimension

• base_dimension can be either directly fixed_string or a type that will include additional information (i.e. user's namespace name)

using base_dimension = fixed_string;

• exp with C++20 support

template struct exp { static constexpr base_dimension dimension = BaseDimension; static constexpr int value = Value; };

EXAMPLEMuch easier to extend the library with new base dimension without identifier

inlinecollisions constexpr between base_dimension vendors base_dim_length("length"); inline constexpr base_dimension base_dim_time("time");

ACCU 2019 | Implementing a Physical Units Library for C++ 69 Dimensions

• We can express velocity in the following way

dimension, exp>

– improves user experience – as short as possible template instantiations – easy to understand by every engineer

ACCU 2019 | Implementing a Physical Units Library for C++ 70 Dimensions

• We can express velocity in the following way

dimension, exp>

– improves user experience – as short as possible template instantiations – easy to understand by every engineer

PROBLEM • The same type expected for both operations

constexpr auto v1 = 1_m / 1_s; constexpr auto v2 = 2 / 2_s * 1_m; static_assert(std::is_same);

ACCU 2019 | Implementing a Physical Units Library for C++ 70 Dimensions: make_dimension factory helper

template struct make_dimension { using type = /* unspecified */; }; template using make_dimension_t = make_dimension::type;

• Provides unique ordering for contained base dimensions • Aggregates two arguments of the same base dimension but dierent exponents • Eliminates two arguments of the same base dimension and with opposite equal exponents

ACCU 2019 | Implementing a Physical Units Library for C++ 71 Dimensions: make_dimension factory helper

template struct make_dimension { using type = /* unspecified */; }; template using make_dimension_t = make_dimension::type;

• Provides unique ordering for contained base dimensions • Aggregates two arguments of the same base dimension but dierent exponents • Eliminates two arguments of the same base dimension and with opposite equal exponents

EXAMPLE • To form a velocity

make_dimension_t, exp>

ACCU 2019 | Implementing a Physical Units Library for C++ 71 Units

• unit - the unit of a specific physical dimension

template struct unit { using dimension = Dimension; using ratio = Ratio; };

ACCU 2019 | Implementing a Physical Units Library for C++ 72 Units

• unit - the unit of a specific physical dimension

template struct unit { using dimension = Dimension; using ratio = Ratio; };

EXAMPLE To express meter

unit>

ACCU 2019 | Implementing a Physical Units Library for C++ 72 Quantities

• quantity - the amount of a specific dimension expressed in a specific unit of that dimension

template class quantity;

ACCU 2019 | Implementing a Physical Units Library for C++ 73 Quantities

• quantity - the amount of a specific dimension expressed in a specific unit of that dimension

template class quantity;

• Interface similar to std::chrono::duration + additional member functions – multiplication of 2 quantities with dierent dimensions

1_kmph * 1_h == 1_km

– division of 2 quantities of dierent dimensions

1_km / 1_h == 1_kmph

– division of a scalar with a quantity

1 / 1_s == 1_Hz

ACCU 2019 | Implementing a Physical Units Library for C++ 73 Quantities

• quantity - the amount of a specific dimension expressed in a specific unit of that dimension

template class quantity;

• Interface similar to std::chrono::duration + additional member functions – quantity value equal to 1 expressed in a specific Rep

[[nodiscard]] static constexpr quantity one() noexcept;

ACCU 2019 | Implementing a Physical Units Library for C++ 74 Quantities

• quantity - the amount of a specific dimension expressed in a specific unit of that dimension

template class quantity;

EXAMPLE

template using velocity = quantity;

ACCU 2019 | Implementing a Physical Units Library for C++ 75 Struggling with the user experience

1 Generic programming

2 Compile-time errors

3 Debugging

ACCU 2019 | Implementing a Physical Units Library for C++ 76 Do you remember that?

BOOST.UNITS • Is it really a velocity dimension?

template constexpr auto avg_speed(bu::quantity, Rep1> d, bu::quantity, Rep2> t) { return d / t; }

UNITS

template constexpr auto avg_speed(Length d, Time t) { static_assert(units::traits::is_length_unit::value); static_assert(units::traits::is_time_unit

• Not possible to define template arguments that will provide proper overload resolution

ACCU 2019 | Implementing a Physical Units Library for C++ 77 C++ Concepts to the rescue

BOOST.UNITS

#include template concept QuantityOf = bu::is_quantity_of_dimension::value;

ACCU 2019 | Implementing a Physical Units Library for C++ 78 C++ Concepts to the rescue

BOOST.UNITS

#include template concept QuantityOf = bu::is_quantity_of_dimension::value;

template concept Length = QuantityOf; template concept Time = QuantityOf; template concept Velocity = QuantityOf;

ACCU 2019 | Implementing a Physical Units Library for C++ 78 C++ Concepts to the rescue

BOOST.UNITS

#include template concept QuantityOf = bu::is_quantity_of_dimension::value;

template concept Length = QuantityOf; template concept Time = QuantityOf; template concept Velocity = QuantityOf;

constexpr Velocity auto avg_speed(Length auto d, Time auto t) { return d / t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 78 C++ Concepts to the rescue

UNITS

template concept Length = units::traits::is_length_unit::value; template concept Time = units::traits::is_time_unit::value; template concept Velocity = units::traits::is_velocity_unit::value;

ACCU 2019 | Implementing a Physical Units Library for C++ 79 C++ Concepts to the rescue

UNITS

template concept Length = units::traits::is_length_unit::value; template concept Time = units::traits::is_time_unit::value; template concept Velocity = units::traits::is_velocity_unit::value;

constexpr Velocity auto avg_speed(Length auto d, Time auto t) { return d / t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 79 C++ Concepts to the rescue

UNITS

template concept Length = units::traits::is_length_unit::value; template concept Time = units::traits::is_time_unit::value; template concept Velocity = units::traits::is_velocity_unit::value;

constexpr Velocity auto avg_speed(Length auto d, Time auto t) { return d / t; }

Concepts can be used in places where regular template argument deduction does not work (i.e. return types, class template parameters, etc).

ACCU 2019 | Implementing a Physical Units Library for C++ 79 I lied a bit ;-)

All template types are heavily embraced with concepts

ACCU 2019 | Implementing a Physical Units Library for C++ 80 I lied a bit ;-)

All template types are heavily embraced with concepts

UNITS ENGINE CONCEPTS • TypeList • Number • Ratio • Exponent • Dimension • Unit • Quantity

ACCU 2019 | Implementing a Physical Units Library for C++ 80 I lied a bit ;-)

All template types are heavily embraced with concepts

UNITS ENGINE CONCEPTS PREDEFINED QUANTITIES CONCEPTS • TypeList • Length • Number • Time • Ratio • Frequency • Exponent • Velocity • Dimension • ...

• Unit template concept Velocity = Quantity && • Quantity std::Same;

ACCU 2019 | Implementing a Physical Units Library for C++ 80 Type aliases are great (but not for users)

• Velocity is one of the simplest derived dimensions one can imagine

using velocity = make_dimension_t, exp>;

ACCU 2019 | Implementing a Physical Units Library for C++ 81 Type aliases are great (but not for users)

• Velocity is one of the simplest derived dimensions one can imagine

using velocity = make_dimension_t, exp>;

• Type aliases names are lost quickly during compilation process

ACCU 2019 | Implementing a Physical Units Library for C++ 81 Type aliases are great (but not for users)

• Velocity is one of the simplest derived dimensions one can imagine

using velocity = make_dimension_t, exp>;

• Type aliases names are lost quickly during compilation process • As a result user gets huge types in error messages

[with D = units::quantity, units::exp >, units::unit, units::exp >, std::ratio<1000, 3600> >, long long int>]

ACCU 2019 | Implementing a Physical Units Library for C++ 81 Type aliases are great (but not for users)

• Velocity is one of the simplest derived dimensions one can imagine

using velocity = make_dimension_t, exp>;

• Type aliases names are lost quickly during compilation process • As a result user gets huge types in error messages

[with D = units::quantity, units::exp >, units::unit, units::exp >, std::ratio<1000, 3600> >, long long int>]

It is a pity that we still do not have strong typedef's in the C++ language :-(

ACCU 2019 | Implementing a Physical Units Library for C++ 81 Inheritance to the rescue

struct dimension_velocity : make_dimension_t, exp> {};

• We get strong types that do not vanish during compilation process

ACCU 2019 | Implementing a Physical Units Library for C++ 82 Inheritance to the rescue

struct dimension_velocity : make_dimension_t, exp> {};

• We get strong types that do not vanish during compilation process • Easily applicable only to simple classes because of problems with – constructors – assignment operators – comparison operators – ...

ACCU 2019 | Implementing a Physical Units Library for C++ 82 Inheritance to the rescue

struct dimension_velocity : make_dimension_t, exp> {};

• We get strong types that do not vanish during compilation process • Easily applicable only to simple classes because of problems with – constructors – assignment operators – comparison operators – ... • CRTP could help but it complicates the design

ACCU 2019 | Implementing a Physical Units Library for C++ 82 Inheritance to the rescue

struct dimension_velocity : make_dimension_t, exp> {};

• We get strong types that do not vanish during compilation process • Easily applicable only to simple classes because of problems with – constructors – assignment operators – comparison operators – ... • CRTP could help but it complicates the design • Anyway velocity should be considered a helper alias for quantity rather than a strong type

template using velocity = quantity;

ACCU 2019 | Implementing a Physical Units Library for C++ 82 Upcasting problem

struct dimension_velocity : make_dimension_t, exp> {}; template using velocity = quantity;

Velocity auto v = 10_m / 2_s;

ACCU 2019 | Implementing a Physical Units Library for C++ 83 Upcasting problem

struct dimension_velocity : make_dimension_t, exp> {}; template using velocity = quantity;

Velocity auto v = 10_m / 2_s;

How to form dimension_velocity class from division of dimension_length by dimension_time?

ACCU 2019 | Implementing a Physical Units Library for C++ 83 P0887 The identity metafunction

template struct type_identity { using type = T; }; template using type_identity_t = type_identity::type;

ACCU 2019 | Implementing a Physical Units Library for C++ 84 Upcasting facility

BASE CLASS

template struct upcast_base { using base_type = BaseType; };

template struct dimension : upcast_base> {};

ACCU 2019 | Implementing a Physical Units Library for C++ 85 Upcasting facility

BASE CLASS

template struct upcast_base { using base_type = BaseType; };

template struct dimension : upcast_base> {};

HELPER ALIASES

template using upcast_from = T::base_type; template using upcast_to = std::type_identity;

ACCU 2019 | Implementing a Physical Units Library for C++ 85 Upcasting facility

UPCASTING TRAITS

template struct upcasting_traits : upcast_to {}; template using upcasting_traits_t = upcasting_traits::type;

ACCU 2019 | Implementing a Physical Units Library for C++ 86 Upcasting facility

template struct dimension : upcast_base> {};

struct dimension_velocity : make_dimension_t, exp> {};

ACCU 2019 | Implementing a Physical Units Library for C++ 87 Upcasting facility

template struct dimension : upcast_base> {};

struct dimension_velocity : make_dimension_t, exp> {};

template<> struct upcasting_traits> : upcast_to {};

ACCU 2019 | Implementing a Physical Units Library for C++ 87 Upcasting facility

template struct dimension : upcast_base> {};

struct dimension_velocity : make_dimension_t, exp> {};

template<> struct upcasting_traits> : upcast_to {};

Help needed: How to automate the last line?

ACCU 2019 | Implementing a Physical Units Library for C++ 87 Upcasting facility

BASE UNIT

struct meter : unit> {}; template<> struct upcasting_traits> : upcast_to {};

ACCU 2019 | Implementing a Physical Units Library for C++ 88 Upcasting facility

BASE UNIT

struct meter : unit> {}; template<> struct upcasting_traits> : upcast_to {};

UNIT WITH PREFIX

struct kilometer : kilo {}; template<> struct upcasting_traits> : upcast_to {};

ACCU 2019 | Implementing a Physical Units Library for C++ 88 Upcasting facility

BASE UNIT

struct meter : unit> {}; template<> struct upcasting_traits> : upcast_to {};

UNIT WITH PREFIX

struct kilometer : kilo {}; template<> struct upcasting_traits> : upcast_to {};

DERIVED UNIT

struct kilometer_per_hour : derived_unit {}; template<> struct upcasting_traits> : upcast_to {};

ACCU 2019 | Implementing a Physical Units Library for C++ 88 Upcasting facility

BEFORE [with D = units::quantity, units::exp >, units::unit, units::exp >, std::ratio<1000, 3600> >, long long int>]

ACCU 2019 | Implementing a Physical Units Library for C++ 89 Upcasting facility

BEFORE [with D = units::quantity, units::exp >, units::unit, units::exp >, std::ratio<1000, 3600> >, long long int>]

AFTER [with D = units::quantity

ACCU 2019 | Implementing a Physical Units Library for C++ 89 Upcasting facility

BEFORE [with D = units::quantity, units::exp >, units::unit, units::exp >, std::ratio<1000, 3600> >, long long int>]

AFTER [with D = units::quantity

POSSIBLE WITH A DESIGN COST [with D = units::velocity

ACCU 2019 | Implementing a Physical Units Library for C++ 89 Upcasting facility

BEFORE [with D = units::quantity, units::exp >, units::unit, units::exp >, std::ratio<1000, 3600> >, long long int>]

AFTER [with D = units::quantity

POSSIBLE WITH A DESIGN COST [with D = units::velocity

Do we want the last option with a cost of CRTP usage in quantity?

ACCU 2019 | Implementing a Physical Units Library for C++ 89 C++17 CTAD (Class Template Argument Deduction)

CLASS TEMPLATE

template> class vector;

ACCU 2019 | Implementing a Physical Units Library for C++ 90 C++17 CTAD (Class Template Argument Deduction)

CLASS TEMPLATE

template> class vector;

vector v = {1, 2, 3}; vector v2(cont.begin(), cont.end());

ACCU 2019 | Implementing a Physical Units Library for C++ 90 C++17 CTAD (Class Template Argument Deduction)

CLASS TEMPLATE

template> class vector;

vector v = {1, 2, 3}; vector v2(cont.begin(), cont.end());

ALIAS TEMPLATE

namespace pmr { template using vector = std::vector>; }

ACCU 2019 | Implementing a Physical Units Library for C++ 90 C++17 CTAD (Class Template Argument Deduction)

CLASS TEMPLATE

template> class vector;

vector v = {1, 2, 3}; vector v2(cont.begin(), cont.end());

ALIAS TEMPLATE

namespace pmr { template using vector = std::vector>; }

pmr::vector v = {1, 2, 3}; pmr::vector v2(cont.begin(), cont.end());

ACCU 2019 | Implementing a Physical Units Library for C++ 90 P1021 Filling holes in Class Template Argument Deduction

ALIAS TEMPLATE • Adds CTAD support for – aggregate templates – type aliases – inherited constructors

pmr::vector v = {1, 2, 3}; pmr::vector v2(cont.begin(), cont.end());

ACCU 2019 | Implementing a Physical Units Library for C++ 91 C++20 CTAD in Units

template class quantity; template quantity(Rep r) -> quantity; template using length = quantity;

ACCU 2019 | Implementing a Physical Units Library for C++ 92 C++20 CTAD in Units

template class quantity; template quantity(Rep r) -> quantity; template using length = quantity;

units::length d1(3); // OK -> quantity units::length d2(3.14); // OK -> quantity units::length d3(3); // FAIL -> quantity units::length d4(3.14); // OK -> quantity units::length d5(3.14); // OK -> quantity

ACCU 2019 | Implementing a Physical Units Library for C++ 92 C++20 CTAD in Units

template class quantity; template quantity(Rep r) -> quantity; template using length = quantity;

units::length d1(3); // OK -> quantity units::length d2(3.14); // OK -> quantity units::length d3(3); // FAIL -> quantity units::length d4(3.14); // OK -> quantity units::length d5(3.14); // OK -> quantity

WORKAROUND THAT I WOULD PREFER NOT TO DO ("METER IS A UNIT NOT A QUANTITY! ")

template using miles = length;

units::miles d3(3); // OK -> quantity

ACCU 2019 | Implementing a Physical Units Library for C++ 92 Contracts

template requires std::Same class quantity { public: template [[nodiscard]] std::common_type_t constexpr operator/(const quantity& lhs, const quantity& rhs) { Expects(rhs != quantity(0)); // ... } };

ACCU 2019 | Implementing a Physical Units Library for C++ 93 Contracts

template requires std::Same class quantity { public: template [[nodiscard]] std::common_type_t constexpr operator/(const quantity& lhs, const quantity& rhs) { Expects(rhs != quantity(0)); // ... } }; error: macro "Expects" passed 3 arguments, but takes just 1

ACCU 2019 | Implementing a Physical Units Library for C++ 93 Contracts

template requires std::Same class quantity { public: template [[nodiscard]] std::common_type_t constexpr operator/(const quantity& lhs, const quantity& rhs) { using rhs_type = quantity; Expects(rhs != rhs_type(0)); // ... } };

ACCU 2019 | Implementing a Physical Units Library for C++ 94 Contracts

template requires std::Same class quantity { public: template [[nodiscard]] std::common_type_t constexpr operator/(const quantity& lhs, const quantity& rhs) { Expects(rhs != std::remove_cvref_t(0)); // ... } };

ACCU 2019 | Implementing a Physical Units Library for C++ 95 Contracts

template requires std::Same class quantity { public: template [[nodiscard]] std::common_type_t constexpr operator/(const quantity& lhs, const quantity& rhs) { Expects(rhs != std::remove_cvref_t(0)); // ... } };

Still not the best solution: • usage of a macro in a header file (possible ODR issue) • not a part of a function signature

ACCU 2019 | Implementing a Physical Units Library for C++ 95 C++20 Contracts

template requires std::Same class quantity { public: template [[nodiscard]] std::common_type_t constexpr operator/(const quantity& lhs, const quantity& rhs) [[expects: rhs != quantity(0)]] { // ... } };

ACCU 2019 | Implementing a Physical Units Library for C++ 96 Toy example

constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto t) { return d / t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 97 Toy example

constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto t) { return d / t; }

const auto kmph = avg_speed(220_km, 2_h); std::cout << kmph.count() << " km/h\n";

const auto mph = avg_speed(140_mi, 2_h); std::cout << mph.count() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 97 Toy example

constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto t) { return d / t; }

const auto kmph = avg_speed(units::length(220), units::time(2)); std::cout << kmph.count() << " km/h\n";

const auto mph = avg_speed(units::length(140), units::time(2)); std::cout << mph.count() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 98 Toy example

constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto t) { return d / t; }

units::length d(220); units::time t(2); const auto kmph = avg_speed(d, t); std::cout << kmph.count() << " km/h\n";

units::length d(140); units::time t(2); const auto mph = avg_speed(d, t); std::cout << mph.count() << " mph\n";

ACCU 2019 | Implementing a Physical Units Library for C++ 99 User experience: Compilation

constexpr units::velocity avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 100 User experience: Compilation

constexpr units::velocity avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

GCC-8 error: conversion from ‘quantity, units::exp >, units::unit, units::exp >, std::ratio<3600000, 1> > ,[...]>’ to non-scalar type ‘quantity’ requested

ACCU 2019 | Implementing a Physical Units Library for C++ 100 User experience: Compilation

constexpr units::velocity avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

GCC-8 error: conversion from ‘quantity, units::exp >, units::unit, units::exp >, std::ratio<3600000, 1> > ,[...]>’ to non-scalar type ‘quantity’ requested

Repeating of broken dimension type is unfortunate here, but it actually makes some code (i.e. using CTAD) easier to understand. Design decision tradeo still to decide...

ACCU 2019 | Implementing a Physical Units Library for C++ 101 User experience: Compilation after possible refactoring

constexpr units::quantity avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 102 User experience: Compilation after possible refactoring

constexpr units::quantity avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

GCC-8 error: conversion from ‘quantity, units::exp >, std::ratio<3600000, 1> > ,[...]>’ to non-scalar type ‘quantity’ requested

ACCU 2019 | Implementing a Physical Units Library for C++ 102 User experience: Compilation after possible refactoring

constexpr units::quantity avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

GCC-8 error: conversion from ‘quantity, units::exp >, std::ratio<3600000, 1> > ,[...]>’ to non-scalar type ‘quantity’ requested

BEFORE AFTER

template class quantity;

template template using length = quantity; class quantity;

units::length d(3); // 3 meters units::quantity d(3); // Huh???

void foo(Quantity auto q) { void foo(Quantity auto q) { units::length d(q); // Yay, we are working with length units::quantity d(q); // Huh??? } }

ACCU 2019 | Implementing a Physical Units Library for C++ 102 User experience: Compilation

constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

ACCU 2019 | Implementing a Physical Units Library for C++ 103 User experience: Compilation

constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

GCC-8 (FULL ERROR LOG) velocity.cpp: In instantiation of ‘constexpr units::Velocity {anonymous}::avg_speed(D, T) [with D = units::quantity; T = units::quantity]’: /mnt/c/repos/units_compare/src/mpusz/velocity.cpp:23:37: required from here velocity.cpp:12:16:error: placeholder constraints not satisfied return d * t; ^ include/units/si/velocity.h:47:16: note: within ‘template concept const bool units::Velocity [with T = units::quantity, units::exp >, units::unit, units::exp >, std::ratio<3600000, 1> >, double>]’ concept Velocity = Quantity && std::Same; ^~~~~~~~ include/stl2/detail/concepts/core.hpp:37:15: note: within ‘template concept const bool std::v1::Same [with T = units::dimension, units::exp >; U = units::dimension_velocity]’ META_CONCEPT Same = meta::Same && meta::Same; ^~~~ ...

ACCU 2019 | Implementing a Physical Units Library for C++ 103 User experience: Compilation

constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

GCC-8 (FULL ERROR LOG - CONTINUED) ... include/meta/meta_fwd.hpp:206:18: note: within ‘template concept const bool meta::Same [with T = units::dimension, units::exp >; U = units::dimension_velocity]’ META_CONCEPT Same = ^~~~ include/meta/meta_fwd.hpp:206:18: note: ‘meta::detail::bool_’ evaluated to false include/meta/meta_fwd.hpp:206:18: note: within ‘template concept const bool meta::Same [with T = units::dimension_velocity; U = units::dimension, units::exp >]’ include/meta/meta_fwd.hpp:206:18: note: ‘meta::detail::bool_’ evaluated to false

ACCU 2019 | Implementing a Physical Units Library for C++ 104 User experience: Compilation

constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto t) { return d * t; }

GCC-8 (FULL ERROR LOG - CONTINUED) ... include/meta/meta_fwd.hpp:206:18: note: within ‘template concept const bool meta::Same [with T = units::dimension, units::exp >; U = units::dimension_velocity]’ META_CONCEPT Same = ^~~~ include/meta/meta_fwd.hpp:206:18: note: ‘meta::detail::bool_’ evaluated to false include/meta/meta_fwd.hpp:206:18: note: within ‘template concept const bool meta::Same [with T = units::dimension_velocity; U = units::dimension, units::exp >]’ include/meta/meta_fwd.hpp:206:18: note: ‘meta::detail::bool_’ evaluated to false

Probably it will be improved even more with time...

ACCU 2019 | Implementing a Physical Units Library for C++ 104 User experience: Debugging

ACCU 2019 | Implementing a Physical Units Library for C++ 105 User experience: Debugging

ACCU 2019 | Implementing a Physical Units Library for C++ 105 User experience: Debugging

Breakpoint 1, (anonymous namespace)::avg_speed, units::quantity > (d=..., t=...) at velocity.cpp:31 31 return d / t;

ACCU 2019 | Implementing a Physical Units Library for C++ 106 User experience: Debugging

(gdb) ptype d type = class units::quantity [with D = units::dimension_length, U = units::kilometer, Rep = double] { ...

ACCU 2019 | Implementing a Physical Units Library for C++ 107 Open design questions

1 What to do with std::chrono::duration?

2 What is the best way to add support for temperatures?

3 Should we provide strong types and upcasting_traits for quantity types?

4 Should we provide aliases for quantities of units (i.e. meters) to workaround CTAD problem?

5 Do we need non-linear scale? How to support it?

ACCU 2019 | Implementing a Physical Units Library for C++ 108 Open design questions

1 What to do with std::chrono::duration?

2 What is the best way to add support for temperatures?

3 Should we provide strong types and upcasting_traits for quantity types?

4 Should we provide aliases for quantities of units (i.e. meters) to workaround CTAD problem?

5 Do we need non-linear scale? How to support it?

More design questions on the project website (https://github.com/mpusz/units)

ACCU 2019 | Implementing a Physical Units Library for C++ 108 Let's join forces!

We really need physical units and dimensional analysis support in the C++ Standard Library

ACCU 2019 | Implementing a Physical Units Library for C++ 109 Let's join forces!

We really need physical units and dimensional analysis support in the C++ Standard Library

WHY TO JOIN? • C++ community and industry really need it • Great opportunity to learn C++20 • An interesting and hard challenge to solve ;-)

ACCU 2019 | Implementing a Physical Units Library for C++ 109 Let's join forces!

We really need physical units and dimensional analysis support in the C++ Standard Library

WHY TO JOIN? • C++ community and industry really need it • Great opportunity to learn C++20 • An interesting and hard challenge to solve ;-)

Please, help...

ACCU 2019 | Implementing a Physical Units Library for C++ 109