Concept Subspace :: sus :: construct :: Cast

template <class To, class From>
concept Cast
requires
requires(const From& from) {
  {
    ::sus::construct::CastImpl<
        std::remove_const_t<To>, std::remove_const_t<From>>::cast_from(from)
  } noexcept -> std::same_as<std::remove_const_t<To>>;
}

When a pair of types T and F satisfy Cast<T, F>, it means that F can be cast to T through a conversion that will always succeed in producing some value, but may be lossy or produce a value with a different meaning. The conversion may truncate or extend F in order to do the conversion to T.

This operation is commonly known as type casting or type coercion. The conversion to T can be done by calling sus::cast<T>(from).

The conversion is defined for the identity conversion where both the input and output are the same type, if the type is Copy, in which case the input is copied and returned. As casting is meant to be a cheap conversion, primarily for moving between primitive types, it does not support Clone types, and Into should be used in more complex cases.

Casting numeric types

For numeric and primitive types, Cast is defined to provide a mechanism like static_cast<T> but it is much safer than static_cast<T> as it has defined behaviour for all inputs:

  • Casting from a float to an integer will perform a static_cast, which rounds the float towards zero, except:
    • NAN will return 0.
    • Values larger than the maximum integer value, including f32::INFINITY, will saturate to the maximum value of the integer type.
    • Values smaller than the minimum integer value, including f32::NEG_INFINITY, will saturate to the minimum value of the integer type.
  • Casting from an integer to a float will perform a static_cast, which converts to the nearest floating point value. The rounding direction for values that land between representable floating point values is implementation defined (per C++20 Section 7.3.10).
  • Casting from an f32 (or float) to an f64 (or double) preserves the value unchanged.
  • Casting from an f64 (or double) to an f32 (or float) performs the same action as a static_cast if the value is in range for f32, otherwise:
  • Casting to and from std::byte produces the same values as casting to and from u8.

These conversions are all defined in sus/num/types.h.

Extending to other types

Types can participate in defining their Cast strategy by providing a specialization of sus::convert::CastImpl<To, From>. The conversions should always produce a value of type T, should not panic, and should not cause Undefined Behaviour.

The CastImpl specialization needs a (typically constexpr) static method cast_from that receives const From& and returns To.