Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

Using safe_range and safe_literal
PrevUpHomeNext

When trying to avoid arithmetic errors of the above type, programmers will select data types which are wide enough to hold values large enough to be certain that results won't overflow, but are not so large as to make the program needlessly inefficient. In the example below, we presume we know that the values we want to work with fall in the range [-24,82]. So we "know" the program will always result in a correct result. But since we trust no one, and since the program could change and the expressions be replaced with other ones, we'll still use the loose_trap_policy exception policy to verify at compile time that what we "know" to be true is in fact true.

//  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 <iostream>

#include <boost/safe_numerics/safe_integer_range.hpp>
#include <boost/safe_numerics/safe_integer_literal.hpp>
#include <boost/safe_numerics/exception.hpp>
#include <boost/safe_numerics/native.hpp>
#include "safe_format.hpp" // prints out range and value of any type

using namespace boost::safe_numerics;

// create a type for holding small integers in a specific range
using safe_t = safe_signed_range<
    -24,
    82,
    native,           // C++ type promotion rules work OK for this example
    loose_trap_policy // catch problems at compile time
>;

// create a type to hold one specific value
template<int I>
using const_safe_t = safe_signed_literal<I, native, loose_trap_policy>;

// We "know" that C++ type promotion rules will work such that
// addition will never overflow. If we change the program to break this,
// the usage of the loose_trap_policy promotion policy will prevent compilation.
int main(int, const char *[]){
    std::cout << "example 83:\n";

    constexpr const const_safe_t<10> x;
    std::cout << "x = " << safe_format(x) << std::endl;
    constexpr const const_safe_t<67> y;
    std::cout << "y = " << safe_format(y) << std::endl;
    auto zx = x + y;
    const safe_t z = zx;
    //auto z = x + y;
    std::cout << "x + y = " << safe_format(x + y) << std::endl;
    std::cout << "z = " << safe_format(z) << std::endl;
    return 0;
}
  • safe_signed_range defines a type which is limited to the indicated range. Out of range assignments will be detected at compile time if possible (as in this case) or at run time if necessary.

  • A safe range could be defined with the same minimum and maximum value effectively restricting the type to holding one specific value. This is what safe_signed_literal does.

  • Defining constants with safe_signed_literal enables the library to correctly anticipate the correct range of the results of arithmetic expressions at compile time.

  • The usage of loose_trap_policy will mean that any assignment to z which could be outside its legal range will result in a compile time error.

  • All safe integer operations are implemented as constant expressions. The usage of constexpr will guarantee that z will be available at compile time for any subsequent use.

  • So if this program compiles, it's guaranteed to return a valid result.

The output uses a custom output manipulator, safe_format, for safe types to display the underlying type and its range as well as current value. This program produces the following run time output.

example 83:
x = [10,10] = 10
y = [67,67] = 67
x + y = [77,77] = 77
z = [-24,82] = 77

Take note of the various variable types:

  • x and y are safe types with fixed ranges which encompass one single value. They can hold only that value which they have been assigned at compile time.

  • The sum x + y can also be determined at compile time.

  • The type of z is defined so that It can hold only values in the closed range -24,82. We can assign the sum of x + y because it is in the range that z is guaranteed to hold. If the sum could not be be guaranteed to fall in the range of z, we would get a compile time error due to the fact we are using the loose_trap_policy exception policy.

All this information regarding the range and values of variables has been determined at compile time. There is no runtime overhead. The usage of safe types does not alter the calculations or results in anyway. So safe_t and const_safe_t could be redefined to int and const int respectively and the program would operate identically - although it might We could compile the program for another machine - as is common when building embedded systems and know (assuming the target machine architecture was the same as our native one) that no erroneous results would ever be produced.

PrevUpHomeNext