Skip to content

mp-units 2.5.0 released

A new product version can be obtained from GitHub and Conan.

I initially had different plans for this release, but during development it turned out that the new big feature I was working on was too big and will require some breaking changes. This put me off tracks, and unfortunately the mp-units development slowed down recently 😞

It also turned out that I got unemployed, so now I depend solely on my C++ trainer's career to earn money for living. Fortunately, I delivered many in-house C++ trainings to my customers this year, and I hope this will also be the case for the upcoming 2026. I was also busy with preparations of new talks and classes for this year's C++ conferences.

Having that much on my plate, a break from mp-units was needed to not burn out on the way. But hopefully I am back now and I plan to continue working on long awaited features now.

Said that, if you care about mp-units and would like to see it grow faster, please either contribute or consider sponsoring my work.

This release contains a lot of small patches and improvements.

This post describes the most significant changes while a much longer list of the changes introduced by the new version can be found in our Release Notes.

Representation concepts improved

First, handling of custom representation types has been changed. We no longer have to specialize is_scalar, is_vector, and is_tensor customization points for custom representation types. Their character will be deduced from the types interface now.

To enable detection of the following representation categories we provided new CPOs:

  • ComplexScalar requires all of the bellow:

    • real - CPO that checks for member or non-member function real(),
    • imag - CPO that checks for member or non-member function imag(),
    • modulus - CPO that checks for member or non-member functions modulus() or abs() (to support std::complex).
  • RealScalar is the types that provide scalar like interface and do not satisfy ComplexScalar concept

    • Additionally any type that accidentally provides such interfaces, but should not be used as a real scalar quantity representation, may opt-off from this concept by specializing the disable_real<> customization point.

      For example, we are doing this for a bool type already:

      template<> constexpr bool disable_real<bool> = true;
      
  • Vector requires the type to be compatible with the magnitude CPO which checks for member or non-member function magnitude(). Moreover, to allow using real types to represent one-dimensional vector quantities it also checks if the type satisfies RealScalar concept and:

    • if the type satisfies std::is_arithmetic it uses std::abs(),
    • otherwise, it looks for member or non-member function abs().

We've also removed the Representation concept (💥 breaking change 💥). It turned out to not be that useful.

Additionally, RepresentationOf concept now takes QuantitySpec (instead of quantity_character) (💥 breaking change 💥) which allowed us to accept representation type of any character for quantity kinds (when just unit is used and no specific quantity_spec is provided). For example, this allows us the following:

// quantity kind (any quantity type of kind length)
quantity q1 = 10 * m;
quantity q2 = cartesian_vector(1, 2, 3) * m;

// vector quantity
quantity q3 = isq::displacement(10 * m);
quantity q4 = isq::displacement(cartesian_vector(1, 2, 3) * m);

// scalar quantity
quantity q5 = isq::width(10 * m);
// quantity q6 = isq::width(cartesian_vector(1, 2, 3) * m);  // Compile-time error

As we can see above, in this release we are allowed to use a vector representation type for q2 as we do not know a specific type of this quantity. We just know that it is some kind of length. Using a vector quantity for q6 is not allowed as isq::width is defined as a scalar quantity.

On the other hand, this release also allows us to use scalar types for vector quantities, so in case of q3 we can use int for isq::displacement even though it is defined as a vector quantity. In this case an int is considered a one-dimensional vector type.

Info

To enable vector quantities usage we have added a very simple cartesian_vector representation type in this release.

std::numeric_limits support added

This release introduces proper std::numeric_limits specialization support for quantity and quantity_point types.

The default implementation delegates to the numerical properties of the underlying representation type, making it easier to integrate mp-units with generic numerical code that relies on std::numeric_limits.

Automatic SI prefix selection with si::invoke_with_prefixed

A new utility function si::invoke_with_prefixed has been added to automatically select the most appropriate SI prefix for quantity values across wide ranges:

quantity voltage = 0.001'234 * V;
si::invoke_with_prefixed([](auto q) { std::cout << q << '\n'; }, voltage, V);
// Prints: 1.234 mV

This function is particularly useful when displaying values that may span many orders of magnitude, such as in the capacitor discharge example where voltage decays from volts through millivolts, microvolts, nanovolts to picovolts.

The function supports two modes via the prefix_range parameter:

  • prefix_range::engineering (default) - selects only powers of 1000, resulting in values in range [1.0, 1000)
  • prefix_range::full - selects all SI prefixes including intermediate ones (deca, hecto, deci, centi), often resulting in values in range [1.0, 10.0)

For more details, see the Systems of Units chapter.

Improved quantity specification conversions

The conversion rules for quantity specifications have been refined to better preserve type relationships:

  • Subkinds (e.g., angular measure) are no longer implicitly convertible to their parent kind (e.g., dimensionless) (💥 breaking change 💥). This improves type safety by preventing accidental loss of specificity.
  • Convertibility of ingredients is now better preserved in derived types, ensuring that dimensional relationships are maintained correctly through operations.
  • Explicit quantity_spec conversions can now additionally be performed through the explicit constructor:

    quantity length = isq::length(42 * m);
    quantity height1 = isq::height(length);    // OK
    quantity<isq::height[m]> height2(length);  // Compile-time error before but now OK
    

Absolute quantities renamed to quantity points

In this release, we've renamed the absolute construction helper to point (💥 breaking change 💥). This change better aligns with established terminology in mathematics and computer graphics where "point" is the standard term for affine space elements.

