Document #: | P2980R1 |
Date: | 2023-11-28 |
Project: | Programming Language C++ |
Audience: |
Library Evolution Working Group |
Reply-to: |
Mateusz Pusz (Epam
Systems) <mateusz.pusz@gmail.com> Dominik Berner <dominik.berner@gmail.com> Johel Ernesto Guerrero Peña <johelegp@gmail.com> Chip Hogg (Aurora Innovation) <charles.r.hogg@gmail.com> Nicolas Holthaus <nholthaus@gmail.com> Roth Michaels (Native Instruments) <isocxx@rothmichaels.us> Vincent Reverdy <vince.rev@gmail.com> |
Several groups in the ISO C++ Committee reviewed the “P1935: A C++ Approach to Physical Units” [P1935R2] proposal in Belfast 2019 and Prague 2020. All those groups expressed interest in the potential standardization of such a library and encouraged further work. The authors also got valuable initial feedback that highly influenced the design of the V2 version of the [mp-units] library.
In the following years, the library’s authors scoped on getting more feedback from the production and design and developed version 2 of the [mp-units] library that resolves the issues raised by the users and Committee members. The features and interfaces of this version are close to being the best we can get with the current version of the C++ language standard.
This paper is authored by not only the [mp-units] library developers but also by the authors of other actively maintained similar libraries on the market and other active members of the C++ quantities and units community who have worked on this subject for many years. We join our forces to say with one voice that we deeply care about standardizing such features as a part of the C++ Standard Library. Based on our long and broad experience in the subject, we agree that the interfaces we will provide in the upcoming proposals are the best we can get today in the C++ language.
This document consistently uses the official metrology vocabulary defined in the [ISO/IEC Guide 99] and [JCGM 200:2012].
Dominik is a strong believer that the C++ language can provide very high safety guarantees when programming through strong typing; a type error caught during compilation saves hours of debugging. For the last 15 years, he has mainly coded in C++ and actively follows its evolution through the new standards.
When working on regulated projects at Med-Tech, there usually were very tight requirements on which data types were to be used for what, which turned out to be lists of primitives to be memorized by each developer. However, throughout his career, Dominik spent way too many hours debugging and fixing issues caused by these types being incorrect. In an attempt to bring a closer semantic meaning to these lists, he eventually wrote [SI library] as a side project.
While [SI library] provides many useful features, such as type-safe conversion between physical quantities as well as zero-overhead computation for values of the same units, there are some shortcomings which would require major rework. Instead of creating yet another library, Dominik decided to join forces with the other authors of this paper to push for standardizing support for more type-safety for physical quantities. He hopes that this will eventually lead to a safer and more robust C++ and open many more opportunities for the language.
Johel got interested in the units domain while writing his first
hundred lines of game development. He got up to opening the game window,
so this milestone was not reached until years later. Instead, he looked
for the missing piece of abstraction, called “pixel” in the GUI
framework, but modeled as an
int
. He found out about [nholthaus/units], and got fascinated
with the idea of a library that succinctly allows expressing his
domain’s units (https://github.com/nholthaus/units/issues/124#issuecomment-390773279).
Johel became a contributor to [nholthaus/units] v3 from 2018 to 2020.
He improved the interfaces and implementations by remodeling them after
std::chrono::duration
. This
included parameterizing the representation type with a template
parameter instead of a macro. He also improved the error messages by
mapping a list of types to an user-defined name.
By 2020, Johel had been aware of [mp-units] v0 quantity<dim_length, length, int>
,
put off by its verbosity. But then, he watched a talk by Mateusz Pusz on
[mp-units]. It described how good error
messages was a stake in the ground for the library. Thanks to his
experience in the domain, Johel was convinced that [mp-units] was the future.
Since 2020, Johel has been contributing to [mp-units]. He added
quantity_point
, the
generalization of
std::chrono::time_point
, closing
#1. He also added quantity_kind
,
which explored the need of representing distinct quantities of the same
dimension. To help guide its evolution, he’s been constantly pointing in
the direction of [JCGM 200:2012] as a
source of truth. And more recently, to the ISO/IEC 80000 series, also
helping interpret it.
Computing systems engineer. (C++) programmer since 2014. Lives at HEAD with C++Next and good practices. Performs in-depth code reviews of familiarized code bases. Has an eye for identifying automation opportunities, and acts on them. Mostly at https://github.com/JohelEGP/.
Chip Hogg is a Staff Software Engineer on the Motion Planning Team at Aurora Innovation, the self-driving vehicle company that is developing the Aurora Driver. After obtaining his PhD in Physics from Carnegie Mellon in 2010, he was a postdoctoral researcher and then staff scientist at the National Institute of Standards and Technology (NIST), doing Bayesian data analysis. He joined Google in 2012 as a software engineer, leaving in 2016 to work on autonomous vehicles at Uber’s Advanced Technologies Group (ATG), where he stayed until their acquisition by Aurora in 2021.
Chip built his first C++ units library at Uber ATG in 2018, where he first developed the concept of unit-safe interfaces. At Aurora in 2021, he ported over only the test cases, writing a new and more powerful units library from scratch. This included novel features such as vector space magnitudes, and an adaptive conversion policy which guards against overflow in integers.
He soon realized that there was a much broader need for Aurora’s units library. No publicly available units library for C++14 or C++17 could match its ergonomics, developer experience, and performance. This motivated him to create [Au] in 2022: a new, zero-dependency units library, which was a drop-in replacement for Aurora’s original units library, but offered far more composable interfaces, and was built on simpler, stronger foundations. Once Au proved its value internally, Chip migrated it to a separate repository and led the open-sourcing process, culminating in its public release in 2023.
While Au provides excellent ergonomics and robustness for pre-C++20 users, Chip also believes the C++ community would benefit from a standard units library. For that reason, he has joined forces with the mp-units project, contributing code and design ideas.
Nicolas graduated Summa Cum Laude from Northwestern University with a B.S. in Computer Engineering. He worked for several years at the United States Naval Air Warfare Center - Manned Flight Simulator - designing real-time C++ software for aircraft survivability simulation. He has subsequently continued in the field at various start-ups, MIT Lincoln Laboratory, and most recently, STR (Science and Technology Research).
Nicolas became obsessed with dimensional analysis as a high school
JETS team member after learning that the $125M Mars Climate Orbiter was
destroyed due to a simple feet-to-meters miscalculation. He developed
the widely adopted C++ [nholthaus/units] library based on the
findings of the 2002 white paper “Dimensional Analysis in C++” by Scott
Meyers. Astounded that no one smarter had already written such a
library, he continued with units
2.0 and 3.0 based on modern C++. Those libraries have been extensively
adopted in many fields, including modeling & simulation,
agriculture, and geodesy.
In 2023, recognizing the limits of
units
, he joined forces with
Mateusz Pusz in his effort to standardize his evolutionary dimensional
analysis library, with the goal of providing the highest-quality
dimensional analysis to all C++ users via the C++ standard library.
Roth Michaels is a Principal Software Engineer at Native Instruments, a leading manufacturer of audio, and music, software and hardware. Working in this domain, he has been involved with the creation of ad hoc typed quantities/units for digital signal processing and GUI library use-cases. Seeing both the complexity of development and practical uses where developers need to leave the safety of these simple wrappers encouraged Roth to explore various quantity/units libraries to see if they would apply to this domain. He has been doing research into defining and using digital audio and music domain-specific quantities and units using first [mp-units] as proposed in [P1935R2] and the new V2 library described in this paper.
Before working for Native Instruments, Roth worked as a consultant in multiple industries using a variety of programming languages. He was involved with the Swift Evolution community in its early days before focusing primarily on C++ after joining iZotope and now Native Instruments.
Holding a degree in music composition, Roth has over a decade of experience working with quantities and units of measure related to music, digital signal processing, analog audio, and acoustics. He has joined the [mp-units] project as a domain expert in these areas and to provide perspective on logarithmic and non-linear quantity/unit relationships.
Mateusz got interested in the physical units subject while contributing to the [LK8000] Tactical Flight Computer Open Source project over 10 years ago. The project’s code was far from being “safe” in the C++ sense, and this is when Mateusz started to explore alternatives.
Through the following years, he tried to use several existing solutions, which were always far from being user-friendly, so he also tried to write a better framework a few times from scratch by himself.
Finally, with the availability of brand new Concepts TS in the gcc-7, the [mp-units] project was created. It was designed with safety and user experience in mind. After many years of working on the project, the [mp-units] library is probably the most modern and complete solution in the C++ market.
Through the last few years, Mateusz has put much effort into building a community around physical units. He provided many talks and workshops on this subject at various C++ conferences. He also approached the authors of other actively maintained libraries to get their feedback and invited them to work together to find and agree on the best solution for the C++ language. This paper is the result of those actions.
Vincent is an astrophysicist, computer scientist, and a member of the French delegation to the ISO C++ Committee, currently working as a full researcher at the French National Centre for Scientific Research (CNRS). He has been interested for years in units and quantities for programming languages to ensure higher levels of both expressivity and safety in computational physics codes. Back in 2019, he authored [P1930R0] to provide some context of what could be a quantity and unit library for C++.
After designing and implementing several Domain-Specific Language (DSL) demonstrators dedicated to units of measurements in C++, he became more interested in the theoretical side of the problem. Today, one of his research activities is dedicated to the mathematical formalization of systems of quantities and systems of units as an interdisciplinary problem between physics, mathematics, and computer science.
This chapter describes why we believe that quantities and units should be part of a C++ Standard Library.
It is no longer only the space industry or experienced pilots that benefit from the autonomous operations of some machines. We live in a world where more and more ordinary people trust machines with their lives daily. In the near future, we will be allowed to sleep while our car autonomously drives us home from a late party. As a result, many more C++ engineers are expected to write life-critical software today than it was a few years ago. However, writing safety-critical code requires extensive training and experience, both of which are in short demand. While there exists some standards and guidelines such as MISRA C++ [MISRA C++] with the aim of enforcing the creation of safe code in C++, they are cumbersome to use and tend to shift the burden on the discipline of the programmers to enforce these. At the time of writing, the C++ language does not change fast enough to enforce safe-by-construction code.
One of the ways C++ can significantly improve the safety of applications being written by thousands of developers is by introducing a type-safe, well-tested, standardized way to handle physical quantities and their units. The rationale is that people tend to have problems communicating or using proper units in code and daily life. Numerous expensive failures and accidents happened due to using an invalid unit or a quantity type.
The most famous and probably the most expensive example in the software engineering domain is the Mars Climate Orbiter that in 1999 failed to enter Mars’ orbit and crashed while entering its atmosphere [Mars Orbiter]. This is one of many examples here. People tend to confuse units quite often. We see similar errors occurring in various domains over the years:
20 °C
above the average
temperature was converted to
68 °F
. The actual temperature
increase was 32 °F
, not
68 °F
[The Guardian].The safety subject is so vast and essential by itself that we dedicated a separate paper [P2981R0] that discusses all the nuances in detail.
We standardized many library features mostly used in the implementation details (fmt, ranges, random-number generators, etc.). However, we believe that the most important role of the C++ Standard is to provide a standardized way of communication between different vendors.
Let’s imagine a world without
std::string
or
std::vector
. Every vendor has
their version of it, and of course, they are highly incompatible with
each other. As a result, when someone needs to integrate software from
different vendors, it turns out to be an unnecessarily arduous task.
Introducing
std::chrono::duration
and
std::chrono::time_point
improved
the interfaces a lot, but time is only one of many quantities that we
deal with in our software on a daily basis. We desperately need to be
able to express more quantities and units in a standardized way so
different libraries get means to communicate with each other.
If Lockheed Martin and NASA could have used standardized vocabulary types in their interfaces, maybe they would not interpret pound-force seconds as newton seconds, and the [Mars Orbiter] would not have crashed during the Mars orbital insertion maneuver.
Mission and life-critical projects, or those for embedded devices, often have to obey the safety norms that care about software for safety-critical systems (e.g., ISO 61508 is a basic functional safety standard applicable to all industries, and ISO 26262 for automotive). As a result, their company policy often forbid third-party tooling that lacks official certification. Such certification requires a specification to be certified against, and those tools often do not have one. The risk and cost of self-certifying an Open Source project is too high for many as well.
Companies often have a policy that the software they use must obey all the rules MISRA provides. This is a common misconception, as many of those rules are intended to be deviated from. However, those deviations require rationale and documentation, which is also considered to be risky and expensive by many.
All of those reasons often prevent the usage of an Open Source product in a company, which is a huge issue, as those companies typically are natural users of quantities and units libraries.
Having the quantities and units library standardized would solve those issues for many customers, and would allow them to produce safer code for projects on which human life depends every single day.
Suppose vendors can’t use an Open Source library in a production
project for the above reasons. They are forced to write their own
abstractions by themselves. Besides being costly and time-consuming, it
also happens that writing a quantities and units library by yourself is
far from easy. Doing this is complex and complicated, especially for
engineers who are not experts in the domain. There are many exceptional
corner cases to cover that most developers do not even realize before
falling into a trap in production. On the other hand, domain experts
might find it difficult to put their knowledge into code and create a
correct implementation in C++. As a result, companies either use really
simple and unsafe numeric wrappers, or abandon the effort entirely and
just use built-in types, such as
float
or
int
, to express quantity values,
thus losing all semantic categorization. This often leads to safety
issues caused by accidentally using values representing the wrong
quantity or having an incorrect unit.
Many applications of a quantity and units library may need to operate on a combination of standard (e.g. SI) and domain-specific quantities and units. The complexity of developing domain-specific solutions highlights the value in being able to define new quantities and units that have all the expressivity and safety as those provided by the library.
Experience with writing ad hoc typed quantities without library
support that can be combined with or converted to
std::chrono::duration
has shown
the downside of bespoke solutions: If not all operations or conversions
are handled, users will need to leave the safety of typed quantities to
operate on primitive types.
The interfaces of the [mp-units] library were designed with ease of extensibility in mind. Each definition of a dimension, quantity type, or unit typically takes only a single line of code. This is possible thanks to the extensive usage of C++20 class types as Non-Type Template Parameters (NTTP). For example, the following code presents how second (a unit of time in the [SI]) and hertz (a unit of frequency in the [SI]) can be defined:
inline constexpr struct second : named_unit<"s", kind_of<isq::time>> {} second;
inline constexpr struct hertz : named_unit<"Hz", 1 / second, kind_of<isq::frequency>> {} hertz;
When people think about industries that could use quantities and unit libraries, they think of a few companies related to aerospace, autonomous cars, or embedded industries. That is all true, but there are many other potential users for such a library.
Here is a list of some less obvious candidates:
As we can see, the range of domains for such a library is vast and not limited to applications involving specifically physical units. Any software that involves measurements, or operations on counts of some standard or domain-specific quantities, could benefit from a zero-cost abstraction for operating on quantity values and their units. The library also provides affine space abstractions, which may prove useful in many applications.
Plenty of physical units libraries have been available to the public for many years. In 1998 Walter Brown provided an “Introduction to the SI Library of Unit-Based Computation” paper for the International Conference on Computing in High Energy Physics [CHEP’98]. It emphasizes the importance of strong types and static type-checking. After that, it describes a library modeling the [SI] to provide “strict compile-time type-checking without run-time overhead”.
It also states that at this time, “in numeric programming,
programmers make heavy, near-exclusive, use of a language’s native
numeric types (e.g., double
)”.
Today, twenty-five years later, plenty of “Modern C++” production code
bases still use double
to
represent various quantities and units. It is high time to change
this.
Throughout the years, we have learned the best practices for handling specific cases in the domain. Various products may have different scopes and support different C++ versions. Still, taking that aside, they use really similar concepts, types, and operations under the hood. We know how to do those things already.
The authors of this paper developed and delivered multiple successful C++ libraries for this domain. Libraries developed by them have more than 90% of all the stars on GitHub in the field of physical units libraries for C++.
The authors joined forces and are working together to propose the best quantities and units library we can get with the latest version of the C++ language. They spend their private time and efforts hoping that the ISO C++ Committee will be willing to include such a feature in the C++ standard library.
The library facilities that we plan to propose in the upcoming papers is designed with the following goals in mind.
The most important property of any such a library is the safety it brings to C++ projects. The correct handling of physical quantities, units, and numerical values should be verifiable both by the compiler and by humans with manual inspection of each individual line.
In some cases, we are even eager to prioritize safe interfaces over the general usability experience (e.g. getters of the underlying raw numerical value will always require a unit in which the value should be returned in, which results in more typing and is sometimes redundant).
More information on this subject can be found in [P2981R0].
The library should be as fast or even faster than working with fundamental types. The should be no runtime overhead, and no space size overhead should be needed to implement higher-level abstractions.
The primary purpose of the library is to generate compile-time errors. If users did not introduce any bugs in the manual handling of quantities and units, the library would be of little use. This is why the library is optimized for readable compilation errors and great debugging experience.
The library is easy to use and flexible. The interfaces are straight-forward and safe by default. Users should be able to easily express any quantity and unit, which requires them to compose.
The above constraints imply the usage of special implementation techniques. The library will not only provide types, but also compile-time known values that will enable users to write easy to understand and efficient equations on quantities and units.
There are plenty of expectations from different parties regarding such a library. It should support at least:
Most entities in the library can be defined with a single line of code without preprocessor macros. Users can easily extend provided systems with custom dimensions, quantities, and units.
The set of entities required for standardization should be limited to the bare minimum.
Derived units do not need separate library types. Instead, they can
be obtained through the composition of predefined named units. Units
should not be associated with User-Defined Literals (UDLs), as it is the
case with std::chrono::duration
.
UDLs do not compose, have very limited scope and functionality, and are
expensive to standardize.
The user interface has no preprocessor macros.
It should be possible for most proposed features (besides text output) to be freestanding.
Note: The code examples presented in this paper may not exactly reflect the final interface design that is going to be proposed in the follow-up papers. We are still doing some small fine-tuning to improve the library.
So far, no papers have been submitted on a working interface of physical quantities and units. To allow the reader to better understand the features and scope of the library, this chapter presents a few simple examples.
Let’s start with a really simple example presenting basic operations that every physical quantities and units library should provide:
#include <mp-units/systems/si/si.h>
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
// 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);
// derived quantities
static_assert(1 * km / (1 * s) == 1000 * m / s);
static_assert(2 * km / h * (2 * h) == 4 * km);
static_assert(2 * km / (2 * km / h) == 1 * h);
static_assert(2 * m * (3 * m) == 6 * m2);
static_assert(10 * km / (5 * km) == 2 * one);
static_assert(1000 / (1 * s) == 1 * kHz);
Try it in the Compiler Explorer.
The next example serves as a showcase of various features available in the [mp-units] library.
#include <mp-units/format.h>
#include <mp-units/ostream.h>
#include <mp-units/systems/international/international.h>
#include <mp-units/systems/isq/isq.h>
#include <mp-units/systems/si/si.h>
#include <iostream>
using namespace mp_units;
constexpr QuantityOf<isq::speed> auto avg_speed(QuantityOf<isq::length> auto d,
<isq::time> auto t)
QuantityOf{
return d / t;
}
int main()
{
using namespace mp_units::si::unit_symbols;
using namespace mp_units::international::unit_symbols;
constexpr quantity v1 = 110 * km / h;
constexpr quantity v2 = 70 * mph;
constexpr quantity v3 = avg_speed(220. * km, 2 * h);
constexpr quantity v4 = avg_speed(isq::distance(140. * mi), 2 * isq::duration[h]);
constexpr quantity v5 = v3.in(m / s);
constexpr quantity v6 = value_cast<m / s>(v4);
constexpr quantity v7 = value_cast<int>(v6);
::cout << v1 << '\n'; // 110 km/h
std::cout << v2 << '\n'; // 70 mi/h
std::println("{}", v3); // 110 km/h
std::println("{:*^14}", v4); // ***70 mi/h****
std::println("{:%Q in %q}", v5); // 30.5556 in m/s
std::println("{0:%Q} in {0:%q}", v6); // 31.2928 in m/s
std::println("{:%Q}", v7); // 31
std}
Try it in the Compiler Explorer.
The following example codifies the history of a famous issue during the construction of a bridge across the Rhine River between the German and Swiss parts of the town Laufenburg [Hochrheinbrücke]. It also nicely presents how the Affine Space is being modeled in the library.
#include <mp-units/ostream.h>
#include <mp-units/quantity_point.h>
#include <mp-units/systems/isq/space_and_time.h>
#include <mp-units/systems/si/si.h>
#include <iostream>
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
constexpr struct amsterdam_sea_level : absolute_point_origin<isq::altitude> {
} amsterdam_sea_level;
constexpr struct mediterranean_sea_level : relative_point_origin<amsterdam_sea_level + isq::altitude(-27 * cm)> {
} mediterranean_sea_level;
using altitude_DE = quantity_point<isq::altitude[m], amsterdam_sea_level>;
using altitude_CH = quantity_point<isq::altitude[m], mediterranean_sea_level>;
template<auto R, typename Rep>
::ostream& operator<<(std::ostream& os, quantity_point<R, altitude_DE::point_origin, Rep> alt)
std{
return os << alt.quantity_ref_from(alt.point_origin) << " AMSL(DE)";
}
template<auto R, typename Rep>
::ostream& operator<<(std::ostream& os, quantity_point<R, altitude_CH::point_origin, Rep> alt)
std{
return os << alt.quantity_ref_from(alt.point_origin) << " AMSL(CH)";
}
int main()
{
// expected bridge altitude in a specific reference system
= amsterdam_sea_level + isq::altitude(330 * m);
quantity_point expected_bridge_alt
// some nearest landmark altitudes on both sides of the river
// equal but not equal ;-)
= altitude_DE::point_origin + 300 * m;
altitude_DE landmark_alt_DE = altitude_CH::point_origin + 300 * m;
altitude_CH landmark_alt_CH
// artifical deltas from landmarks of the bridge base on both sides of the river
= isq::height(3 * m);
quantity delta_DE = isq::height(-2 * m);
quantity delta_CH
// artificial altitude of the bridge base on both sides of the river
= landmark_alt_DE + delta_DE;
quantity_point bridge_base_alt_DE = landmark_alt_CH + delta_CH;
quantity_point bridge_base_alt_CH
// artificial height of the required bridge pilar height on both sides of the river
= expected_bridge_alt - bridge_base_alt_DE;
quantity bridge_pilar_height_DE = expected_bridge_alt - bridge_base_alt_CH;
quantity bridge_pilar_height_CH
::cout << "Bridge pillars height:\n";
std::cout << "- Germany: " << bridge_pilar_height_DE << '\n';
std::cout << "- Switzerland: " << bridge_pilar_height_CH << '\n';
std
// artificial bridge altitude on both sides of the river in both systems
= bridge_base_alt_DE + bridge_pilar_height_DE;
quantity_point bridge_road_alt_DE = bridge_base_alt_CH + bridge_pilar_height_CH;
quantity_point bridge_road_alt_CH
::cout << "Bridge road altitude:\n";
std::cout << "- Germany: " << bridge_road_alt_DE << '\n';
std::cout << "- Switzerland: " << bridge_road_alt_CH << '\n';
std
::cout << "Bridge road altitude relative to the Amsterdam Sea Level:\n";
std::cout << "- Germany: " << bridge_road_alt_DE - amsterdam_sea_level << '\n';
std::cout << "- Switzerland: " << bridge_road_alt_CH - amsterdam_sea_level << '\n';
std}
The above provides the following text output:
Bridge pillars height:
- Germany: 27 m
- Switzerland: 3227 cm
Bridge road altitude:
- Germany: 330 m AMSL(DE)
- Switzerland: 33027 cm AMSL(CH)
Bridge road altitude relative to the Amsterdam Sea Level:
- Germany: 330 m
- Switzerland: 33000 cm
Try it in the Compiler Explorer.
This example estimates the process of filling a storage tank with some contents. It presents:
std::chrono::duration
.#include <mp-units/chrono.h>
#include <mp-units/format.h>
#include <mp-units/math.h>
#include <mp-units/systems/isq/isq.h>
#include <mp-units/systems/si/si.h>
#include <cassert>
#include <chrono>
#include <format>
#include <numbers>
#include <utility>
// allows standard gravity (acceleration) and weight (force) to be expressed with scalar representation
// types instead of requiring the usage of Linear Algebra library for this simple example
template<class T>
requires mp_units::is_scalar<T>
inline constexpr bool mp_units::is_vector<T> = true;
namespace {
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
// add a custom quantity type of kind isq::length
inline constexpr struct horizontal_length : quantity_spec<isq::length> {} horizontal_length;
// add a custom derived quantity type of kind isq::area with a constrained quantity equation
inline constexpr struct horizontal_area : quantity_spec<isq::area, horizontal_length * isq::width> {} horizontal_area;
inline constexpr auto g = 1 * si::standard_gravity;
inline constexpr auto air_density = isq::mass_density(1.225 * kg / m3);
class StorageTank {
<horizontal_area[m2]> base_;
quantity<isq::height[m]> height_;
quantity<isq::mass_density[kg / m3]> density_ = air_density;
quantitypublic:
constexpr StorageTank(const quantity<horizontal_area[m2]>& base, const quantity<isq::height[m]>& height) :
(base), height_(height)
base_{
}
constexpr void set_contents_density(const quantity<isq::mass_density[kg / m3]>& density)
{
assert(density > air_density);
= density;
density_ }
[[nodiscard]] constexpr QuantityOf<isq::weight> auto filled_weight() const
{
const auto volume = isq::volume(base_ * height_);
const QuantityOf<isq::mass> auto mass = density_ * volume;
return isq::weight(mass * g);
}
[[nodiscard]] constexpr quantity<isq::height[m]> fill_level(const quantity<isq::mass[kg]>& measured_mass) const
{
return height_ * measured_mass * g / filled_weight();
}
[[nodiscard]] constexpr quantity<isq::volume[m3]> spare_capacity(const quantity<isq::mass[kg]>& measured_mass) const
{
return (height_ - fill_level(measured_mass)) * base_;
}
};
class CylindricalStorageTank : public StorageTank {
public:
constexpr CylindricalStorageTank(const quantity<isq::radius[m]>& radius,
const quantity<isq::height[m]>& height) :
(quantity_cast<horizontal_area>(std::numbers::pi * pow<2>(radius)), height)
StorageTank{
}
};
class RectangularStorageTank : public StorageTank {
public:
constexpr RectangularStorageTank(const quantity<horizontal_length[m]>& length,
const quantity<isq::width[m]>& width,
const quantity<isq::height[m]>& height) :
(length * width, height)
StorageTank{
}
};
} // namespace
int main()
{
const auto height = isq::height(200 * mm);
auto tank = RectangularStorageTank(horizontal_length(1'000 * mm), isq::width(500 * mm), height);
.set_contents_density(1'000 * kg / m3);
tank
const auto duration = std::chrono::seconds{200};
const quantity fill_time = value_cast<int>(quantity{duration}); // time since starting fill
const quantity measured_mass = 20. * kg; // measured mass at fill_time
const auto fill_level = tank.fill_level(measured_mass);
const auto spare_capacity = tank.spare_capacity(measured_mass);
const auto filled_weight = tank.filled_weight();
const QuantityOf<isq::mass_change_rate> auto input_flow_rate = measured_mass / fill_time;
const QuantityOf<isq::speed> auto float_rise_rate = fill_level / fill_time;
const QuantityOf<isq::time> auto fill_time_left = (height / fill_level - 1 * one) * fill_time;
const auto fill_ratio = fill_level / height;
::println("fill height at {} = {} ({} full)", fill_time, fill_level, fill_ratio.in(percent));
std::println("fill weight at {} = {} ({})", fill_time, filled_weight, filled_weight.in(N));
std::println("spare capacity at {} = {}", fill_time, spare_capacity);
std::println("input flow rate = {}", input_flow_rate);
std::println("float rise rate = {}", float_rise_rate);
std::println("tank full E.T.A. at current flow rate = {}", fill_time_left.in(s));
std}
The above code outputs:
fill height at 200 s = 0.04 m (20 % full)
fill weight at 200 s = 100 g₀ kg (980.665 N)
spare capacity at 200 s = 0.08 m³
input flow rate = 0.1 kg/s
float rise rate = 0.0002 m/s
tank full E.T.A. at current flow rate = 800 s
Try it in the Compiler Explorer.
Users can easily define new quantities and units for domain-specific use-cases. This example from digital signal processing will show how to define custom units for counting digital samples and how they can be converted to time measured in milliseconds:
#include <mp-units/format.h>
#include <mp-units/systems/isq/isq.h>
#include <mp-units/systems/si/si.h>
#include <format>
namespace dsp_dsq {
using namespace mp_units;
inline constexpr struct SampleCount : quantity_spec<dimensionless, is_kind> {} SampleCount;
inline constexpr struct SampleDuration : quantity_spec<isq::time> {} SampleDuration;
inline constexpr struct SamplingRate : quantity_spec<isq::frequency, SampleCount / isq::time> {} SamplingRate;
inline constexpr struct Sample : named_unit<"Smpl", one, kind_of<SampleCount>> {} Sample;
namespace unit_symbols {
inline constexpr auto Smpl = Sample;
}
}
int main()
{
using namespace dsp_dsq::unit_symbols;
using namespace mp_units::si::unit_symbols;
const auto sr1 = 44100.f * Hz;
const auto sr2 = 48000.f * Smpl / s;
const auto bufferSize = 512 * Smpl;
const auto sampleTime1 = (bufferSize / sr1).in(s);
const auto sampleTime2 = (bufferSize / sr2).in(ms);
const auto sampleDuration1 = (1 / sr1).in(ms);
const auto sampleDuration2 = dsp_dsq::SampleDuration(1 / sr2).in(ms);
const auto rampTime = 35.f * ms;
const auto rampSamples1 = value_cast<int>((rampTime * sr1).in(Smpl));
const auto rampSamples2 = value_cast<int>((rampTime * sr2).in(Smpl));
::println("Sample rate 1 is: {}", sr1);
std::println("Sample rate 2 is: {}", sr2);
std
::println("{} @ {} is {}", bufferSize, sr1, sampleTime1);
std::println("{} @ {} is {}", bufferSize, sr2, sampleTime2);
std
::println("One sample @ {} is {}", sr1, sampleDuration1);
std::println("One sample @ {} is {}", sr2, sampleDuration2);
std
::println("{} is {} @ {}", rampTime, rampSamples1, sr1);
std::println("{} is {} @ {}", rampTime, rampSamples2, sr2);
std}
The above code outputs:
Sample rate 1 is: 44100 Hz
Sample rate 2 is: 48000 Smpl/s
512 Smpl @ 44100 Hz is 0.01161 s
512 Smpl @ 48000 Smpl/s is 10.6667 ms
One sample @ 44100 Hz is 0.0226757 ms
One sample @ 48000 Smpl/s is 0.0208333 ms
35 ms is 1543 Smpl @ 44100 Hz
35 ms is 1680 Smpl @ 48000 Smpl/s
Try it in the Compiler Explorer.
The tables below briefly highlight the expected scope and feature set. Each of the features will be described in detail in the upcoming papers. To learn more right away and to be able to provide early feedback, we encourage everyone to check out the documentation of the [mp-units] project.
Note: The priorities provided in the below tables are the recommendations by authors based on their experience in the domain, but are in no way final. This is just an entry point for the discussion in the Committee.
The features in this chapter are intended to be provided in the same ISO proposal (possibly in a few steps).
Feature
|
Priority
|
Description
|
---|---|---|
Core library | 1 | std::quantity , expression
templates, dimensions, quantity specifications, units,
references, and concepts for them |
The Affine Space | 1 | std::quantity_point ,
absolute and relative point origins |
Quantity kinds | 1 | Support quantities of the same dimension that should be distinct,
e.g., frequency ,
activity , and
modulation_rate , or
energy and
moment_of_force |
Various quantities of the same kind | 1 | Support quantities of the same kind that should be distinct, e.g.,
width ,
height ,
wavelength (all of the kind
length ) |
Vector and tensor representation types | 2 | Support for quantities of vector and tensor representation types |
Logarithmic quantities and units support | 2 | Support for logarithmic quantities and units, e.g., decibel |
Polymorphic unit | ??? | Runtime-known type-erased unit of a specified quantity type |
quantity text output |
1 | Text output for quantity variables (number + unit) |
Units text output | 2 | Text output for unit variables |
Dimensions text output | 2 | Text output for dimension variables |
std::format support |
1 | Custom grammar and support for all the standard formatting facilities |
std::ostream support |
1 | Stream insertion operators support |
std::chrono support |
2 | Customization points that enable support for
std::chrono_duration and
std::chrono_time_point and other
external libraries |
In the above table:
std::quantity
is a class
template that is the workhorse of the library. It is an incompatible
generalization of
std::chrono::duration
.std::quantity_point
is an
incompatible generalization of
std::chrono::time_point
.std::quantity_point
of a
specific quantity type.Below, we provide a short overview of estimated additional framework-related costs associated with providing support for the following features (based on our current implementation experience):
is_kind
tag typekind_of<QS>
class and
variable templatescommon_quantity_spec()
function that finds a common node in the hierarchy treequantity_character
enumis_scalar<T>
,
is_vector<T>
,
is_tensor<T>
customizations pointsRepresentationOf<Ch>
,
VectorRepresntation<T>
,
…)system_reference
class
templateNote 1: There is no built-in support to output
quantity_point
as text.
Note 2: As long as the C++ Standard doesn’t provide a generic facility to parse localized text, we do not plan to propose any support for doing that for physical quantities and their units.
The features in this and the following chapters are intended to be provided in separate ISO proposals that will be independently discussed and considered for standardization.
Feature
|
Priority
|
Description
|
---|---|---|
The most important ISQ quantities | 1 | Specification of the most commonly used ISQ quantities |
ISQ 3-6 | 1 | Specification of the ISQ quantities specified in ISO/IEC 80000 parts 3 - 6 (Space and time, Mechanics, Thermodynamics, Electromagnetism) |
ISQ 7-12 | 2 | Specification of the ISQ quantities specified in ISO 80000 parts 7 - 12 (Light and radiation, Acoustics, Physical chemistry and molecular physics, Atomic and nuclear physics, Characteristic numbers, Condensed matter physics) |
ISQ 13 | 1 | Specification of the ISQ quantities specified in IEC 80000-13 (Information science and technology) |
Angular | 3 | Strong angular quantities |
Angular ISQ | 3 | Changes to the ISQ to support strong angular quantities |
Feature
|
Priority
|
Description
|
---|---|---|
SI | 1 | All the units, prefixes, and symbols (including prefixed versions) of the SI |
IEC 80000-13 | 1 | All the units, prefixes, and symbols (including prefixed versions) of IEC 80000-13 |
International | 1 | International yard and pound units (common to Imperial and USC systems) |
Imperial | 2 | Imperial system-specific units |
USC system | 2 | United States Customary system-specific units |
CGS system | 2 | Centimetre-Gram-Second system |
Angular | 3 | Strong angular units |
IAU system | 3 | International Astronomical Union units system |
Feature
|
Priority
|
Description
|
---|---|---|
Math | 2 | Common mathematical functions on quantities |
Random | 3 | Random number generators of quantities |
The features in this chapter are heavily used in the library, but are not domain-specific. Having them standardized (instead of left as exposition-only) could not only improve the specification of this library, but also could serve as an important building block for tools in other domains that we can get in the future from other authors.
Feature
|
Priority
|
Description
|
---|---|---|
fixed_string |
1 | String-like structural type with inline storage (can be used as an NTTP) |
Compile-time prime numbers | 1 | Compile-time facilities to break any integral value to a product of prime numbers and their powers |
Value-preserving conversions | 1 | Type trait stating if the conversion from one type to another is value preserving or not ([P0870R5], [P2509R0]?) |
Number concepts | 2 | Concepts for vector- and point-space numbers ([P3003R0]) |
Bounded numeric types | 3 | Numerical type wrappers with values bounded to a provided interval (optionally with a wraparound semantics) |
Having quantities and units support in C++ would be extremely useful for many C++ developers, and ideally, we should ship it in C++29. We believe that it can be done, and we propose a plan to get there. The plan below is, of course, not an irrevocable commitment. If things get delayed or controversial for whatever reason, quantities and units (or some parts of it) will miss the train and we will have to wait three more years. However, having a clear plan approved by LEWG will help to keep the efforts on track.
Meeting
|
C++ Milestones
|
Activities
|
---|---|---|
2023.3 (Kona) | Paper on motivation, scope, and plan to LEWG, SG6, and SG23 Paper about safety benefits and concerns to SG23 Papers about quantity arithmetics and number concepts to SG6 |
|
2024.1 (Tokyo) | Paper on the Core Framework to LEWGI, SG6, and SG16 Paper on prime numbers to LEWGI and SG6 Paper on fixed_string to LEWGI and
SG16 |
|
2024.2 (St. Louis) | Papers on systems (priority 1) to LEWGI and SG6 Move fixed_string and prime numbers
papers to LEWG |
|
2024.3 (Wrocław) | Papers on systems (priority 2 & 3) to LEWGI and SG6 Papers on the math and random utilities to LEWGI and SG6 Move a paper on number concepts to LEWG |
|
2025.1 | Last meeting for LEWG review of new C++26 features | Move Core Framework paper to LEWG Move fixed_string and prime numbers
papers to LWG |
2025.2 | C++26 CD finalized | Move papers on systems (priority 1), math and random utilities to
LEWG Move number concepts paper to LWG |
2025.3 | Move papers on systems (priority 2 & 3) to LEWG | |
2026.1 | C++26 DIS finalized | |
2026.2 | C++29 WP opens | Move Core Framework to LWG |
2026.3 | Move systems (priority 1), math and random utilities to LWG | |
2027.1 | Move systems (priority 2 & 3) to LWG | |
2027.2 | ||
2027.3 | ||
2028.1 | Last meeting for LEWG review of new C++29 features | Finalize wording review in LWG and merge into C++29 WP |
2028.2 | C++29 CS finalized | Resolve any outstanding design/wording issues |
2028.3 | Resolve NB comments | |
2029.1 | C++29 DIS finalized | Resolve NB comments |
Special thanks and recognition goes to Epam Systems for supporting Mateusz’s membership in the ISO C++ Committee and the production of this proposal.
We would also like to thank Peter Sommerlad for providing valuable feedback that helped us shape the final version of this document.