15.6. Parsing numbers with error reporting

Converting a string to an integer should distinguish valid input from invalid input and detect values that cannot fit in the result type. std::stoi from <string> returns an int and throws std::invalid_argument or std::out_of_range when it cannot produce one. It also reports how many characters it consumed, so we can reject a partially parsed value.

Unlike atoi, this interface does not silently make a failed conversion look like a valid zero. Do not strip every nondigit from a string: that would turn -12 into 12 or 12x3 into 123 instead of detecting a problem.

For a format that permits comma-separated thousands, first validate the groups, then remove the commas. Phone numbers and identifiers should remain strings; arithmetic conversion loses leading zeros and other meaningful formatting.

Example c192_15_6
 1#include <cstddef>
 2#include <iostream>
 3#include <stdexcept>
 4#include <string>
 5
 6int parse_distance(const std::string& text) {
 7    std::string digits;
 8    std::size_t group_size = 0;
 9    bool has_comma = false;
10    for (char ch : text) {
11        if (ch >= '0' && ch <= '9') {
12            digits += ch;
13            ++group_size;
14        } else if (ch == ',') {
15            if (group_size == 0 || (!has_comma && group_size > 3) ||
16                (has_comma && group_size != 3)) {
17                throw std::invalid_argument("invalid digit grouping");
18            }
19            has_comma = true;
20            group_size = 0;
21        } else {
22            throw std::invalid_argument("expected a nonnegative integer");
23        }
24    }
25    if (digits.empty() || (has_comma && group_size != 3)) {
26        throw std::invalid_argument("incomplete distance");
27    }
28    std::size_t used = 0;
29    int value = std::stoi(digits, &used);
30    if (used != digits.size()) {
31        throw std::invalid_argument("trailing characters");
32    }
33    return value;
34}
35
36int main() {
37    try {
38        std::cout << parse_distance("1,750") << '\n';
39    } catch (const std::exception& error) {
40        std::cerr << error.what() << '\n';
41        return 1;
42    }
43}

The examples 1750 and 1,750 produce the same value. Inputs such as 1,,750, 17,50, -12, an empty string, or letters are rejected. An integer larger than int can represent is also rejected. The accumulated digits string is an example of building a result one character at a time.

What does the atoi() function do?

Which of the following strings will return "2020" when passed into convertToInt()?

Return a copy of text with every old_char replaced by new_char.

  1.         ch = new_char;
        }
    }
  2.     return text;
    }
  3. for (char& ch : text) {
  4. if (ch == old_char) {
  5. std::string replace_with(std::string text, char old_char, char new_char) {