Matching Words in a String with Regular Expressions
A reg ex_iterator reports the matches of a pattern over a range, and it cannot be pointed directly at an istream, so text should be read into a string first.
A Program That Silently Drops a Word
Imagine a small program whose job is to print every whitespace-separated word in a string. It builds a regular expression, creates a regex_iterator over the string's start and end positions, and prints the first capture group of each match. The tool is neat and compact, and that neatness invites a dangerous assumption: that the iterator will report every piece of text you care about. It will not. Its results depend on exactly what the pattern allows, and the first word of a string is often silently left out.
What do you think happens?
Given the text "to be or" and the pattern R"(\s+(\w+))", which words does the program print, and in what order?
Reveal answer
be, or
The word to sits at the very start of the text. The pattern requires a whitespace character before the word, and nothing precedes the first word, so no match can begin there. The two matches are the space before be and the space before or, and only the captured words are printed. Nothing warns you that to was skipped.
Tracing Which Characters Get Matched
The question to ask at every step is whether the pattern could possibly begin a match at that position. Take the text "to be or" and lay out its characters by index. The pattern R"(\s+(\w+))" needs whitespace first, then a run of word characters. The whitespace at index 2 is followed by be, so characters 2 through 4 form the first match. The whitespace at index 5 is followed by or, so characters 5 through 7 form the second match. The letters t and o at indexes 0 and 1 never take part in any match, because no whitespace comes before them.
The changed cells are the only ones the iterator reports as part of a match. Only the captured letters, be and or, are printed, because the program reads capture group 1. The whitespace cells are consumed as part of each match but never shown to the caller. Trace the program against the index map and the missing first word becomes obvious rather than mysterious.
How the Pattern Parts Work Together
The pattern R"(\s+(\w+))" has two parts that cooperate. The outer part, \s+, demands at least one whitespace character. The inner group, (\w+), captures one or more word characters, and that captured text is what the program prints through the index [1]. A match therefore consists of whitespace followed immediately by a word, and only the word is exposed to the caller.
Because the whitespace is part of what must match, the leading space is consumed as part of the match rather than being printed. This is why a word at the start of the string cannot be reported: there is no whitespace character available to satisfy the first half of the pattern.
be
or- The regex encodes the whitespace requirement that causes the first word to be skipped.
- The iterator is built over the string's start and end positions, not over a stream.
- The default
sregex_iteratornamed end is the end-of-sequence marker. - (*it)[1] reads capture group 1, which is the word without its leading space.
The example string is illustrative and was chosen to show the mechanism; the printed result for this exact input follows from the pattern's rules as described above.
Removing the Whitespace Requirement
If you remove the whitespace requirement and use the simpler pattern R"((\w+))", the iterator no longer needs anything in front of a word. The source shows that this pattern does report aa, the word the first version missed. Its output for the source's test string is: aa as asd e asdf asdfg.
The simplified pattern also reports a stray letter e and the word asdf as separate matches. The input contains a caret character between them, and a caret is not a word character, so the run of word characters breaks at that point. A pattern does not know what you meant; it knows only which character classes it accepts. Each change to the pattern changes which substrings are eligible, so you must check the results against the input you actually have.
Predicting matches for the simplified pattern
Using the text to be or and the pattern R"((\w+))", list every match the iterator reports.
- 1Check the start
The pattern needs only a word, so a match can begin at index 0 where t is a word character.
- 2Take the first word
The run of word characters from index 0 is to, which becomes the first match.
- 3Skip the whitespace
The space at index 2 is not a word character, so no match includes it. The search moves on to the next word.
- 4Take the remaining words
be and or each form their own match in the same way.
The iterator reports to, be, and or. Compared with the whitespace-based pattern, the only change is that to is now included.
Why an Iterator Cannot Read From a Stream
A regex_iterator reports the matches of a pattern over a range, and the source states plainly that you cannot iterate directly over an istream with one. The iterator needs a stable range with a start and an end, so the text must first reach a string or another range the iterator can accept. Only then can the iterator look for matches across the whole text.
How the Iterator Advances and Stops
The regex_iterator is described as bidirectional: it can move forward through matches and also move backward. Its behavior is governed by the sequence of matches the pattern produces over the range you gave it. A loop advances the iterator, reads the current match, and keeps going until the iterator equals the default-constructed value, written sregex_iterator{}. The source identifies this default value as the only possible end-of-sequence, so comparing against it is the correct way to know matching has finished.
Read the loop in the earlier code this way: each pass handles one match, the advance step moves to the next one, and the comparison against end is the single condition that stops the loop. Because the default iterator is the only end marker the source names, the loop should be written to compare against it rather than against some other guessed value.
What Reading and Writing Allow
Dereferencing a regex_iterator gives you access to the match it currently points to, and capture groups let you pick out the part you need. This is reading, and it is what the program does when it prints (*it)[1]. The iterator, however, is not a way to write values into the scanned text. The source states that you cannot write through a regex_iterator. It lets you inspect what matched; it does not give you a route for changing the text it scanned.
| Action | Allowed? | Why |
|---|---|---|
| Dereference the iterator to inspect the current match | Yes | Reading the match is the iterator's purpose |
| Read capture group 1 to get just the word | Yes | Capture groups expose the part of the match you chose |
| Write new text through the iterator into the scanned range | No | The iterator is not a way to write values into the scanned text |
| Point the iterator directly at an istream | No | The range must be stable, so text is first read into a string |
What reading through a regex_iterator allows versus what writing through it forbids
Mistakes Beginners Make
Common mistake Expecting the first word to be reported by a whitespace-based pattern
What it looks likeUsing R"(\s+(\w+))" on to be or and expecting to see to in the output
Why it failsThe pattern needs whitespace before each word, and the first word has nothing in front of it, so no match can start there.
Do this insteadUse R"((\w+))" when the first word must be included, and check the results against the actual input.
Common mistake Pointing the iterator directly at an istream
What it looks likeConstructing a regex_iterator over the input stream and looping over it
Why it failsThe iterator needs a stable range with a start and an end, and an istream is consumed as it is read.
Do this insteadRead the text into a std::string first, then construct the iterator over that string's start and end positions.
Common mistake Trying to change the scanned text through the iterator
What it looks likeAssigning a new value through the dereferenced iterator to replace a matched word
Why it failsThe source states that a
regex_iteratorcannot be used to write into the scanned text.Do this insteadRead matches through the iterator, then build any new text in a separate string.
Common mistake Comparing the loop against a guessed end value
What it looks likeStopping when the iterator reaches some arbitrary position
Why it failsThe default
sregex_iteratoris the only end-of-sequence the source identifies.Do this insteadLoop while the iterator is not equal to sregex_iterator{}.
Practice: Predict the Matches
The text is "aa as". Using the whitespace-based pattern R"(\s+(\w+))", predict which words the program prints. Then predict the output if the pattern is changed to R"((\w+))". Explain each result by naming the characters each match covers.
Hints
- Ask whether a match can begin at index 0, where aa starts.
- The whitespace-based pattern needs a space character before each word it reports.
- The simplified pattern needs only a run of word characters, so it can start at index 0.
Key Takeaways
- A
regex_iteratorreports the matches of a pattern over a range, and it cannot be pointed directly at an istream, so text must first be read into a string. - A whitespace-based pattern such as R"(\s+(\w+))" cannot report a word at the very start of the text, because nothing precedes that word; R"((\w+))" finds it.
- Reading through a
regex_iterator, including dereferencing it to get capture groups, is allowed; writing through it into the scanned text is not. - The default
sregex_iterator{} is the only end-of-sequence, so loops should advance the iterator and stop when it equals that default value.