Corresponding Quantity¶
Sometimes, a type T
may be exactly “morally equivalent” to a specific Quantity
specialization.
If T
stores a value in an underlying type, Rep
, and that value represents a quantity whose units
are quantity-equivalent to Unit
, then there’s no danger in
converting between T
and Quantity<Unit, Rep>
, and it would be convenient to make this as easy as
possible.
The CorrespondingQuantity<T>
type trait makes this possible. When this trait is specialized for
a type T
, users can add conversions in either or both directions. These conversions will allow
T
to participate in the usual Quantity
-to-Quantity
implicit
conversions as if it were its corresponding quantity type.
How to specialize¶
A valid specialization of CorrespondingQuantity
must have two type traits:
Unit
is the unit type for the units ofT
.Rep
is the underlying storage type inT
.
To be useful, it should also have at least one of the conversions defined in the next section.
Conversions¶
When this relationship exists, it means that T
is “morally equivalent” to its corresponding
quantity type. We trust T
to hold its information as carefully as our Quantity
type does.
Therefore, we enable implicit conversions.
From T
to Quantity
¶
To enable implicit conversions from T
to its corresponding quantity Quantity<Unit, Rep>
, define
a static member function with signature Rep extract_value(T)
, as in the following example.
Example of setting up implicit conversion from T
to Quantity
Suppose we have a type MyMeters
, whose member int value
represents a length in meters. We
could set up implicit conversions from MyMeters
to Quantity<Meters, int>
like so:
Recall that the custom type T
is considered to be fully equivalent to its corresponding quantity
Q
. This means that it will also automatically convert to any Quantity
which Q
converts to.
See the following example.
Example of ‘two-hop’ conversion, continued from above
Here we have a “two-hop” conversion. MyMeters{3}
is treated as equivalent to
a Quantity<Meters, int>
holding a 3
. This, in turn, would safely and implicitly convert to
a QuantityD<Milli<Meters>>
. Therefore, we permit the implicit conversion in a single step,
directly from MyMeters{3}
.
The final result would be milli(meters)(3000.0)
.
From Quantity
to T
¶
To enable implicit conversions from the corresponding quantity Quantity<Unit, Rep>
of a type T
,
to T
itself, define a static member function with signature T construct_from_value(Rep)
, as in
the following example.
Example of setting up implicit conversion from Quantity
to T
Suppose we have a type MyDegrees
, whose member float value
represents an angle in degrees.
We could set up implicit conversions from MyDegrees
to Quantity<Degrees, float>
like so:
Recall that the custom type T
is considered to be fully equivalent to its corresponding quantity
Q
. This means that any Quantity
which converts to Q
will also convert to T
. See the
following example.
Example of ‘two-hop’ conversion, continued from above
Here we have a “two-hop” conversion. The corresponding quantity for MyDegrees
is
Quantity<Degrees, float>
. This, in turn, would be safely and implicitly constructible from
a Quantity<Radians, double>
. Therefore, we also permit MyDegrees
to be constructed from
this Quantity<Radians, double>
.
The final result would be MyDegrees{90.0f}
, within floating point rounding error.
as_quantity()
¶
The as_quantity()
function converts any type to an instance of its corresponding Quantity
, as
long as this direction of conversion has been set up. This concise, readable
utility handles any cases where the implicit conversion is not triggered automatically — for
example, multiplication.
Multiplying an Au speed by a chrono
duration
Imagine we have a third-party API which measures durations, and returns its results using the
venerable std::chrono
library.
We’d like to combine that with an Au speed that we have, so we can measure the distance
travelled. Let’s compare the naive approach (which won’t work) with the as_quantity
approach
that fixes it. (Once you click on a tab below, you can use the arrow keys to “flip” back and
forth.)
const auto speed = (miles / hour)(65.0);
const QuantityD<Meters> dist = speed * measure_duration();
// Compiler error! ------------------^
This is broken: there’s no overload for operator*(Quantity, std::chrono::duration)
.
const auto speed = (miles / hour)(65.0);
const QuantityD<Meters> dist = speed * as_quantity(measure_duration());
// Fixed: -----------------------------^^^^^^^^^^^
as_quantity(measure_duration())
means “take the result of measure_duration()
, and
re-express it as whatever Quantity
is most appropriate”. At this point, Au’s machinery
takes over. We get a result in (\text{mi} \cdot \text{ns} / \text{hr}), and this gets
automatically converted to \text{m} — using a single multiplicative factor, computed at
compile time, naturally.
as_quantity()
is SFINAE-friendly. If you
have a template on a type T
, you can use delctype(as_quantity(T{}))
in a SFINAE context — such
as std::enable_if_t
, or a trailing return
type — to constrain the template. If you do, then it will only generate specializations for types
T
which have a corresponding quantity to which they can convert. There are some
examples
in the library itself.
Built-in corresponding quantities¶
Au strives to minimize dependencies, but we do depend on C++14. Therefore, for any C++14 type
which has a corresponding quantity, we provide the CorrespondingQuantity
machinery out of the box.
Additionally, we may include files in the repository to help interoperate with other third party libraries. Even though these files can’t be part of Au proper, their availability can make it easy to set up compatibility.
Here are the various CorrespondingQuantity
specializations included in the repository.
std::chrono::duration
¶
std::chrono::duration
has two template
parameters: Rep
, and Period
. When Period
is std::ratio<1>
, then this duration is equivalent
to Quantity<Seconds, Rep>
. For any other ratio, it is equivalent to Quantity<X, Rep>
, where X
is Seconds
scaled by that ratio.
We include this correspondence out of the box. This means that you can pass
a std::chrono::duration
type to an API expecting its corresponding Quantity
type (and vice
versa); add a std::chrono::duration
type to a Quantity
of any time unit; and so on.
nholthaus/units library¶
We include a file that sets up a correspondence between the quantity types in the popular nholthaus/units library, and those in Au.
This file is not “active” by default. You will need to set it up in your project, following our detailed how-to guide.