Skip to content

Ensure Ultimate Safety

This guide shows how to combine mp-units safety features to get guaranteed, always-on constraint enforcement for your quantities — independent of build mode or compiler optimization level.

Background reading

For in-depth background on how quantity_bounds and overflow policies work, see Range-Validated Quantity Points.

The Problem

By default, the library's bounds checking for quantity points uses MP_UNITS_EXPECTS, which may be compiled away in release builds. This is fine for catching logic errors during development, but some domains need guaranteed enforcement:

  • Safety-critical systems (aviation, medical, automotive)
  • Input validation at system boundaries
  • Financial calculations with strict ranges

The Solution

The library provides building blocks that work together:

  1. quantity_bounds<Origin> — a customization point that attaches a validation policy to a quantity point origin
  2. constrained<T, ErrorPolicy> — a transparent wrapper that tags a representation type with an error policy
  3. constraint_violation_handler<Rep> — a customization point that library features query to dispatch errors

The library ships several overflow policies to assign to quantity_bounds: check_in_range, clamp_to_range, wrap_to_range, and reflect_in_range. For guaranteed enforcement, this guide uses check_in_range, which delegates to the constraint_violation_handler when one is available for the representation type.

Step 1: Choose an Error Policy

The library ships two error policies in <mp-units/constrained.h>:

Policy Availability Behavior
throw_policy Hosted only Throws std::domain_error
terminate_policy Always Calls std::abort()

You can also write your own:

struct log_and_continue_policy {
  static void on_constraint_violation(std::string_view msg) {
    spdlog::error("Constraint violation: {}", msg);
  }
};

Step 2: Use constrained<T, Policy> as Your Representation Type

Wrap your numeric type with the desired policy:

#include <mp-units/constrained.h>

using safe_double = mp_units::constrained<double, mp_units::throw_policy>;

constrained<T> is transparent: it implicitly converts to/from T and forwards all arithmetic. But it carries the error policy as a compile-time tag that the library can detect.

The library automatically registers a constraint_violation_handler for every constrained<T, EP> instantiation, forwarding violations to EP::on_constraint_violation().

Step 3: Attach Bounds to Your Origin

Specialize quantity_bounds for your origin with an appropriate overflow policy. For guaranteed enforcement, use check_in_range:

#include <mp-units/overflow_policies.h>
#include <mp-units/framework.h>
#include <mp-units/systems/si.h>

using namespace mp_units;
using namespace mp_units::si::unit_symbols;

inline constexpr struct geo_latitude final : quantity_spec<isq::angular_measure> {} geo_latitude;
inline constexpr struct equator final : absolute_point_origin<geo_latitude> {} equator;

template<>
inline constexpr auto mp_units::quantity_bounds<equator> = check_in_range{-90 * deg, 90 * deg};

Step 4: Combine Them

Use constrained<T> as the representation type in your quantity point:

using latitude = quantity_point<geo_latitude[deg], equator, safe_double>;

void process_coordinates(double raw_lat)
{
  try {
    latitude lat{raw_lat * deg, equator};  // throws if |raw_lat| > 90
    // ... use lat safely ...
  } catch (const std::domain_error& e) {
    // handle invalid input
  }
}

When check_in_range detects an out-of-bounds value, it finds the constraint_violation_handler<constrained<double, throw_policy>> specialization and calls throw_policy::on_constraint_violation("value out of bounds"), which throws std::domain_error. This happens in every build mode — debug, release, or release-with-debug-info.

Extensible policy interface

The quantity_bounds customization point accepts any callable policy with the signature V operator()(V). The library ships six built-in policies — check_in_range, clamp_to_range, wrap_to_range, reflect_in_range, check_non_negative, and clamp_non_negative — and the interface is fully extensible. check_non_negative and clamp_non_negative cover the common [0, +∞) halfline case; they are also applied automatically to natural origins of non-negative ISQ quantity specs. For a fully custom one-sided or asymmetric policy, see Custom Policies.

How It Works?

flowchart TB
    A["quantity_point<br>construction / mutation"] --> B["quantity_bounds&lt;Origin&gt;::operator()"]
    B --> D{"Policy type?"}
    D -- "check_in_range" --> E{"Has constraint_<br>violation_handler<br>for Rep?"}
    E -- "Yes<br>(e.g. constrained&lt;T&gt;)" --> F["handler::on_violation()"]
    E -- "No<br>(plain rep)" --> G["MP_UNITS_EXPECTS<br>(may be no-op)"]
    F --> H["ErrorPolicy::<br>on_constraint_violation()"]
    D -- "clamp / wrap / reflect" --> I["Value transformed<br>(always active)"]

Bringing Your Own Handler

You don't have to use constrained<T> — any representation type can opt in by specializing constraint_violation_handler:

// Your custom safe-double type
class my_safe_double { /* ... */ };

template<>
struct mp_units::constraint_violation_handler<my_safe_double> {
  static void on_violation(std::string_view msg)
  {
    throw std::domain_error(std::string(msg));
  }
};

Now any quantity_bounds policy that queries constraint_violation_handler (such as check_in_range) will use your handler whenever a quantity_point with my_safe_double rep goes out of bounds.

Combining with safe_int

For integer quantities, the library provides safe_int<T> in <mp-units/safe_int.h>, which detects arithmetic overflow. You can use both together:

#include <mp-units/safe_int.h>

// Overflow-detecting integer for arithmetic operations
quantity distance = safe_int{42} * m;  // arithmetic overflow detected at runtime

// Bounded quantity point with overflow-detecting rep
using altitude = quantity_point<isq::height[m], msl_origin, safe_int<int>>;

safe_int also specializes constraint_violation_handler, so check_in_range will use its error policy for bounds violations.

See Also