Skip to content

Working with Legacy Interfaces

This guide shows you how to safely interface mp-units with legacy code that doesn't use type-safe units. You'll learn the different functions for extracting numerical values, when to use each one, and how to maintain as much type safety as possible at the boundary between type-safe and legacy code.

For background on numerical values and value conversions, see Value Conversions in the User's Guide.

The Challenge

When working with legacy or unsafe interfaces, you need to extract the numerical value of a quantity and pass it to third-party APIs that don't understand type-safe units:

// Legacy function expects an integer representing speed in km/h
void legacy_check_speed_limit(int speed_in_km_per_h);

// How do we safely pass our quantity to this function?
quantity speed = 180 * km / (2 * h);  // 90 km/h

Simply accessing the raw value without specifying the unit would be dangerous—you might accidentally pass a value in the wrong unit. mp-units provides several member functions to safely bridge this gap while enforcing correctness.

Safe Value Extraction

The .numerical_value_in(Unit) Function

The primary way to extract a numerical value is using .numerical_value_in(Unit). This function requires you to explicitly specify the expected unit:

void legacy_check_speed_limit(double speed_in_km_per_h);

quantity speed = 180 * km / (2 * h);  // 90 km/h
legacy_check_speed_limit(speed.numerical_value_in(km / h));  // ✅ Passes 90

Key benefits:

  • Explicitly enforces the correct unit required by the interface
  • Automatically converts to the specified unit if needed
  • Won't compile in case of incompatible quantity kinds or value truncation
  • Significantly reduces safety-related issues
  • Makes code self-documenting about what unit the legacy function expects

Example of compile-time protection:

// ❌ Compile error: dimensionally incompatible
legacy_check_speed_limit(speed.numerical_value_in(m));

// ❌ Compile error: wrong dimensions
quantity distance = 100 * m;
legacy_check_speed_limit(distance.numerical_value_in(km / h));

Handling Value Truncation

The .numerical_value_in() function prevents value truncation to ensure safety. If the quantity's representation type is not value preserving and the unit conversion may truncate the value, you'll get a compile error:

// ❌ Compile error: value truncation
quantity speed = 30 * m / s;
legacy_check_speed_limit(speed.numerical_value_in(km / h));

Assignment/Construction Truncation Not Detected

The library cannot detect or prevent truncation that occurs when assigning or passing the returned value to an incompatible type. For example:

void legacy_function(int speed_in_km_per_h);  // Expects int

quantity<km / h, double> speed = 112.654 * km / h;
// ⚠️ Compiles but truncates: .numerical_value_in() returns double,
// which gets implicitly converted to int when passed to the function
legacy_function(speed.numerical_value_in(km / h));  // Passes 112

The library only protects against truncation within the quantity's own representation type conversions, not what happens to the value after it's extracted.

You have two options to resolve this:

Option 1: Use a value-preserving representation type

// ✅ Automatic conversion (floating-point types are value preserving)
quantity speed = 33. * m / s;   // 118.8 km/h
legacy_check_speed_limit(speed.numerical_value_in(km / h));

Option 2: Explicitly force truncation

When you're certain that truncation is acceptable, use .force_numerical_value_in(Unit):

quantity speed = 33 * m / s;   // 118.8 km/h
legacy_check_speed_limit(speed.force_numerical_value_in(km / h));  // ✅ Truncates to 118

Use .force_numerical_value_in() Sparingly

Only use this function when you've verified that truncation is acceptable for your use case. Document why truncation is safe to help future maintainers understand your reasoning.

Direct Storage Access

The .numerical_value_ref_in(Unit) Function

The functions above always return by value because unit conversion and value scaling may be required. When you need direct access to the underlying storage—for example, when a legacy function requires a pointer—use .numerical_value_ref_in(Unit):

void legacy_set_speed_limit(int* speed_in_km_per_h)
{
  *speed_in_km_per_h = 100;
}

quantity<km / h, int> speed_limit;
legacy_set_speed_limit(&speed_limit.numerical_value_ref_in(km / h));  // ✅ Direct access

Important constraints:

  • Still requires specifying the target unit for safety
  • Only works when no scaling is needed
  • The provided unit must have the same scaling factor as the quantity's current unit
  • Returns a reference to the internal storage

When it won't compile:

quantity<m / s, int> speed;
// ❌ Compile error: requires conversion from m/s to km/h
int& value = speed.numerical_value_ref_in(km / h);

Practical Examples

Example 1: Legacy API with Multiple Parameters

// Legacy function expecting separate value and unit code
void legacy_log_measurement(double value, int unit_code);

constexpr int UNIT_METERS = 1;
constexpr int UNIT_KILOMETERS = 2;

quantity distance = 1500. * m;

// Clear and explicit about units
legacy_log_measurement(distance.numerical_value_in(m), UNIT_METERS);
legacy_log_measurement(distance.numerical_value_in(km), UNIT_KILOMETERS);

Example 2: Filling Legacy Data Structures

struct LegacyMeasurement {
  double value_in_meters;
  double value_in_feet;
};

quantity distance = 10. * m;

LegacyMeasurement legacy_data{
  .value_in_meters = distance.numerical_value_in(m),
  .value_in_feet = distance.numerical_value_in(ft)
};

Example 3: Working with Output Parameters

void legacy_get_dimensions(int* width_mm, int* height_mm);

quantity<mm, int> width, height;
legacy_get_dimensions(
  &width.numerical_value_ref_in(mm),
  &height.numerical_value_ref_in(mm)
);

Summary

mp-units provides three functions for interfacing with legacy code:

Function Purpose Conversion Truncation Returns
.numerical_value_in(Unit) Safe value extraction Yes Prevented By value
.force_numerical_value_in(Unit) Value extraction with explicit truncation Yes Allowed By value
.numerical_value_ref_in(Unit) Direct storage access No N/A By reference

These functions maintain type safety at the boundary between type-safe and legacy code, helping prevent unit-related errors while still allowing interoperability with existing APIs.