Library Documentation Index

Safe Numerics

PrevUpHomeNext

safe<T, PP, EP>

Description
Notation
Associated Types
Template Parameters
Model of
Valid Expressions
Examples of use
Header

Description

A safe<T, PP , EP> can be used anywhere a type T can be used. Any expression which uses this type is guaranteed to return an arithmetically correct value or to trap in some way.

Notation

Symbol Description
T Underlying type from which a safe type is being derived

Associated Types

PP Promotion Policy. A type which specifies the result type of an expression using safe types.
EP Exception Policy. A type containing members which are called when a correct result cannot be returned

Template Parameters

Parameter Type Requirements Description
T std::is_integer<T>

The underlying type. Currently only built-in integer types are supported

PP PromotionPolicy<PP>

Optional promotion policy. Default value is boost::numeric::native

EP Exception Policy<EP>

Optional exception policy. Default value is boost::numeric::default_exception_policy

See examples below.

Model of

Numeric

Integer

Valid Expressions

Implements all expressions and only those expressions supported by the base type T. Note that all these expressions are constexpr. The result type of such an expression will be another safe type. The actual type of the result of such an expression will depend upon the specific promotion policy template parameter.

When a binary operand is applied to two instances of safe<T, PP, EP>one of the following must be true:

  • The promotion policies of the two operands must be the same or one of them must be void

  • The exception policies of the two operands must be the same or one of them must be void

If either of the above is not true, a compile error will result.

Examples of use

The most common usage would be safe<T> which uses the default promotion and exception policies. This type is meant to be a "drop-in" replacement of the intrinsic integer types. That is, expressions involving these types will be evaluated into result types which reflect the standard rules for evaluation of C++ expressions. Should it occur that such evaluation cannot return a correct result, an exception will be thrown.

There are two aspects of the operation of this type which can be customized with a policy. The first is the result type of an arithmetic operation. C++ defines the rules which define this result type in terms of the constituent types of the operation. Here we refer to these rules as "type promotion" rules. These rules will sometimes result in a type which cannot hold the actual arithmetic result of the operation. This is the main motivation for making this library in the first place. One way to deal with this problem is to substitute our own type promotion rules for the C++ ones.

As a Drop-in replacement for standard integer types.

The following program will throw an exception and emit an error message at runtime if any of several events result in an incorrect arithmetic result. Behavior of this program could vary according to the machine architecture in question.

#include <exception>
#include <iostream>
#include <safe_integer.hpp>

void f(){
    using namespace boost::safe_numerics;
    safe<int> j;
    try {
        safe<int> i;
        std::cin >> i;  // could overflow !
        j = i * i;      // could overflow
    }
    catch(std::exception & e){
       std::cout << e.what() << std::endl;
    }
    std::cout << j;
}

The term "drop-in replacement" reveals the aspiration of this library. In most cases, this aspiration is realized. In the following example, the normal implicit conversions function the same for safe integers as they do for built-in integers.

//  Copyright (c) 2018 Robert Ramey
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <boost/safe_numerics/safe_integer.hpp>
using namespace boost::safe_numerics;

int f(int i){
    return i;
}

using safe_t = safe<long>;

int main(){
    const long x = 97;
    f(x);   // OK - implicit conversion to int
    const safe_t y = 97;
    f(y);   // Also OK - checked implicit conversion to int
    return 0;
}

When the safe<long> is implicitly converted to an int when calling f, the value is checked to be sure that it is within the legal range of an int and will invoke an exception if it cannot. We can easily verify this by altering the exception handling policy in the above example to loose_trap_policy. This will invoke a compile time error on any conversion might invoke a runtime exception.

//  Copyright (c) 2018 Robert Ramey
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <boost/safe_numerics/safe_integer.hpp>
#include <cstdint> // uint8_t
using namespace boost::safe_numerics;

uint8_t f(uint8_t i){
    return i;
}

using safe_t = safe<long, native, loose_trap_policy>;

int main(){
    const long x = 97;
    f(x);   // OK - implicit conversion to int can never fail
    const safe_t y = 97;
    f(y);   // could overflow so trap at compile time
    return 0;
}

But this raises it's own questions. We can see that in this example, the program can never fail:

  • The value 97 is assigned to y

  • y is converted to an int

  • and used as an argument to f

The conversion can never fail because the value of 97 can always fit into an int. But the library code can't detect this and emits the checking code even though it's not necessary.

This can be addressed by using a safe_literal. A safe literal can contain one and only one value. All the functions in this library are marked constexpr. So it can be determined at compile time that conversion to an int can never fail and no runtime checking code need be emitted. Making this small change will permit the above example to run with zero runtime overhead while guaranteeing that no error can ever occur.

//  Copyright (c) 2018 Robert Ramey
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <boost/safe_numerics/safe_integer.hpp>
#include <boost/safe_numerics/safe_integer_literal.hpp>

using namespace boost::safe_numerics;

int f(int i){
    return i;
}

template<intmax_t N>
using safe_literal = safe_signed_literal<N, native, loose_trap_policy>;

int main(){
    const long x = 97;
    f(x);   // OK - implicit conversion to int
    const safe_literal<97> y;
    f(y);   // OK - y is a type with min/max = 97;
    return 0;
}

With this trivial example, such efforts would hardly be deemed necessary. But in a more complex case, perhaps including compile time arithmetic expressions, it could be much more difficult to verify that the constant is valid and/or no checking code is needed. And there is also possibility that over the life time of the application, the compile time constants might change, thus rendering any ad hoc analyse obsolete. Using safe_literal will future-proof your code against well-meaning, but code-breaking updates.

Adjust type promotion rules.

Another way to avoid arithmetic errors like overflow is to promote types to larger sizes before doing the arithmetic.

Stepping back, we can see that many of the cases of invalid arithmetic wouldn't exist if the result types were larger. So we can avoid these problems by replacing the C++ type promotion rules for expressions with our own rules. This can be done by specifying a promotion policy automatic. The policy stores the result of an expression in the smallest size type that can accommodate the largest value that an expression can yield. No checking for exceptions is necessary. The following example illustrates this.

#include <boost/safe_numerics/safe_integer.hpp>
#include <iostream>

int main(int, char[]){
    using safe_int = safe<
        int, boost::numeric::automatic, 
        boost::numeric::default_exception_policy
    >; 
    safe_int i;
    std::cin >> i; // might throw exception
    auto j = i * i; // won't ever trap - result type can hold the maximum value of i * i
    static_assert(boost::numeric::is_safe<decltype(j)>::value); // result is another safe type
    static_assert(
        std::numeric_limits<decltype(i * i)>::max() >=
        std::numeric_limits<safe_int>::max() * std::numeric_limits<safe_int>::max()
    ); // always true

    return 0;
}

Header

#include <boost/safe_numerics/safe_integer.hpp>


PrevUpHomeNext