Regular expressions

A regular expression describes a shape of text. std::regex compiles the description once, and the matching functions test a string against it: regex_match requires the whole string to fit, regex_search looks for a part that fits.

The cost is real: std::regex is slower than a hand-written parser and its error messages are weaker than a parser's. It earns its place for short, irregular patterns such as a log line, a date or a version number, which is what this lesson matches.

The compiled regex can be reused: construct it once, outside the loop, and pass it to every match. Constructing it inside a loop recompiles the pattern on every iteration, which is the usual reason a regex seems slow.

A full match

regex_match returns true only when the entire string fits the pattern. The pattern below is three capitals, a dash and two digits, and the two calls differ only in the number of digits at the end.

The program prints 1 for ABC-12 and 0 for ABC-123. The pattern is anchored by regex_match itself: there is no need for ^ and $ to make the whole string count.

The grammar is ECMAScript by default, which is the one JavaScript uses. The other grammars, such as grep and POSIX extended, are constants passed as the second argument of the constructor.

#include <iostream> #include <regex> #include <string> int main() { std::regex code{"[A-Z]{3}-[0-9]{2}"}; std::cout << std::regex_match(std::string{"ABC-12"}, code) << ' ' << std::regex_match(std::string{"ABC-123"}, code) << std::endl; }

g++ -std=c++23 -Wall -Wextra match.cpp -o match
./match

1 0

A search with captures

regex_search finds the first part of the string that fits. The smatch result holds the whole match at index 0 and each capture group from index 1, in the order the parentheses appear.

The program parses a request line into its method, its path and its status. The pattern has three groups, and the print shows the three pieces of the line.

The groups are strings, not numbers, and they are valid only while the searched string is unchanged. Converting the status to an int is the caller's job, as in lesson 017.

#include <iostream> #include <regex> #include <string> int main() { std::string line = "GET /index.html 200"; std::regex request{"^(GET|POST) (\\S+) (\\d+)$"}; std::smatch parts; if (std::regex_search(line, parts, request)) { std::cout << parts[1] << ' ' << parts[2] << ' ' << parts[3] << '\n'; } }

g++ -std=c++23 -Wall -Wextra search.cpp -o search
./search

GET /index.html 200

Capturing a date

A pattern with groups can take a date apart. The three groups of the pattern are the year, the month and the day, and the program prints them in another order with slashes.

The text around the date is not part of the pattern, and regex_search finds the date inside it. The match is a view into the text; the program builds a new string from the pieces.

A pattern with a quantifier inside a group repeats the group, which is the usual source of surprises: (\d)+ captures only the last digit. The pattern here puts the quantifier inside each group and captures the whole field.

#include <iostream> #include <regex> #include <string> int main() { std::string text = "on 2026-11-11 at noon"; std::regex date{"(\\d{4})-(\\d{2})-(\\d{2})"}; std::smatch parts; if (std::regex_search(text, parts, date)) { std::cout << parts[1] << '/' << parts[2] << '/' << parts[3] << '\n'; } }

g++ -std=c++23 -Wall -Wextra groups.cpp -o groups
./groups

2026/11/11

Replacing matches

regex_replace copies the input and replaces every match with a replacement string. The replacement may refer to capture groups with $1, $2 and so on, which is how a rewrite keeps part of what it matched.

The program replaces every e-mail address in a line with a placeholder. The pattern is deliberately simple, and it is enough to show the call.

The replacement string is a format string of its own: $& is the whole match and $$ is a literal dollar. A program that builds a replacement from user input has to escape the dollars, or the text is interpreted as a group reference.

#include <iostream> #include <regex> #include <string> int main() { std::string text = "ada@example.com and bob@test.org"; std::regex email{"[a-z]+@[a-z.]+"}; std::cout << std::regex_replace(text, email, "<mail>") << '\n'; }

g++ -std=c++23 -Wall -Wextra replace.cpp -o replace
./replace

