6

Given a string that should represent a number, I'd like to put it into a conversion function which would provide notification if the whole string did not convert.

For input: "12":

  • istringstream::operator>> outputs 12
  • atoi outputs 12
  • stoi outputs 12

For input "1X" I'd like a failure response but I get:

  • istringstream::operator>> outputs 1
  • atoi outputs 1
  • stoi outputs 1

For input "X2":

  • istringstream::operator>> outputs 0 and sets an error flag
  • atoi outputs 0
  • stoi throws an error

[Live Example]

Is there a way to provoke the error behavior on input "1X"?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • I think you have answers in SO: http://stackoverflow.com/questions/2844817/how-do-i-check-if-a-c-string-is-an-int and http://stackoverflow.com/questions/1243428/convert-string-to-int-with-bool-fail-in-c/1243435#1243435 – masoud Oct 07 '15 at 11:45
  • @deepmax Yup, for the purposes of a minimal example I didn't include it, but in my local test code I'm doing this at the top of the loop: `cout << "\tinput string: " << i << (all_of(i, i + strlen(i), bind(isdigit, placeholders::_1)) ? " is good\n" : " is bad\n");` However, even though I *can* check this way I don't want to. All the other functions must step through each character as well and I would like to have a way to harness what they already know. – Jonathan Mee Oct 07 '15 at 12:12
  • @deepmax You are wrong this is not a duplicate, those answers do **not** address verifying that the whole string has been read. – Jonathan Mee Oct 07 '15 at 12:14
  • 1
    I agree that this is not a duplicate. It asks a different question, which is not answered by the linked answers. @deepmax you should un-mark it. – Klitos Kyriacou Oct 07 '15 at 12:32
  • 1
    Anyway, here's the answer: int pos; int n = stoi(mystring, &pos); if (pos != mystring.length()) it's not numeric. – Klitos Kyriacou Oct 07 '15 at 12:34
  • I've reopened the question, but I believe it's a dup since they are answering the question right to the point. – masoud Oct 07 '15 at 12:36
  • @KlitosKyriacou That's exactly what I needed, can you put that as an answer? – Jonathan Mee Oct 07 '15 at 12:39
  • @JonathanMee: The comment that KlitosKyriacou provided is in the answers of the links that I've posted (and more detailed). Read this answer: http://stackoverflow.com/a/1243531/952747 – masoud Oct 07 '15 at 12:42
  • You're right, deepmax, although those answers use strtol instead of stoi (which is what I had searched for). In some libraries, stoi is implemented in terms of strtol, so those answers can be adapted. – Klitos Kyriacou Oct 07 '15 at 12:46
  • @deepmax Neither of your linked answers even contains the text `stoi`. I was able to find [a solution that uses `strtol`](http://stackoverflow.com/a/1243531/2642059) which is also viable. However, just because an answer that answers my question is provided on a separate question does *not* make it a duplicate. I searched http://www.stackoverflow.com for a question about converting an *entire* string into a number, and there wasn't one. This question saves the next person from having to re-ask it. – Jonathan Mee Oct 07 '15 at 12:47
  • @KlitosKyriacou I went ahead and added [an answer](http://stackoverflow.com/a/32997831/2642059) lest the question should be re-closed, as I don't think that there is a well documented answer for this question available. I did use `stoi` as you suggested, and as such if you want to type up an answer I will accept yours as you were onto the right of it first. – Jonathan Mee Oct 07 '15 at 16:41
  • That's ok Jonathan, your own answer is much more detailed and helpful than anything I would have written. – Klitos Kyriacou Oct 07 '15 at 17:12
  • @deepmax Thank you for un-closing. I believe the information presented in this question and answer will be very helpful to anyone suffering from a problem similar to mine. – Jonathan Mee Oct 07 '15 at 18:10

2 Answers2

3

Edit: In or later from_chars is preferred. See here for more: https://topanswers.xyz/cplusplus?q=724#a839


For a given string str there are several ways to accomplish this each with advantages and disadvantages. I've written a live example here: https://ideone.com/LO2Qnq and discuss each below:

strtol

As suggested here strtol's out-parameter can be used to get the number of characters read. strtol actually returns a long not an int so a cast is happening on the return.

char* size;
const int num = strtol(str.c_str(), &size, 10);

if(distance(str.c_str(), const_cast<const char*>(size)) == str.size()) {
    cout << "strtol: " << num << endl;
} else {
    cout << "strtol: error\n";
}

Note that this uses str.c_str() to refer to the same string. c_str Returns pointer to the underlying array serving as character storage not a temporary if you have C++11:

c_str() and data() perform the same function

Also note that the pointer returned by c_str will be valid between the strtol and distance calls unless:

  • Passing a non-const reference to the string to any standard library function
  • Calling non-const member functions on the string, excluding operator[], at(), front(), back(), begin(), rbegin(), end() and rend()

If you violate either of these cases you'll need to make a temporary copy of i's underlying const char* and perform the test on that.

sscanf

sscanf can use %zn to return the number of characters read which may be more intuitive than doing a pointer comparison. If base is important, sscanf may not be a good choice. Unlike strtol and stoi which support bases 2 - 36, sscanf provides specifiers for only octal (%o), decimal (%d), and hexadecimal (%x).

size_t size;
int num;

if(sscanf(str.c_str(), "%d%zn", &num, &size) == 1 && size == str.size()) {
    cout << "sscanf: " << num << endl;
} else {
    cout << "sscanf: error\n";
}

stoi

As suggested here stoi's output parameter works like sscanf's %n returning the number of characters read. In keeping with C++ this takes a string and unlike the C implementations above stoi throws an invalid_argument if the first non-whitespace character is not considered a digit for the current base, and this unfortunately means that unlike the C implementations this must check for an error in both the try and catch blocks.

try {
    size_t size;
    const auto num = stoi(str, &size);

    if(size == str.size()) {
        cout << "stoi: " << num << endl;
    } else {
        throw invalid_argument("invalid stoi argument");
    }
} catch(const invalid_argument& /*e*/) {
    cout << "stoi: error\n";
}
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
1

Alternatively you can use std::istringstream as you mentioned, but check to make sure it parsed to the end of the stream. Assuming you have a constant reference, you could do something like the following

T parse(const std::string& input) {
    std::istringstream iss(input);
        T result;
        iss >> result;
        if (iss.eof() || iss.tellg() == int(input.size())) {
            return result;
        } else {
            throw std::invalid_argument("Couldn't parse entire string");
    }
}

The benefit of this approach is that you parse anything that overloads operator>>. Note: I'm not entirely sure if the condition is enough, but with my testing it seemed to be. For some reason the stream would get a failure marking if it parsed to the end.

Erik
  • 6,470
  • 5
  • 36
  • 37
  • I've given you a +1 because this maybe a very helpful way to do the conversion for someone who needs to work further with an `istringstream` and could incorporate your solution, however in the general case I believe one of the more lightweight conversions in [my answer](http://stackoverflow.com/a/32997831/2642059) should be used. – Jonathan Mee Feb 01 '16 at 01:15