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
- Write a pattern for a version number of the form 1.2.3 and print its three parts.
- Count how many times a word occurs in a sentence with sregex_iterator.
- 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.
-
What is the difference between
regex_matchandregex_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.
-
What does
std::regexcost?Answer
Compilation of the pattern, which is why it is built once
The pattern is compiled into an automaton before it can be reused.
-
What does
smatchstore?Answer
The matches and their capture groups
The match results hold one entry per group, with position and length.
-
What does
regex_replacetake?Answer
A format string with group references
The replacement can use
$1and the other group numbers. -
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.
-
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.
-
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.
-
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>.
-
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.
-
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