Tutorial 3: Extracting Numeric Values for Legacy APIs¶
When working with mp-units, you'll often need to interface with legacy code or external APIs that expect raw numeric values in specific units. This tutorial shows how to safely extract numeric values from strongly-typed quantities while maintaining the benefits of compile-time unit checking.
We'll explore common scenarios where you need to "break out" of the type-safe world to communicate with legacy systems, and then safely "break back in" when processing their responses.
Problem statement¶
This tutorial demonstrates how to:
- Compute a physical quantity (speed) in one unit system (e.g., miles per hour),
- Convert and pass it as a numeric value in another unit (e.g., km/h) to a legacy API,
- Accept and convert values back from the legacy API as needed.
Your task¶
The legacy code below:
- Calculates speed in mph from distance in miles and duration in minutes.
- Converts and passes the speed as km/h to a legacy API that expects km/h.
- Accepts a new speed in km/h from the legacy API, converts it back to mph, and uses it in your code.
Refactor the code in the main()
function to use modern, strongly-typed quantities:
- Use
.numerical_value_in(Unit)
to extract values for the legacy API. - Use
.numerical_value_ref_in(Unit)
to provide a reference for legacy APIs that directly modify underlying values. - Keep extraction and conversion localized at API boundaries; keep the rest of the code strongly typed.
- Do not modify the legacy functions.
// ce-embed height=650 compiler=clang2110 flags="-std=c++23 -stdlib=libc++ -O3"
#include <mp-units/systems/si.h>
#include <mp-units/systems/international.h>
#include <iostream>
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
using namespace mp_units::international::unit_symbols;
#define MIN_TO_H(d) ((d) / 60.0) // Convert minutes to hours
#define MPH_TO_KMH(s) ((s) * 1.609344) // Convert mph to km/h
#define KMH_TO_MPH(s) ((s) / 1.609344) // Convert km/h to mph
// Simulate a legacy API that expects speed in km/h
bool legacy_check_speed_limit(double speed_kmh, double limit_kmh)
{
std::cout << "[legacy] speed: " << speed_kmh << " km/h\n";
std::cout << "[legacy] limit: " << limit_kmh << " km/h\n";
return speed_kmh <= limit_kmh;
}
// Simulate a legacy API that sets speed in km/h
void legacy_set_cruise_control_speed(double& speed_kmh)
{
speed_kmh = 90;
}
int main()
{
const double speed_limit_kmh = 90;
double distance_mi = 120;
double duration_min = 120;
double speed_mph = distance_mi / MIN_TO_H(duration_min);
if (!legacy_check_speed_limit(MPH_TO_KMH(speed_mph), speed_limit_kmh)) {
double new_speed_kmh;
legacy_set_cruise_control_speed(new_speed_kmh);
speed_mph = KMH_TO_MPH(new_speed_kmh);
std::cout << "Speed adjusted to " << speed_mph << " mi/h\n";
}
}
Solution
#include <mp-units/systems/si.h>
#include <mp-units/systems/international.h>
#include <iostream>
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
using namespace mp_units::international::unit_symbols;
// Simulate a legacy API that expects speed in km/h
bool legacy_check_speed_limit(double speed_kmh, double limit_kmh)
{
std::cout << "[legacy] speed: " << speed_kmh << " km/h\n";
std::cout << "[legacy] limit: " << limit_kmh << " km/h\n";
return speed_kmh <= limit_kmh;
}
// Simulate a legacy API that sets speed in km/h
void legacy_set_cruise_control_speed(double& speed_kmh)
{
speed_kmh = 90;
}
int main()
{
const quantity speed_limit = 90. * km / h;
quantity distance = 120. * mi;
quantity duration = 120. * min;
quantity speed = (distance / duration).in(mph);
if (!legacy_check_speed_limit(speed.numerical_value_in(km / h), speed_limit.numerical_value_in(km / h))) {
quantity<km / h> new_speed;
legacy_set_cruise_control_speed(new_speed.numerical_value_ref_in(km / h));
speed = new_speed.in(mph);
std::cout << "Speed adjusted to " << speed << '\n';
}
}
Takeaways¶
- Always extract and convert numeric values at API boundaries, not throughout your codebase.
- Use explicit conversion macros or functions to document and control unit changes.
- Keep your internal logic type-safe and unit-aware; only use raw numbers when required by external APIs.
- Comment your conversions and API boundaries for clarity and maintainability.