How to Split a String in C++: Delimiters and Parsing Input
Split a C++ string on whitespace with istringstream, or on a delimiter with getline or a find loop. Trimming fields, key=value pairs and validating input.
- Course: C++ study plan
- Module: Strings
- Kind: Lesson
- Reading time: 14 min
- Runtime: Clang 18 · C++20
How do you split a string in C++?
C++ has no split function, so there are two idioms. To split on whitespace, wrap the line in a std::istringstream and read with >>, which skips runs of spaces and tabs. To split on a delimiter such as a comma, call std::getline(stream, field, ',') in a loop, or use a find and substr loop, which also keeps a trailing empty field.
Lesson
Every program in this track starts the same way: text arrives on standard input and must become numbers, names and records before any logic runs. The judge never sends malformed input by accident, but it does send the edges — a blank line, spaces around a token, an empty field, a count of zero — and a parser that assumes the happy path fails there. This lesson settles the reading patterns: tokens versus lines, splitting on whitespace with std::istringstream, splitting on a delimiter with std::getline or a find loop, trimming, "N then N lines", key=value pairs, and validating what you read.
Tokens, lines and the newline between them
std::cin >> x skips leading whitespace, reads one token and stops at the next whitespace — it never consumes the newline that ends the line. std::getline(std::cin, line) reads everything up to the newline, consumes the newline and stores the rest. Use one after the other and getline returns the empty remainder of the line that >> was on:
int n = 0;
std::cin >> n;
std::cin.ignore(); // drop the newline that >> left behind
std::string first;
std::getline(std::cin, first); // now the next real line
std::cin.ignore() discards exactly one character; std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') (from <limits>) discards the rest of the line however long — safer when the count could be followed by spaces. Module 1, Console I/O, introduced this; every pattern below uses one of the two.
Splitting a line on whitespace
#include <sstream>
std::string line;
std::getline(std::cin, line); // ada 36 london
std::istringstream iss(line);
std::string word;
while (iss >> word) {
std::cout << '[' << word << "]\n"; // [ada] [36] [london]
}
An std::istringstream is a stream over a string: >> behaves exactly as on std::cin, skipping any amount of whitespace, so runs of spaces, tabs and leading or trailing blanks all disappear — the right tool whenever fields are separated by "one or more spaces". Reading a number the same way also validates it:
std::istringstream iss("42 apples");
int count = 0;
std::string what;
if (iss >> count >> what) { /* both read: 42 and "apples" */ }
std::istringstream bad("forty");
if (!(bad >> count)) { /* "forty" is not an integer: count is now 0 and the stream has failed */ }
A failed >> sets the stream's fail bit and, since C++11, writes 0 to the target; every later >> on that stream fails until clear() is called. Module 18, Streams and files, goes deeper; for parsing, "the read failed, so the token was not a number" is enough. Two shapes of one loop cover most inputs: while (std::cin >> x) reads tokens to the end of input and while (std::getline(std::cin, line)) reads lines — the condition is the stream itself, which converts to false once a read has failed.
Splitting on a delimiter
Comma-, colon- or pipe-separated fields need a different cut, because whitespace is now part of the data and empty fields are meaningful. std::getline takes a third argument, the delimiter:
std::istringstream iss("ada,,london");
std::string field;
while (std::getline(iss, field, ',')) {
std::cout << '[' << field << "]\n"; // [ada] [] [london]
}
An empty field between two commas comes out as an empty string, which is right. But a delimiter at the very end is followed by nothing: the final getline hits end of input without extracting a character and fails, so "a,b," yields two fields, not three. When a trailing delimiter must mean a trailing empty field, use the find loop:
#include <vector>
std::vector<std::string> split(const std::string& text, char delim) {
std::vector<std::string> parts;
std::size_t start = 0;
while (true) {
const std::size_t at = text.find(delim, start);
if (at == std::string::npos) {
parts.push_back(text.substr(start)); // the last field, possibly empty
return parts;
}
parts.push_back(text.substr(start, at - start));
start = at + 1;
}
}
split("a,b,", ',') gives {"a", "b", ""} and split("", ',') one empty field — a definition, so state it when it matters. Lesson 6 rewrites this with std::string_view so no field is copied.
Trimming every field
Fields split on a delimiter keep their spaces: "ada , 36" splits into "ada " and " 36". Lesson 2's trim fixes each one:
std::string trim(const std::string& s) {
const std::size_t start = s.find_first_not_of(" \t");
if (start == std::string::npos) return "";
const std::size_t end = s.find_last_not_of(" \t");
return s.substr(start, end - start + 1);
}
Trim before testing for emptiness or converting to a number: std::from_chars (lesson 3) rejects a leading space outright, and " " is not an empty field until it has been trimmed.
N then N lines
int n = 0;
std::cin >> n;
std::cin.ignore();
for (int i = 0; i < n; ++i) {
std::string line;
std::getline(std::cin, line);
// parse line
}
The shape behind most exercises: a count, then that many records. Trust the count, not the end of input — a second section (queries, say) may follow. When the records are single tokens rather than lines, std::cin >> token needs no ignore() at all, because >> skips newlines itself.
key=value pairs
std::string line = " timeout = 30 ";
const std::size_t eq = line.find('=');
if (eq == std::string::npos) {
std::cout << "invalid: no '='\n";
} else {
std::string key = trim(line.substr(0, eq)); // "timeout"
std::string value = trim(line.substr(eq + 1)); // "30"
if (key.empty()) std::cout << "invalid: empty key\n";
}
Cut on the first = so a value may itself contain one (url = a?b=c). Store the pairs in a std::map<std::string, std::string> — Module 3 iterated one; Module 13, STL containers, covers it — and let later lines overwrite earlier ones with settings[key] = value. Empty lines and lines starting with # are skipped before any of this runs.
Validating tokens
Reading is not done until every token has been checked. Decide in advance: what is the delimiter, are runs of it one separator or several (whitespace: one; commas: several), are surrounding blanks significant, may a field be empty, what range may a number have, and what happens on a bad line — skip, report, or stop. A parser that prints line 3: invalid and carries on beats one that crashes on the first surprise; on the judge a crash is a runtime error, not a wrong answer.
int lineno = 0;
std::string line;
while (std::getline(std::cin, line)) {
++lineno;
if (!line.empty() && line.back() == '\r') line.pop_back(); // Windows line endings
const std::string token = trim(line);
if (token.empty()) continue; // blank lines carry nothing
int value = 0;
if (!parse_int(token, value)) { // lesson 3's from_chars helper
std::cout << "line " << lineno << ": invalid\n";
continue;
}
// use value
}
The '\r' check costs one comparison and saves an afternoon when a file was edited on Windows; this judge sends \n only, but real input promises nothing.
Pitfalls
std::getlinestraight afterstd::cin >> nreads the empty rest of the count's line;ignore()first.std::getline(iss, f, ',')drops a trailing empty field; thefindloop keeps it — pick one and say so.>>on a non-number sets the fail bit and every later read on that stream fails untilclear().- Fields split on a delimiter keep their surrounding spaces; trim before comparing or converting.
- Cut
key=valueon the first=, not the last, so values may contain=.
Key takeaways
>>reads tokens and skips whitespace;getlinereads lines; anignore()sits between them.std::istringstreamturns a line into a stream so>>splits it on whitespace and validates numbers.getlinewith a delimiter and afind/substrloop split on a character; they differ on a trailing delimiter.- Trim every field, then test for empty, then convert.
- Read "N then N lines" by the count, and
while (std::getline(...))when the input runs to the end. - Validate every token and report bad lines; a crash is a runtime error on the judge.
Common questions
How do you split a string by a delimiter in C++?
Wrap the text in a std::istringstream and loop while (std::getline(iss, field, ',')); each call reads up to the next comma, and an empty field between two commas comes out as an empty string. A delimiter at the very end yields no final empty field, so use a find and substr loop when that field matters.
What is istringstream used for in C++?
std::istringstream, from <sstream>, is an input stream over a string, so >> and std::getline work on a line exactly as they do on std::cin. It splits a line into whitespace-separated tokens and validates numbers: if (iss >> count) is false when the token is not an integer.
How do you parse key=value pairs in C++?
Find the first = with line.find('='), report the line as invalid if that returns npos, and otherwise take substr(0, eq) as the key and substr(eq + 1) as the value, trimming both. Cutting on the first = lets the value contain one; store the pairs in a std::map<std::string, std::string>.
How do you read N lines after a number in C++?
Read the count with std::cin >> n, call std::cin.ignore() to drop the newline that >> left behind, then call std::getline(std::cin, line) n times. Trust the count rather than the end of input, because another section may follow the records.
How do you handle Windows line endings when reading lines in C++?
std::getline stops at the newline but keeps the carriage return before it, so lines from a file saved on Windows end in one extra character. After each read, if the line is not empty and line.back() is the carriage return, call line.pop_back(); the check costs one comparison.
Exercises
CSV fields
Read lines until end of input. Each line is a comma-separated record. For every line print the number of fields and then each field with its surrounding spaces trimmed, all joined with |.
The rules: two commas in a row enclose an empty field, a comma at the end of the line is followed by an empty field (so x,y, has three fields), and a line that is empty or contains only spaces has no fields — print 0.
Input: any number of lines. Output: one line per input line.
Example: input
ada, 36 ,london
a,,b
→ output
3|ada|36|london
3|a||b
A find/substr loop keeps the trailing empty field; std::getline with a delimiter drops it — choose accordingly.
Settings file
Read an integer N, then N lines of a settings file, then an integer M and M query keys (single tokens). Each settings line is either blank, a comment starting with #, or key = value: cut on the first =, trim both sides; the value may contain spaces or further = signs and may be empty. A line with no =, or an empty key, is invalid: print line <i>: invalid (i counts all N lines from 1) and carry on. A key set twice keeps the last value.
Then, for each query key, print <key>=<value> or <key>: not set.
Input: N, N lines, M, M keys. Output: one line per invalid settings line, then one line per query.
Example: input
3
colour = blue
broken line
colour=green
2
colour size
→ output
line 2: invalid
colour=green
size: not set