SysTutorials

Converting Strings to Integers in C++: Modern Approaches and Trade-offs

· updated April 11, 2026

Converting strings to integers is a fundamental operation in C++, but the method you choose affects both code clarity and performance. Here are the four main approaches, ranked by use case.

std::stoi() — The Default Choice

std::stoi() is the standard C++11 method for most situations. It’s exception-based, handles whitespace automatically, and works with custom number bases.

#include <iostream>
#include <string>

int main()
{
  std::string str("123");

  try {
    int n = std::stoi(str);
    std::cout << n << "\n";
  }
  catch (const std::invalid_argument&) {
    std::cerr << "Invalid argument\n";
    return 1;
  }
  catch (const std::out_of_range&) {
    std::cerr << "Out of range\n";
    return 1;
  }
  return 0;
}

Function signature:

int stoi(const std::string& str, std::size_t* pos = 0, int base = 10);

Key behavior:

For larger integers, use std::stol() for long or std::stoll() for long long.

Base conversion example:

std::string hexStr = "FF";
int number = std::stoi(hexStr, nullptr, 16);  // Result: 255

Use std::stoi() when code clarity matters more than raw performance, or when exception-based error handling fits your control flow.

std::from_chars() — Maximum Performance (C++17+)

For performance-critical code, std::from_chars() provides low-overhead conversion without exceptions or global state manipulation:

#include <iostream>
#include <string>
#include <charconv>

int main()
{
  std::string text = "123";
  int number;

  auto [ptr, ec] = std::from_chars(text.data(), text.data() + text.size(), number);

  if (ec == std::errc()) {
    std::cout << number << "\n";
  } else if (ec == std::errc::invalid_argument) {
    std::cerr << "Invalid argument\n";
    return 1;
  } else if (ec == std::errc::out_of_range) {
    std::cerr << "Out of range\n";
    return 1;
  }
  return 0;
}

Advantages:

Partial parsing example:

std::string text = "123abc";
int number;
auto [ptr, ec] = std::from_chars(text.data(), text.data() + text.size(), number);

if (ec == std::errc()) {
  std::cout << "Parsed: " << number << "\n";
  std::cout << "Remaining: " << std::string(ptr, text.data() + text.size()) << "\n";
}

Choose std::from_chars() when parsing large datasets or in performance-sensitive code paths. It’s the standard choice for new C++17+ projects.

std::istringstream — Stream-Based Parsing

String streams treat strings as input streams, allowing familiar >> syntax:

#include <iostream>
#include <string>
#include <sstream>

int main()
{
  std::string text = "123";
  std::istringstream iss(text);
  int number;

  if (iss >> number && iss.eof()) {
    std::cout << number << "\n";
  } else {
    std::cerr << "Conversion failed\n";
    return 1;
  }
  return 0;
}

This approach:

Parsing structured data:

std::string text = "123 456 789";
std::istringstream iss(text);
int a, b, c;

if (iss >> a >> b >> c && iss.eof()) {
  std::cout << "Parsed: " << a << " " << b << " " << c << "\n";
}

Use std::istringstream when parsing space-separated or structured input formats, or when working with existing stream-based code.

strtol() — C Interoperability

For C library compatibility or working in constrained environments, strtol() provides error reporting via errno:

#include <iostream>
#include <cstdlib>
#include <cerrno>
#include <string>

int main()
{
  std::string text = "123";
  errno = 0;

  char* endptr = nullptr;
  long value = std::strtol(text.c_str(), &endptr, 10);

  if (errno == ERANGE) {
    std::cerr << "Value out of range\n";
    return 1;
  }

  if (endptr == text.c_str()) {
    std::cerr << "No valid conversion\n";
    return 1;
  }

  if (*endptr != '\0') {
    std::cerr << "Partial conversion detected\n";
  }

  int number = static_cast<int>(value);
  std::cout << number << "\n";
  return 0;
}

Critical points:

Use strtol() only when interfacing with C code or legacy systems.

Handling Edge Cases and Untrusted Input

Different methods handle edge cases differently:

#include <iostream>
#include <string>
#include <vector>

int main()
{
  std::vector<std::string> test_cases = {
    "",           // Empty
    "   ",        // Whitespace only
    "abc",        // No digits
    "123abc",     // Partial conversion
    "-456",       // Negative
    "0xFF",       // Hex notation
    "999999999999" // Overflow
  };

  for (const auto& str : test_cases) {
    try {
      int n = std::stoi(str);
      std::cout << "\"" << str << "\" -> " << n << "\n";
    }
    catch (const std::invalid_argument&) {
      std::cout << "\"" << str << "\" -> Invalid\n";
    }
    catch (const std::out_of_range&) {
      std::cout << "\"" << str << "\" -> Out of range\n";
    }
  }
  return 0;
}

Expected behavior with std::stoi():

Choosing Your Method

Situation Use
General-purpose, readable code std::stoi()
Parsing large datasets, C++17+ std::from_chars()
Structured/space-separated input std::istringstream
C library interop, legacy code strtol()
Never use atoi()

For new C++17+ code, prefer std::from_chars() in performance-critical paths and std::stoi() elsewhere. Both are safer and more predictable than older C-style functions.

← All posts