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

This is the documentation for a snapshot of the develop branch, built from commit 0f79ae966a.
Library Documentation Index

Safe Numerics

PrevUpHomeNext

Implicit Conversions Can Lead to Erroneous Results

At CPPCon 2016 Jon Kalb gave a very entertaining (and disturbing) lightning talk related to C++ expressions.

The talk included a very, very simple example similar to the following:

//  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.hpp>

int main(){
    std::cout << "example 4: ";
    std::cout << "implicit conversions change data values" << std::endl;
    std::cout << "Not using safe numerics" << std::endl;
    
    // problem: implicit conversions change data values
    try{
        signed int   a{-1};
        unsigned int b{1};
        std::cout << "a is " << a << " b is " << b << '\n';
        if(a < b){
            std::cout << "a is less than b\n";
        }
        else{
            std::cout << "b is less than a\n";
        }
        std::cout << "error NOT detected!" << std::endl;
    }
    catch(const std::exception &){
        // never arrive here - just produce the wrong answer!
        std::cout << "error detected!" << std::endl;
        return 1;
    }

    // solution: replace int with safe<int> and unsigned int with safe<unsigned int>
    std::cout << "Using safe numerics" << std::endl;
    try{
        using namespace boost::safe_numerics;
        safe<signed int>   a{-1};
        safe<unsigned int> b{1};
        std::cout << "a is " << a << " b is " << b << '\n';
        if(a < b){
            std::cout << "a is less than b\n";
        }
        else{
            std::cout << "b is less than a\n";
        }
        std::cout << "error NOT detected!" << std::endl;
        return 1;
    }
    catch(const std::exception & e){
        // never arrive here - just produce the correct answer!
        std::cout << e.what() << std::endl;
        std::cout << "error detected!" << std::endl;
    }
    return 0;
}
example 3: implicit conversions change data values
Not using safe numerics
a is -1 b is 1
b is less than a
error NOT detected!
Using safe numerics
a is -1 b is 1
converted negative value to unsigned: domain error
error detected!

A normal person reads the above code and has to be dumbfounded by this. The code doesn't do what the text - according to the rules of algebra - says it does. But C++ doesn't follow the rules of algebra - it has its own rules. There is generally no compile time error. You can get a compile time warning if you set some specific compile time switches. The explanation lies in reviewing how C++ reconciles binary expressions (a < b is an expression here) where operands are different types. In processing this expression, the compiler:

In order for a programmer to detect and understand this error he should be pretty familiar with the implicit conversion rules of the C++ standard. These are available in a copy of the standard and also in the canonical reference book The C++ Programming Language (both are over 1200 pages long!). Even experienced programmers won't spot this issue and know to take precautions to avoid it. And this is a relatively easy one to spot. In the more general case this will use integers which don't correspond to easily recognizable numbers and/or will be buried as a part of some more complex expression.

This example generated a good amount of web traffic along with everyone's pet suggestions. See for example a blog post with everyone's favorite "solution". All the proposed "solutions" have disadvantages and attempts to agree on how handle this are ultimately fruitless in spite of, or maybe because of, the emotional content. Our solution is by far the simplest: just use the safe numerics library as shown in the example above.

Note that in this particular case, usage of the safe types results in no runtime overhead in using the safe integer library. Code generated will either equal or exceed the efficiency of using primitive integer types.


PrevUpHomeNext