unlib
A minimal C++14 unit library
Unlib is the beginnings of a minimal, C++14-compatible SI unit library, featuring units (e.g., mass), scales (e.g., kilo), and quantities (e.g., kilogram). Unlib tries to protect you from Murphy's doings and thus often errs on the side of requiring you to be explicit, rather than allowing implicit conversions or similar conveniences.
Units
A Unit is an abstract concept representing a measure. This library provides the seven base measures of the SI system
- time (seconds)
- mass (kilogram)
- length (meter)
- current (Ampere)
- luminosity (candela)
- temperature (Kelvin)
- substance amount (mol)
and any combination of those. (Note that this means that unlib does not provide units like, e.g., kilobytes.)
All basic units and many combined units are predefined in the library, but it is easy to define your own measurements. For example, while energy is already defined, you could just as well be define it yourself by multiplying and dividing the base units just like in the formula (kg⋅m2⋅s−2):
using energy = unlib::div_unit_t< unlib::mul_unit_t< unlib::mass
, unlib::square_unit_t<unlib::length> >
, unlib::square_unit_t<unlib::time> >Energy is also power by time (P*t), and since the library already provides unlib::power, this could be used to simplify the formula considerable
using energy = unlib::mul_unit_t<unlib::power,unlib::time>;However, energy can also be defined by providing all the exponents to the unlib::unit_t meta-function:
using energy = unlib::unit_t< unlib::mass
, unlib::square_unit_t<unlib::length>
, unlib::reciprocal_unit_t<unlib::square_unit_t<unlib::time>> >;Finally, the type of a quantity defines the nested type unit_type, which could be used:
using namespace unlib::literals;
auto power_quantity = (1_kg * 1_m * 1_m) / (1_s * 1_s);
using energy = decltype(power_quantity)::unit_type;Quantities
A quantity combines a unit (mass), a scale (kilo), a specific value type (double), and an optional tag (reactive):
using kilogram = unlib::quantity<unlib::mass,unlib::kilo,double>`. (Note: Of course, kilogram is a common enough quantity to be already defined in the library.)
Quantities are what actually holds the typed values in your code. They mostly behave like C++' built-in arithmetic types: they can be added, multiplied, compared, etc. A quantity can be created from a value type explicitly:
kilogram kg{42};It cannot be implicitly created from a value type, though:
void f(unlib::quantity<unlib::mass,unlib::kilo,double>);
f(kg); // fine
f(42); // won't compile
f(kilogram{42}); // fineNor can a quantity be used where its value type is needed. If you need to pass a quantity to an API that expects one of the built-in value types instead, you have to explicitly convert it:
void f(double kg);
f(kg.get()); // returns kg
f(some_mass.get_scaled<unlib::milli>()); // will return mg no matter what scale some_mass isScales
Quantities are scaled, where scales are rational numbers. Due to the limitations of 64 bit interger arithmetic, of the standard scales provided by SI this library only covers atto–exa. They are named like unlib::atto_scaling, unlib::femto_scaling etc. and are aliases for std::atto, std::femto, etc.
The library also provides meta functions to create scaled quantities from the basic quantities:
using milligram = unlib::milli<unlib::gram>;
using kilogram = unlib::kilo<unlib::gram>;
using ton = unlib::kilo<kilogram>;Besides those, noteworthy pre-defined scales are no_scaling, as well as minute_scaling, hour_scaling, day_scaling, and week_scaling, which scale seconds to their respective time unit.
##Tags
In engineering, sometimes different quantities that must not be confused are represented by the same physical unit. For example, in electrical engineering, when it comes to AC, there is active power, reactive power, and apparent power. All three are units of power and can be represented by the physical unit Watt. Nevertheless, in engineering they must not be confused. In order to allow this, quantities also have an optional template parameter Tag. The template parameter defaults to no_tag. A quantity with the tag no_tag is considered an untagged quantity.
(Note: Using tags, the library already provides the three incompatible quantities watt for active power, var for reactive power, and voltampere for apparent power.)
A tag consists of a tag ID and a tag ratio. Except for void, any type, even an incomplete one, can be used for tag IDs. The only significance of these types is that they differ from each other. The ratio is a std::ratio and is used when multiplying or dividing tagged types.
Quantities with either differing tag IDs or differing tag ratios are considered to be of different type and cannot be assigned to each other. (There is, however, a tag_cast to circumvent this.) Quantities can be multiplied and divided if
- either (or both) operand(s) have the
no_tagtag or - both operands have the same tag.
When multiplying and dividing tagged quantities, the library keeps track of the tag's "exponents" (how many times quantities of the same tag have been multiplied or divided with).
Therefore you can, for example, multiply reactive power with time, resulting in reactive energy. This divided by reactive power results in untagged time.
You can also multiply reactive power with reactive power, resulting in a reactive tag with a different eponent. If you then divide this by reactive power, the result will be reactive power again.)
Note that tags do not reflect all properties of their engineering counterparts. For example, dividing active power by voltage, which is untagged, will result in a current quantity tagged as reactive, which very likely won't make much sense. In these cases you will have to use tag_cast to make the library submit to your application domain's rules.
Conversions
There are four different kind of casts available:
value_castallows casting between units with different value types, e.g., seconds inintvs. seconds inlong long.scale_castallows casting between units with different scales, e.g., seconds and minutes. (Note: Quantities that only differ in their scaling can implicitly be constructed from each other:)tag_castallows casting between units with different tags, e.g., active and reactive power.quantity_castallows casting between units where value types, scales, and tags might be different.
All four types of casts come in two flavors. One needs the targetted value type, scale, and/or tag to be specified (just like static_cast etc. do):
unlib::scale_cast<unlib::milli>(any_weight);The other flavor of the same cast does not need this. It returns a temporary object from which a quantity can be created, and that can be assigned to a quantity. Depending on the quantity created from it, or it is assigned to, the respective conversion is invoked automatically:
my_floating_hours float_hrs = unlib::value_cast(integer_seconds); // note: scale cast from secs to hrs is implicitRemember that this cast returns a temporary object which is not a quantity, and must be assigned to a quantity in order to be used. Specifically, it cannot be used in mathematical operations.
// won't compile
unlib::kilo<unlib::watt> power = unlib::tag_cast(some_reactive_power_in_kW) / some_time;In these cases, you need to explicitly mention the target you want to cast to:
unlib::kilo<unlib::watt> power = unlib::tag_cast<unlib::no_tag>(some_reactive_power_in_kW) / some_time;