Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Usage

Construction and conversion

Units are constructed from their base SI value and converted through named accessors:

auto length = utl::Length<float>(1500);
std::cout << length.km() << std::endl;

auto area = utl::Area<float>(6543);
std::cout << area.m2() << std::endl;

Arithmetic with predefined operators

Common physical relations are available directly:

auto v = utl::Velocity<float>(30);
auto t = utl::Time<float>(5);
auto d = v * t;
std::cout << "Distance: " << d.m() << " m" << std::endl;

auto g = utl::Acceleration<float>(9.81);
auto tt = utl::Time<float>(10);
auto s = 0.5 * g * tt * tt;
std::cout << "Free fall: " << s.m() << " m" << std::endl;

Arbitrary dimensional expressions

Any dimensionally valid expression maps back to its named unit automatically. Combinations without a named SI unit yield the raw BaseUnit, which still tracks dimensions and exposes value():

auto m = utl::Mass<float>(1);
auto &c = utl::speed_of_light_f;
auto e = m * c * c;
std::cout << "Energy: " << e.J() << " J" << std::endl;

Unit literals

The utl::literals namespace provides double-precision literal suffixes for common units:

using namespace utl::literals;

auto distance = 120.0_km + 500.0_m;
auto speed = distance / 2.0_h;
if (speed > 50.0_kmph) {
    std::cout << "Speeding: " << speed.kmph() << " km/h" << std::endl;
}

Available suffixes: _s, _ms, _min, _h, _m, _km, _mm, _kg, _g, _A, _K, _mol, _cd, _mps, _kmph, _N, _J, _W, _Hz, _Pa, _V, _rad.

Comparisons and compound assignment

Units of the same dimension support ==, !=, <, <=, >, >= as well as +=, -=, scalar *=, /= and unary minus.

Stream output

Including utl/io.hpp enables printing any unit as its value followed by its SI dimensions:

#include <utl/io.hpp>

std::cout << utl::Velocity<float>(30) << std::endl;  // 30 [s^-1 m]

Precision selection

Every unit is templated on its storage type:

auto unit_f = utl::Unit<float>(utl::PI);
auto unit_d = utl::Unit<double>(utl::PI);

Mixing storage types in the same expression is allowed; the result is promoted to the wider type (std::common_type_t):

auto sum = utl::Length<float>(1.0f) + utl::Length<double>(2.0);  // Length<double>
auto d   = utl::Velocity<float>(30.0f) * utl::Time<double>(5.0); // Length<double>

Physical constants

Predefined constants are available in float (_f) and double (_d) variants:

ConstantUnit
speed_of_light_f / _dVelocity
cesium_hyperfine_freq_f / _dFrequency
elementary_charge_f / _dElectricCharge
planc_constant_f / _dEnergy × Time
boltzmann_constant_f / _dEnergy / Temperature
avogadro_constant_f / _d1 / AmountOfSubstance
luminous_efficacy_f / _dLuminousFlux / Power

Temperature differences

Temperature is affine: °C and °F have offset origins, so the difference of two temperatures is a displacement, not an absolute point. Temperature - Temperature therefore yields a TemperatureDelta, whose degC() / degF() apply only the scale factor (never the +273.15 / +32 offset). Adding two absolute temperatures is deleted; add a delta instead:

using Temp = utl::ThermodynamicTemperature<double>;

auto d = Temp(20, Temp::TYPE::CELSIUS) - Temp(5, Temp::TYPE::CELSIUS); // TemperatureDelta
d.degC();                                                             // 15 (not -258.15)

auto warmer = Temp(20, Temp::TYPE::CELSIUS) + utl::TemperatureDelta<double>(5); // 25 °C

Angles, solid angles and torque

Angle is tracked as an eighth pseudo-dimension, so rad, sr and a plain scalar are distinct types:

auto solid = utl::Angle<double>(2.0) * utl::Angle<double>(3.0); // SolidAngle, 6 sr
auto back  = utl::sqrt(utl::SolidAngle<double>(9.0));           // Angle, 3 rad

// luminous intensity * solid angle = luminous flux (cd * sr = lm)
auto flux = utl::LuminousIntensity<double>(60.0) * utl::SolidAngle<double>(2.0); // 120 lm

// torque (N*m = energy / angle) is a distinct type from energy
auto torque = utl::Energy<double>(10.0) / utl::Angle<double>(2.0); // Torque, 5 N*m
auto work   = torque * utl::Angle<double>(2.0);                    // Energy, 10 J

// trigonometry takes an Angle and returns a scalar
auto y = utl::sin(utl::Angle<double>(90.0, utl::Angle<double>::TYPE::DEG)); // 1.0

The angle exponent defaults to zero, so every other named unit and all existing code are unaffected.

Precise time (utl/time_point.hpp)

For real-time use where sub-microsecond precision matters, the opt-in utl/time_point.hpp provides TimeDuration and TimeStamp — integer-native (int64 nanosecond) wrappers with affine semantics. They never use floating point, so they do not lose precision the way double seconds do near a large epoch.

#include <utl/time_point.hpp>
using namespace utl;

auto dt = TimeStamp(1500000000) - TimeStamp(1000000000); // TimeDuration, 500 ms
auto later = TimeStamp(1000000000) + milliseconds(250);  // TimeStamp
// TimeStamp + TimeStamp does not compile (adding absolute times is meaningless)

auto seconds_as_double = milliseconds(1500).to_time();   // Time<double>, 1.5 s

Construct durations with nanoseconds / microseconds / milliseconds / seconds, read them with ns() / us() / ms() / s() (integer counts), and cross into the dimensional world with to_time().

utl/chrono.hpp bridges these types to std::chrono:

#include <utl/chrono.hpp>

auto d  = to_duration(std::chrono::milliseconds(250));   // TimeDuration
auto c  = to_chrono(milliseconds(250));                  // std::chrono::nanoseconds
auto ts = to_timestamp(std::chrono::steady_clock::now()); // TimeStamp
auto tp = to_chrono<std::chrono::steady_clock>(ts);       // std::chrono::time_point