<mail> and <mail>

match is not search

regex_match asks whether the whole string fits, so a pattern for one letter does not match a three-letter word. regex_search asks whether the pattern occurs anywhere, and the same pattern finds it.

The program prints both answers for the same input: 0 for match and 1 for search. The difference is the reason a pattern written for search appears to fail when it is passed to match.

When a pattern was written to match a whole line, add ^ and $ and use regex_search, or keep regex_match and accept that the pattern must cover everything. The choice is the caller's, and it changes the meaning of the pattern.

#include <iostream> #include <regex> #include <string> int main() { std::string text = "abc"; std::regex pattern{"b"}; std::cout << std::regex_match(text, pattern) << ' ' << std::regex_search(text, pattern) << std::endl; }

g++ -std=c++23 -Wall -Wextra match_search.cpp -o match_search
./match_search

0 1

Exercises

  1. Write a pattern for a version number of the form 1.2.3 and print its three parts.
  2. Count how many times a word occurs in a sentence with sregex_iterator.
  3. Replace every run of spaces with a single space and compare the lengths before and after.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What is the difference between regex_match and regex_search?

    Answer

    match must fit the whole string, search finds a part

    The names say what they do: a full match or a search inside the text.

  2. What does std::regex cost?

    Answer

    Compilation of the pattern, which is why it is built once

    The pattern is compiled into an automaton before it can be reused.

  3. What does smatch store?

    Answer

    The matches and their capture groups

    The match results hold one entry per group, with position and length.

  4. What does regex_replace take?

    Answer

    A format string with group references

    The replacement can use $1 and the other group numbers.

  5. What does this program print? #include <iostream> #include <regex> #include <string> int main() { std::regex pattern{"\\d{3}"}; std::cout << std::regex_match(std::string{"123"}, pattern) << ' '; std::cout << std::regex_match(std::string{"1234"}, pattern) << '\n'; std::cout << std::regex_search(std::string{"abc123def"}, pattern); }

    Answer

    1 0 / 1

    The first string is exactly three digits, the second is not, and the search finds three digits inside the third.

  6. What does this program print? #include <iostream> #include <regex> #include <string> int main() { std::string line = "GET /index.html 200"; std::regex pattern{"(\\w+) (\\S+) (\\d+)"}; std::smatch found; if (std::regex_search(line, found, pattern)) std::cout << found[1] << ' ' << found[2] << ' ' << found[3]; }

    Answer

    GET /index.html 200

    The three groups capture the method, the path and the status, and the program prints them.

  7. What does this program print? #include <iostream> #include <regex> #include <string> int main() { std::string date = "2026-11-11"; std::regex pattern{"(\\d{4})-(\\d{2})-(\\d{2})"}; std::smatch found; std::regex_match(date, found, pattern); std::cout << found[1] << '/' << found[2] << '/' << found[3]; }

    Answer

    2026/11/11

    The three groups are the year, month and day, and the program joins them with slashes.

  8. What does this program print? #include <iostream> #include <regex> #include <string> int main() { std::string text = "mail me at ada@example.com or bob@example.com"; std::regex pattern{"[a-z]+@[a-z.]+[a-z]"}; std::cout << std::regex_replace(text, pattern, "<mail>"); }

    Answer

    mail me at <mail> or <mail>

    The replacement is applied to every match, and both addresses become <mail>.

  9. What does this program print? #include <iostream> #include <regex> #include <string> int main() { std::regex pattern{"cat"}; std::string text = "the cat sat"; std::cout << std::regex_match(text, pattern) << ' ' << std::regex_search(text, pattern); }

    Answer

    0 1

    The string is not exactly cat, but the word cat is inside it.

  10. Why is std::regex often slower than a hand-written parser?

    Answer

    The automaton is general and allocates during matching

    A general engine pays for generality; a tight parser knows its grammar.

The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev

Banner image