...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Safe Numerics |
A type is Numeric if it has the properties of a number.
More specifically, a type T is Numeric if there exists a
specialization of std::numeric_limits<T>
. See the
documentation for the standard library class numeric_limits
.
The standard library includes such specializations for all the built-in
numeric types. Note that this concept is distinct from the C++ standard
library type traits is_integral
and
is_arithmetic
. These latter fulfill the requirement of the
concept Numeric. But there may types which fulfill the concept of Numeric
for which std::is_arithmetic<T> == false
.
There are multiple reasons that we cannot use std::is_arithmetic<T> to identify number-like types for our purposes:
The main purpose of our concept of Numeric<T> is to indicate that the range of type T is available to be queried. Since all specializations of Numeric<T> have a member in std::numeric_limits<T> we have access to this information when needed through the members std::numeric_limits<T>::min() and std::numeric_limits<T>::max().
There are types which do not fulfill std::arithmetic<T> (that is are not built-in numeric types) but still fulfill the concept of Numeric<T> (number like "things").
The library creates new result types for every expression which should also fulfill this concept Numeric. But is_arithmetic<T> cannot be specialized for user types.
So is_arithmetic<T> does not correspond to the same set of types as Numeric<T> does.
T, U, V |
A type that is a model of a Numeric type |
t, u |
An object of a type modeling a Numeric type |
OS, IS |
A type that is a model of an output or input stream |
os, is |
An object of a type modeling output or input stream |
std::numeric_limits<T> |
The numeric_limits class template provides a C++ program with information about various properties of the implementation's representation of the arithmetic types. See C++ standard 18.3.2.2. |
In addition to the expressions defined in Assignable the following expressions must be valid. In the safe_numerics library, a type is considered Numeric if and only if it it has an entry in the std::numeric_limits table with the following members. Note that this is different from the the definition of std::is_arithmetic in that the later is required to support all valid expressions which Numeric does not require support for all these expressions but only requires that they be correctly implemented if they are defined. Also is_arithmetic is only defined for built in numeric types while Numeric applies to any user types which "look like" a number.
Table 1. General
Expression | Return Type | Return Value |
---|---|---|
Numeric<T> |
true_type |
|
Numeric<T>() |
bool |
true |
std::numeric_limits<T>::is_specialized |
bool |
true |
std::numeric_limits<T>::is_integer |
bool |
true or false
|
std::numeric_limits<T>::is_signed |
bool |
true or false
|
Any or all of the following unary operators MAY be defined. Any such defined operators shall implement the semantics as described below
Table 2. Unary Operators
Expression | Return Type | Semantics |
---|---|---|
-t |
T |
Invert sign |
+t |
T |
unary plus - a no op |
t-- |
T |
post decrement |
t++ |
T |
post increment |
--t |
T |
pre decrement |
++t |
T |
pre increment |
Any or all of the following binary operators MAY be defined. Any defined operators shall implement the semantics as described bellow
Table 3. Binary Operators
Expression | Return Type | Semantics |
---|---|---|
t - u |
V |
subtract u from t |
t + u |
V |
add u to t |
t * u |
V |
multiply t by u |
t / u |
T |
divide t by u |
t < u |
bool |
true if t less than u, false
otherwise |
t <= u |
bool |
true if t less than or equal to u,
false otherwise |
t > u |
bool |
true if t greater than u, false
otherwise |
t >= u |
bool |
true if t greater than or equal to u,
false otherwise |
t == u |
bool |
true if t equal to u, false
otherwise |
t != u |
bool |
true if t not equal to u, false
otherwise |
t = u |
|
assign value of u to t |
t += u |
|
add u to t and assign to t |
t -= u |
|
subtract u from t and assign to t |
t *= u |
|
multiply t by u and assign to t |
t /= u |
|
divide t by u and assign to t |
os << t |
|
write contents of t to output stream |
is >> t |
|
read contents of an input stream into t |
This in turn raises another question: Is it "legal" to specialize
std::numeric_limits
for one's own types such as
safe<int>
. In my view the standard is ambiguous on
this. See various interpretations:
In any case, it seems pretty clear that no harm will come of it. In spite of the consideration given to this issue, it turns out that the found no real need to implement these predicates. For example, there is no "is_numeric<T>" implemented as part of the safe numerics library. This may change in the future though. Even if not used, defining and maintaining these type requirements in this document has been very valuable in keeping the concepts and code more unified and understandable.
Remember that above considerations apply to other numeric types used in this library even though we don't explicitly repeat this information for every case.