An out-of-range conversion occurs when a source value cannot be presented in a destination type, not even approximately between 2 adjacent destination values.
If the destination type is unsigned, the conversion, even if logically out-of-range, if always defined (4.7.2) as the least congruent unsigned value.
If the destination type is a signed integer, the result of an out-of-range conversion is implementation-defined (4.7.3).
If the destination type is floating-point, or the source type is floating-point and the destination type integer, an out-of-range conversion is undefined-behavior (4.8.1,4.9.1).
Clearly, out-of-range conversions are a problem because they can involve from implementation-defined to undefined behavior. Even signed to unsigned conversion can be a problem because the resulting value, even if well defined, can be as wrong as any arbitrary unrelated value.
Numerical applications, ranging from sophisticated scientific software to modeling to accounting to simple number manipulation can rarely disregard the problems associated with out-of-range conversions. In consequence, most such applications incorporate ad-hoc out-of-range checking code and protocols.
Furthermore, most C++ users are unaware of these rules. For instance, most C++ programmers are unaware that an out-of-range floating point conversion is undefined behavior (...and can for instance cause the client's hard disk to be wiped out entirely, as the say goes)
Generic Programming came into C++ along with the need for a numerical conversion utility providing well-defined behavior for all generic conversion cases (all combinations of source and destination types). So much in fact, that the Boost project launched, 6 years ago, with such an utility as part of its initial offering.
boost::numeric_cast<> or any other in-house facility, most
numerical applications have been using range-checked numeric conversions from
a long time.
But there is a problem: properly determining whether a source value is in
range for a given destination type is tricky because involves comparing a
value of a type with the boundary values of a different type, task that itself
requires a numeric conversion. This is in fact so difficult that
boost::numeric_cast<> had it wrong for certain cases for years, even after
a number of revisions.
numeric_cast<> is therefore a perfect candidate for
it is widely needed but its implementation much too difficult to ask users to
roll their own (or need one from a non standard library)
The Boost Numeric Conversion Library introduced
a re-implementation of
boost::numeric_cast<> which using metaprogramming fixed all
the range-checking problems of the initial version and even gained
compile-time optimizations by totally avoiding the range-check when
That library, which serves as a reference implementation, developed the right concepts that are needed to formulate a proper range-checking logic for any combination of types (that is, a formulation that prevents the conversions needed by the check itself to be out-of-range). It also shown that template-metaprogramming can be used to optimize away the range-checking when unneeded.
The Boost library uses a policy-based design that offers users a wide
latitude of choices (user-defined-types support, custom out-of-range handling,
float to integer rounders, etc), but the facility proposed in this paper for
standardization is a simplified version more in the spirit of the original
numeric_cast<T>(s) shall give the same result as
static_cast<T>(s) (that is, the conversion itself is a just standard
conversion) but with an added out-of-range check which implementations are
required to bypass if unneeded (the proposed text defines exactly when is this
the case). The out-of-range check is itself performed separately by
another proposed function:
bool is_out_of_range<T>(s) which users can call if they
need a domain-specific out-of-range response. If
numeric_cast<T>(s) detects an
out of range condition, an out-of-range installable handler is called.
The proposed text is intended to allow upcoming numeric types like
decimal types, big integers, rationals, etc to be usable with the facility.
To that effect, the proposed text includes some definitions which encompasses these
eventually-standard types. Additionally, the proposed text intends to allow a
C++ program to extend the standard
numeric_cast<> for user-defined-types.
Nonetheless, even though the facility must interact with other pieces of the standard, the proposal takes the form of a pure extension. There are only additions.
The fundamental need that this proposal aims to satisfy is providing range-checked standard conversions because range checking is a general need in a wide range of application domains. But there are other needs: for example, in converting a floating-point type to an integer type it might be critical to be able to control how the fractional part is handled (instead of simply discarding it as a standard conversion does), or maybe an application needs to trap inexact (but in-range) conversions (when the source value is represented as one of the two adjacent values in the destination type); or "fix" out of range conversions by choosing the closest boundary value in the destination type. Yet from all this features, range checking is the only one that doesn't need to redefine the conversion itself because it can be provided as a precondition to a standard conversion.
It is certainly possible to design a facility to give users further control over the conversion process. To some extent, C99 and the Boost Numeric Conversion Library do that. However, the pieces that would be directly involved and affected are yet to be standardized (additions from C99, additional numeric types, like decimal, integer, rational, etc..) so is more convenient to take it one step at a time and standardize at this point only the simplest solution to the more general problem: range checking.
A policy-based design like that used in the Boost Numeric Conversion library is certainly a reasonable way to give users the extra latitude, but such designs has as a drawback that the resulting behavior is on the hands of the users. For such a fundamental part of a language as its standard library, just carefully specifying the requirements and expected behavior for the policies is simply not enough. There is no discussion about the potential and usefulness of policies, but the actual instrumentation of such designs needs tools from the language, like concepts and design-by-contract forms, which are not yet available (but these have been already formally proposed). Thus, the policy-based design of the Boost version has been dropped in favor of a simpler but better defined facility.
The canonical way to treat exceptional errors in C++ is by throwing an exception. However, numeric conversions are specially important in numeric applications and numeric applications are particularly common in restricted platforms like embedded systems. In consequence, always throwing an exception in the presence of an out-of-range conversion is unlikely to be welcome by the numerical community which is intended to be a primary target. Thus, the proposed solution uses an installable handler (like the new handler) so that implementations and programs can customize the out-of-range response to suit the target platform.
At this point in the C++ standard life-cycle, a number of numeric types are
being proposed. This proposal assumes that at least some of these types will
be accepted so it tries to cover the conversions involving not only
arithmetic types but these eventual standard numeric types as well.
Since the proposed
numeric_cast<>() only adds range checking but doesn't
define the conversion itself, the proposal only has to properly cover the
range checking logic for any type it intends to specify behavior. To achieve
that, the specification of the range checking logic that implementations must
follow is formulated with the assumption that the implementation has complete
access to the details of these standard numeric types. In particular,
the implementation is required to be able to test whether a boundary value of
any given standard numeric type is representable in any other given standard numeric type.
Furthermore, the proposal adds a simple provision to allow C++ programs to
make their user-defined-types be usable with
numeric_cast<>: they are allowed to
provide additional specializations, even partial (assuming the language eventually allows that), of the template function.
A. Add the following to 18.104.22.168 [lib.handler.functions]
B. Add the following to the synopsis for <numeric> at 26.4, [lib.numeric.ops]
template<class T, class S> bool is_out_of_range ( S s ) throw() ;
template<class T, class S> T numeric_cast ( S s )
throw ( range_error );
C. Add the following subclauses:
any type which satisfies the numeric type requirements (26.1)
[Note: a user defined type can fit this definition -end note]
any numeric type (17.1.21) defined in this standard, including library provided types such as complex<>
any conversion, standard or user defined, involving numeric types (17.1.21)
any numeric conversion (17.1.23) involving standard numeric types (17.1.22)
standard numeric conversions from any value of a standard numeric type (17.1.2) S to a
value of a standard numeric
type T such that either numeric_limits<T>::is_bounded==false, or both
can be represented in the type T, either exactly or between two adjacent T
values. That is, conversions for which any value of the source type can be
represented in the destination type.
standard numeric conversions from any value of a standard numeric type (17.1.2) S to a
value of a standard numeric
type T such that mumeric_limits<T>::is_bounded==true and
cannot be represented in the type T, neither exactly nor between two adjacent T
values. That is, conversions for which some values of the source type cannot
be represented in the destination type.
If conversions from S to T are sub-ranged, then necessarily, conversions in the opposite direction, T->S, are super-ranged. The converse is not necessarily true (for example, a conversion from N->N is super-ranged in both directions)
typedef void (*out_of_range_handler)();
1. The type of the handler function called by numeric_cast<> (26.4.5) when an out of range is detected
2. Required behavior: an out_of_range_handler shall perform one of the following::
- throw an exception of type range_error (19.1.7) or a class derived from range_error
- call either abort() or exit()
3. An out of range handler installed by a program or the implementation is allowed to perform additional operations, with side effects, prior to the required operation, provided they do not incur undefined behavior.
[Note: allowing additional operations is intended to let a C++ program properly register and report the incident; not to attempt to recover from the error -endnote]
out_of_range_handler set_out_of_range_handler( out_of_range_handler new_h ) throw() ;
1. Effects: Establishes the function designated by new_h as the current out_of_range_handler.
2. Returns: 0 on first call, the previous out_of_range_handler on subsequent calls.
template<class T, class S> bool is_out_of_range ( Source s ) throw() ;
A. If conversions from S to T are super-ranged (17.1.22), false for any value of s [Note: this case can only be applied if both T and S are standard numeric types (17.1.22), per definition of super-ranged -end note]
B. If conversions from S to T are sub-ranged (17.1.23); the result of evaluating this expression:
(s >= static_cast<S>(numeric_limits<T>::lowest())1
&& (s <= static_cast<S>(numeric_limits<T>::max ()).
[Note: this case can only be applied if both S and T are standard numeric types (17.1.22), per definition of sub-ranged -end note]
[Note: when S->T are sub-ranged; T->S are super-ranged so the expression can be evaluated without incurring undefined behavior]
C.If either S or t are not standard numeric types (17.1.22), an unspecified result.
2. A C++ program is allowed to defined specializations of this template function provided that S or T or both are user-defined-types. If both S and T are standard numeric type (17.1.22) the specializations ill-formed and the implementation is required to issue diagnostic.
template<class T, class S> T numeric_cast( S s ) throw (
1. The result of the expression
is the same as the result of the expression
static_cast<T>(s) with the
following added restrictions and side-effects:
2. If either S or T are not standard numeric types (17.1.22), the behavior is unspecified if there exist in the program a user-provided specialization of the function for these types and undefined if such a user-provided specialization does not exist.
4. If conversions from S to T are sub-ranged (17.1.23), is_out_of_range<T>(s) shall be called and if the result of that is true, the installed out_of_range-handler shall be invoked, if any.
5. If conversions from S to T are super-ranged (17.1.22), is_out_of_range<T>(s) shall not be called and numeric_cast<T>(s) shall be distinguishable from static_cast<T>(s) only in the additional type requirements. That is, implementations are required to skip the overhead of range checking for super-ranged conversions.
6. A C++ program is allowed to defined specializations of this template function provided that S or T or both are user-defined-types. If both S and T are standard numeric type (17.1.22) the specialization is ill-formed and the implementation is required to issue a diagnostic.
7. The implementation is allowed to implement
numeric_cast<> as an internal operator and not as a explicit library function
provided it can diagnose a violation to subclause 6 in case a C++ programs
All the people in the boost community, particularly those involved in the development of the Boost.NumericConversion library.
Thorsten Ottosen for his help preparing this proposal.