Safe and Unsafe Conversions¶
Learn about truncation, forcing conversions, and system-specific units.
Goal: Handle unsafe conversions and understand different unit systems
Time: ~15 minutes
Safe vs Unsafe Conversions¶
mp-units allows implicit conversions when safe (no truncation), but blocks dangerous ones:
// ce-embed height=500 compiler=clang2110 flags="-std=c++23 -stdlib=libc++ -O3" mp-units=trunk
#include <mp-units/systems/si.h>
#include <iostream>
int main()
{
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
// These work (no truncation):
quantity<si::metre, double> meters = 5.0 * km; // ✅ 5000.0 m - safe
quantity<si::metre, int> meters2 = 5 * km; // ✅ 5000 m - safe
std::cout << "Safe conversions:\n";
std::cout << " " << meters << "\n";
std::cout << " " << meters2 << "\n";
// This won't compile (truncation risk):
// quantity<si::kilo<si::metre>, int> km_bad = 500 * m; // ❌ 0.5 km → 0
// Try it! Uncomment the line above to see the compiler error message.
}
Key insight: The library prevents implicit conversions that could lose data through truncation. Converting to a larger unit with integer representation (500 m → 0 km) requires explicit syntax to acknowledge the data loss.
Why? Converting 500 m to km with int representation would truncate 0.5 → 0, losing data!
Floating-Point Types Are Value-Preserving¶
mp-units follows std::chrono::duration logic: floating-point types are considered
value-preserving:
Default representation type
When you explicitly specify a unit without a representation type (e.g., quantity<si::metre>),
it defaults to double. But when using CTAD (e.g., quantity distance = 5.0 * km), the
representation type is deduced from the expression.
// ce-embed height=550 compiler=clang2110 flags="-std=c++23 -stdlib=libc++ -O3" mp-units=trunk
#include <mp-units/systems/si.h>
#include <iostream>
int main()
{
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
// Integer → floating-point: safe (value-preserving)
quantity<si::metre, int> int_meters = 1500 * m;
quantity<si::kilo<si::metre>, double> km_value = int_meters; // ✅ 1.5 km
std::cout << "Int to double: " << int_meters << " → " << km_value << "\n";
// Floating-point → integer: NOT safe (truncation risk)
quantity<si::kilo<si::metre>, double> km_double = 2.7 * km;
// quantity<si::metre, int> meters_int = km_double; // ❌ Won't compile!
// quantity<si::kilo<si::metre>, int> km_int = km_double; // ❌ Also won't compile!
std::cout << "\nDouble to int: blocked by compiler (even without unit change!)\n";
}
Key insight: Following std::chrono::duration logic, floating-point types are considered
value-preserving (int → double is safe), but any conversion to an integer type risks
truncation and is blocked - even without changing units!
Customization points
You can customize this behavior with:
treat_as_floating_point<Rep>: Tells the library if a type should be treated as floating-pointis_value_preserving<From, To>: Determines if a conversion preserves values
By default, mp-units uses std::chrono::duration-like logic for these.
Forcing Truncating Conversions¶
When you need to truncate, make it explicit:
// ce-embed height=550 compiler=clang2110 flags="-std=c++23 -stdlib=libc++ -O3" mp-units=trunk
#include <mp-units/systems/si.h>
#include <iostream>
int main()
{
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
// Three ways to force truncating conversions:
quantity km_int = (500 * m).force_in(km); // ✅ 0 km (truncates to int)
quantity km_dbl = (500 * m).in<double>(km); // ✅ 0.5 km (changes to double)
quantity km_cast = value_cast<double, km>(500 * m); // ✅ 0.5 km (using value_cast)
std::cout << "Forced conversions:\n";
std::cout << " force_in(km): " << km_int << "\n";
std::cout << " in<double>(km): " << km_dbl << "\n";
std::cout << " value_cast: " << km_cast << "\n";
}
Key insight: Explicit syntax prevents accidental data loss!
Challenges¶
- Force truncation: Create 1234 m as
int, force convert to km (should get 1 km) - Safe with doubles: Create 1234 m as
double, convert to km (should get 1.234 km implicitly) - Mixed representations: Add 5.5 km (double) + 2000 m (int), what's the result type?
What You Learned?¶
✅ Implicit conversions only work when safe (no truncation)
✅ Floating-point types are considered value-preserving
✅ Use .force_in(), .in<Rep>(), or value_cast to force truncation
✅ The library follows std::chrono::duration logic for safety
✅ Explicit syntax prevents accidental data loss