c++undefined-behaviornumeric-conversion

Type aware string to number conversion in C++


Let's assume all the numbers in this world are positive integers and they can be represented by uintX_t C++ types.

Let's consider next awesome code to convert a std::string to a number:

#include <string>
#include <cstdint>
#include <iostream>

template <typename T>
T MyAwsomeConversionFunction(const std::string& value)
{
    T result = 0;
    for(auto it = value.begin(); it != value.end() && std::isdigit(*it); ++it)
    {
        result = result * 10 + *it - '0';
    }

    return result;
}

int main(int argc, const char * argv[])
{
    std::cout<<MyAwsomeConversionFunction<uint16_t>("1234")<<std::endl;
    std::cout<<MyAwsomeConversionFunction<uint16_t>("123456")<<std::endl;

    return 0;
}

As you can see there are multiple errors in this function, but I'm interested in a particular one: How to detect when my type is not large enough to contain the value (second conversion call as example) and avoid UB when making result = result * 10 + *it - '0';. I would like to know if that operation will exceed maximum value of T before making it. Is this possible?

EDIT: please check Is signed integer overflow still undefined behavior in C++? for more info about UB on arithmetic operations in C++. I want to avoid executing the line result = result * 10 + *it - '0'; when the result will overflow. In the answer the line is still executed...

EDIT2: I found the answer here: How to detect integer overflow?

EDIT3: The accepted answer applies for signed types. For unsigned types Cheers and hth. - Alf answer is correct.


Solution

  • I'll take a whack at this, although I may get picked apart for mistakes. This does not deal with negative values in the string (your original code doesn't either). And it's limited to ASCII digits, as Alf mentioned in a comment on his answer.

    template <typename T>
    T MyAwsomeConversionFunction(const std::string& value)
    {
        T maxBeforeMult = std::numeric_limits<T>::max / 10;
        T result = 0;
        for(auto it = value.begin(); it != value.end() && std::isdigit(*it); ++it)
        {
            // Check if multiplying would overflow
            if (result > maxBeforeMult)
            {
                // throw overflow
            }
    
            result = result * 10;
            T digit = *it - 0;
    
            // Check if adding would overflow
            if (std::numeric_limits<T>::max - result < digit)
            {
                // throw overflow
            }
    
            result += digit;
        }
    
        return result;
    }