quantity_point qp = point<deg_C>(25.0);
quantity_point qp = absolute<deg_C>(25.0);  // Deprecated

Additionally, this release adds mathematical operations for quantity points:

  • lerp - linear interpolation between two points
  • midpoint - finds the midpoint between two points
quantity_point start = point<deg_C>(20.0);
quantity_point end = point<deg_C>(30.0);
quantity_point mid = midpoint(start, end);  // 25 ℃

Quantity characters refinement

Several improvements have been made to quantity character handling:

  • Character names scalar and complex have been renamed to real_scalar and complex_scalar respectively (💥 breaking change 💥) for better clarity and consistency with mathematical terminology.
  • Electromagnetism quantities have been updated to IEC 80000-6:2022 standard.
  • Complex characters are now properly applied to all complex electromagnetism quantities including electric_current_phasor, voltage_phasor, and complex_power.

ISQ tree structure improvements

The International System of Quantities (ISQ) tree has been reorganized to better reflect physical relationships:

  • isq::displacement and isq::position_vector have been moved to different positions in the hierarchy
  • isq::velocity definition has been corrected to use isq::displacement instead of isq::position_vector, properly reflecting that velocity is the rate of change of displacement

These changes improve the semantic correctness of quantity relationships within the ISQ framework.

quantity::one() removed

The quantity::one() member function has been removed (💥 breaking change 💥). This function was not truly an identity element (it only worked correctly for dimensionless[one] quantities) and caused confusion.

For creating unit quantities, use representation_values<Rep>::one() multiplied by the desired unit:

auto q = representation_values<double>::one() * m;
auto q = quantity<si::metre, double>::one();

Overflow detection for unit conversions

A new scaling_overflows_non_zero_values function has been added to detect at compile time whether a unit conversion will cause overflow for non-zero values of the representation type:

static_assert(!scaling_overflows_non_zero_values<int>(si::kilo<si::metre>, si::metre));
static_assert(scaling_overflows_non_zero_values<std::int8_t>(si::kilo<si::metre>, si::metre));

Those are used in quantity value conversion functions and prevent an obvious overflow while changing an unit. This helps identify problematic conversions before runtime and improves code safety.

Enhanced customization points

A new is_value_preserving customization point has been added to control value-preserving conversion semantics for user-defined types:

template<typename From, typename To>
constexpr bool is_value_preserving = treat_as_floating_point<To> || !treat_as_floating_point<From>;

With this user has a fine-grained control to specify if the conversions between two specific types are value-preserving or not.

Info

The current default implementation follows std::chrono::duration approach. In the future we may replace the current default logic with new type traits. For example:

Prime factorization improvements

Thanks to @chiphogg, the magnitude system can now prime-factorize any rational magnitude, not just specific cases. This removes the need for the first_known_factor workaround and makes magnitude arithmetic more robust and efficient.

value_type_t made recursive

The value_type_t type trait now recursively unwraps nested quantity-like types, making it more useful for generic programming with complex nested structures.

Text encoding improvements

The text_encoding enumeration has been renamed to character_set (💥 breaking change 💥) to better reflect its purpose. The text output system now also properly falls back to portable mode when UTF-8 is not being used, ensuring correct output in more environments.

Improved unit text output

The text representation of scaled units has been improved:

  • Scaled units are now enclosed in parentheses (...) instead of brackets [...]
  • The EQUIV{...} notation for common units has been replaced with simpler [...] brackets
constexpr Unit auto l_per_100km = si::litre / (mag<100> * si::kilo<si::metre>);
std::cout << 6.8 * l_per_100km << "\n";
std::cout << 1 * km + 1 * mi << "\n";
6.8 L/(100 km)
40771 [(1/25146 mi), (1/15625 km)]
6.8 L/[100 km]
40771 EQUIV{[1/25146 mi], [1/15625 km]}

Breaking changes to naming and organization

Several types and functions have been renamed or reorganized for consistency and clarity (💥 breaking changes 💥):

  • quantity_values → representation_values
  • Magnitude concept → UnitMagnitude
  • magnitude → detail::unit_magnitude (moved to implementation details)
  • MagConstant concept → detail::is_mag_constant variable trait
  • default_denominator → default_solidus in the unit_symbol_solidus enum
  • type_list has been moved to implementation details
  • unit_symbol and dimension_symbol now always return std::string_view instead of various string-like types
  • Representation concept has been removed as it was not providing sufficient value

Additionally:

  • format.h and ostream.h header files are now deprecated and their content is (almost always) available through other headers (e.g., mp-units/quantity.h, mp-units/systems/si.h, ...)
  • Custom representation type detection has been simplified - no need to specialize is_scalar, is_vector, and is_tensor customization points anymore

Examples and reusability improvements

  • The measurement.h header has been extracted from examples, making it easier to reuse measurement-related utilities in your own code
  • The clcpp_response and conversion_factor examples have been removed as they were based on outdated patterns

Documentation and tooling

This release includes substantial improvements to documentation and development infrastructure:

  • Comprehensive API Reference documentation by @JohelEGP
  • Highly extended examples section
  • Brand new tutorials section (try it out!)
  • New cheat sheet for quick reference
  • New CONTRIBUTORS.md
  • GitHub Codespaces support added (GitPod removed)
  • CI matrix generation for better test coverage (thanks @burnpanck)
  • GitHub issue templates for bug reports, documentation issues, feature requests, and usage experience
  • Pull request templates for better contribution workflow

Community and support

If you find mp-units valuable for your projects, please consider:

Your support helps ensure continued development and improvement of the library!

Comments