'Is there a more stringent version of std::stoi?

I just discovered (much to my surprise) that the following inputs do not cause std::stoi to throw an exception:

3.14
3.14helloworld

Violating the principle of least surprise - since none of these are valid format integer values.

Note, perhaps even more surprisingly 3.8 is converted to the value 3.

Is there a more stringent version of std::stoi which will throw when an input really is not a valid integer? Or do I have to roll my own?

As an asside, why does the C++ standard library implement std::stoi this way? The only practical use this function has is to desperately try and obtain some integer value from random garbage input - which doesn't seem like a very useful function.

This was my workaround.

static int convertToInt(const std::string& value)
{
    std::size_t index;
    int converted_value{std::stoi(value, &index)};

    if(index != value.size())
    {
        throw std::runtime_error("Bad format input");
    }

    return converted_value;
}


Solution 1:[1]

The answer to your question:

Is there a more stringent version of std::stoi?

is: No, not in the standard library.

std::stoi, as described here behaves like explained in CPP reference:

Discards any whitespace characters (as identified by calling std::isspace) until the first non-whitespace character is found, then takes as many characters as possible to form a valid base-n (where n=base) integer number representation and converts them to an integer value. The valid integer value consists of the following parts: . . . . .

And if you want a maybe more robust version of std::stoi which fits your special needs, you do really need to write your own function.

There are that many potential implementations that there is not the ONE "correct" solution. It depends on your needs and programming style.

I just show you (one of many possible) example solution:

#include <iostream>
#include <string>
#include <utility>
#include <regex>

// Some example. Many many other different soultions possible
std::pair<int, bool> stoiSpecial(const std::string s) {

    int result{};
    bool validArgument{};

    if (std::regex_match(s, std::regex("[+-]?[0-9]+"))) {
        try {
            result = stoi(s);
            validArgument = true;
        }
        catch (...) {};
    }
    return {result, validArgument };
}

// Some test code
int main() {
    
    std::string valueAsString{};
    std::getline(std::cin,valueAsString);

    if (const auto& [result, validArgument] = stoiSpecial(valueAsString); validArgument)
        std::cout << result << '\n';
    else
        std::cerr << "\n\n*** Error: Invalid Argument\n\n";
}

Solution 2:[2]

Is there a more stringent version of std::stoi which will throw when an input really is not a valid integer? Or do I have to roll my own?

You will have to roll your own, because your demands clash with the one, consistent, unsurprising way in which all "string to integer" functionality in both C and C++ is defined.

First off, you'd have to come up with your definition of "a valid integer". Do you accept leading 0 (octal), leading 0x (hexadecimal), and / or leading 0b (binary)? Do you accept leading whitespace?

If you're OK with both, your workaround is good enough. Otherwise, you'd have to check the first character of your string to be isdigit as well as being non-null.


I just discovered (much to my surprise) that the following inputs do not cause std::stoi to throw an exception:

Reading a good reference on any function you are not very familiar with before using it is a rather basic requirement.

That reference states very clearly that, after skipping any leading whitespace, it will take "as many characters as possible" to form "a valid [...] integer number representation", and that the second argument "will receive the address of the first unconverted character".

Violating the principle of least surprise - since none of these are valid format integer values.

Note, perhaps even more surprisingly 3.8 is converted to the value 3.

Is there a more stringent version of std::stoi which will throw when an input really is not a valid integer? Or do I have to roll my own?

There is one significant problem here: You have made assumptions, haven't bothered to check them with a reference, and are now digging in your heels that you know better. Not only is the behavior you observed internally consistent with all of C++'s istream operator>>, std::sto* family, and C's *scanf, strto*, and ato* family. It is also how Java's Scanner.nextInt(), C#'s int.TryParse, Perl's int, and similar functions from a dozen other languages work.

(By the way, this is also true for the various floating-point parsing functions as well.)


Why is std::stoi implemented this way?

Because this is the most efficient implementation for the general use-case.

The only practical use this function has is to desperately try and obtain some integer value from random garbage input - which doesn't seem like a very useful function.

Consider:

4;3.14;16

That is clearly not "random garbage input", but semicolon-separated data -- something encountered quite often, you will agree.

If "reading an int" would throw an exception at a non-digit input, like you suggest, we would be looking at a minimum of two exceptions being thrown for parsing this very non-exceptional line of input. Alternatively, we would have to pass over that input twice, first for finding the semicolons / line ends (and either having to write into the input string or setting up several temporary variables), then a second time for parsing. That would be very inefficient.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Armin Montigny
Solution 2