Library / SDK
hanickadot/compile-time-regular-expressions avatar
hanickadot/compile-time-regular-expressions

CTRE: Compile Time Regular Expressions in C++

Compile Time Regular Expression in C++

3,863 stars211 forksC++Apache-2.0

At a glance

What is it?
CTRE turns a regex pattern into a compile-time template argument, so matching happens in the type system instead of a runtime engine. It suits C++17 and C++20 projects that want PCRE-style syntax without shipping a regex interpreter.
Who is it for?
Adopt CTRE when your patterns are fixed at compile time, your toolchain is one of the supported clang, gcc or MSVC versions, and you want captures without a runtime regex engine. Do not adopt it when patterns come from configuration files, user input or a plugin at runtime, since the pattern must be a template argument or a constexpr fixed_string.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What CTRE solves for C++ projects that match fixed patterns

std::regex is a runtime engine. The pattern is a string, the engine parses it when you construct the object, and matching walks a state machine at execution time. For a program that validates log lines, parses dates, or checks identifiers against a pattern that never changes, that work is repeated on every call and every process start.

CTRE moves the pattern into the type system. The README shows the two entry points: ctre::match<"REGEX">(subject) for C++20, and "REGEX"_ctre.match(subject) for C++17 with the N3599 extension. Because the pattern is a template argument, the library can build its matcher during compilation and the generated code is a fixed sequence of comparisons and branches rather than a generic interpreter loop. That is the whole pitch, and it is a narrow one: it only applies when the pattern is known before the program runs.

The audience is C++ engineers writing parsers, validators and tokenizers for formats they control. It is not a drop-in replacement for code that reads a regex from a file or a command-line flag.

How the pattern becomes a type: the compile-time mechanism

The library parses PCRE syntax at compile time. The repository contains include/ctre/pcre.gram, and the Makefile has a grammar target that regenerates include/ctre/pcre.hpp from it using a tool called DESATOMAT. So the PCRE grammar is compiled into a C++ parser that runs inside the compiler, and the resulting match logic is emitted as template instantiations.

At the call site the API is small. ctre::match checks the whole input, ctre::search looks for a match anywhere inside, and ctre::starts_with checks the prefix. Each returns a regex_results object that converts to bool, exposes to_view() and to_string(), and supports structured bindings so you can unpack the whole match and its capture groups in one declaration. Captures are retrieved either by index through get<Id>() or by name, with the README noting that named captures use the (?<name>...) syntax only.

The range API extends this: ctre::range yields every occurrence, ctre::tokenize stops at the first thing that cannot be matched, and ctre::split returns the pieces between matches. All six functions are functors, so ctre::match<"regex"> can be stored in a variable and called later without parentheses in the declaration.

One design decision worth flagging: unknown escapes are a syntax error, not a literal character. The README states that escaped characters with special meaning are treated as such, and only \-, \", \< and \> are explicitly allowed to insert the character itself. A pattern ported from a language that is lax about escapes may fail to compile.

Installing CTRE and running a first match

CTRE is header-only. The README points at the single-header directory, and says the header can be regenerated with make single-header. With CMake, you add the repository directory as a subdirectory and link against the target ctre.

The example the README gives for extracting a number from a string uses ctre::match with a capture group and get<1>:

c++
std::optional<std::string_view> extract_number(std::string_view s) noexcept {
    if (auto m = ctre::match<"[a-z]+([0-9]+)">(s)) {
        return m.get<1>().to_view();
    } else {
        return std::nullopt;
    }
}

The function returns the digits when the input is letters followed by digits. If the pattern does not match the whole string, m converts to false and you get std::nullopt.

The README also shows a date extractor that unpacks the result with structured bindings, where the first binding is the whole match and the rest are the capture groups:

c++
if (auto [whole, year, month, day] = ctre::match<"(\\d{4})/(\\d{1,2})/(\\d{1,2})">(s); whole) {
    return date{year, month, day};
} else {
    return std::nullopt;
}

For C++17 without the N3599 extension, the pattern goes into a constexpr ctll::fixed_string and is passed as a template parameter, which the README says is tested on MSVC 15.8.8. Unicode matching needs an extra include: either <ctre-unicode.hpp> or <ctre.hpp> together with <unicode-db.hpp>. Without one of those, the README warns you will get missing symbols when you try to use Unicode support.

Where CTRE cannot go: runtime patterns and missing PCRE features

The most important limitation is structural. The pattern must be a compile-time constant, so CTRE cannot serve any application where the regex arrives at runtime from a config file, a database row, a REST payload or a user-supplied filter. That is not a missing feature; it is the design. If you need runtime patterns, std::regex or a runtime PCRE binding is the correct tool.

The PCRE coverage is also partial. The README lists what is not implemented: callouts, comments, conditional patterns, control characters (\cX), match point reset (\K), named characters, octal numbers, options and modes, subroutines, and the Unicode grapheme cluster escape \X. Someone porting a mature PCRE pattern set should check each of those against their patterns before assuming the port is mechanical. Options and modes in particular are easy to rely on without noticing.

Compiler support is another boundary. The README lists clang 14.0+, xcode clang 15.0+, gcc 9.0+ and MSVC 14.29+ (Visual Studio 16.11+). The C++20 cNTTP syntax, where the pattern is a plain string literal template argument, is described as supported only by GCC 9+. On other compilers you fall back to the template UDL or the fixed_string form. With GCC 9.1+ the N3599 extension requires defining CTRE_ENABLE_LITERALS and avoiding -pedantic, which conflicts with builds that enforce pedantic warnings as errors.

CTRE against std::regex and runtime PCRE bindings

The honest comparison is with std::regex, because both live in the same standard library ecosystem and neither adds a dependency beyond headers. std::regex parses the pattern once at construction and then executes it through a virtual-machine-like engine; the pattern string can come from anywhere. CTRE parses the pattern inside the compiler and emits specialized code that cannot accept a runtime pattern at all.

That trade is the entire decision. If your patterns are fixed and your build times can absorb the template instantiation, CTRE removes the runtime parse and the interpreter dispatch. If your patterns are dynamic, std::regex keeps working and CTRE does not. Compared with a PCRE binding, the difference is feature coverage: PCRE2 supports the callouts, conditionals, subroutines and modes that CTRE's README explicitly excludes, at the cost of a runtime library and a runtime parse.

There is also a build-time cost that the README does not quantify. Every distinct pattern is a distinct template instantiation, and the library ships a clang-bench.txt and gcc-bench.txt in the repository root, which suggests the author tracks compiler behaviour. The README itself gives no numbers, so treat compile-time impact as something to measure on your own translation units rather than something documented.

Versioning, licence and the cost of upgrading

Releases are infrequent and versioned: v3.9.0 in May 2024, v3.10.0 in May 2025, and v3.11.0 on 2026-05-08. The last push to the default branch was on 2026-09-11, so the repository is being touched, but the release cadence is roughly annual, which matters if you depend on a fix landing in a tagged version rather than on main.

The project is Apache-2.0. That is a permissive licence with an explicit patent grant, and it is compatible with use in closed-source products, but I am not a lawyer and this is not legal advice. If you redistribute the single header, read the LICENSE file in the repository root for the notice requirements.

Because the library is header-only, an upgrade is a header swap plus a rebuild. The real cost is recompilation of every translation unit that includes ctre.hpp, and the risk that a pattern which previously compiled now fails, given that unknown escapes are errors. The README does not document a deprecation policy or a rollback procedure, so pinning to a tag such as v3.11.0 in your build is the practical safeguard.

Editorial conclusion

Adopt CTRE when your patterns are fixed at compile time, your toolchain is one of the supported clang, gcc or MSVC versions, and you want captures without a runtime regex engine. Do not adopt it when patterns come from configuration files, user input or a plugin at runtime, since the pattern must be a template argument or a constexpr fixed_string. Before committing, verify that your compiler supports the syntax you plan to use, that the PCRE features you need are not on the exclusion list, and that including ctre-unicode.hpp does not pull in more than you want.

Frequently asked questions

What does "compile time" mean for CTRE?

It means the regex pattern is a template argument or a constexpr fixed_string, so the library parses it and builds the matcher while your program is being compiled rather than when it runs. The README describes the library as supporting matching, searching and capturing during compile time or runtime.

Does CTRE support the same PCRE syntax as PCRE2?

It implements most PCRE syntax, but the README lists exceptions including callouts, comments, conditional patterns, control characters, match point reset, named characters, octal numbers, options and modes, subroutines, and the Unicode grapheme cluster escape. Named captures work only with the (?<name>...) syntax.

Which compilers can build CTRE?

The README lists clang 14.0+, xcode clang 15.0+, gcc 9.0+ and MSVC 14.29+ (Visual Studio 16.11+). The C++20 cNTTP syntax ctre::match<PATTERN>(subject) is described as supported only by GCC 9+.

Can CTRE match a regex that is only known at runtime?

No. The pattern has to be a template argument or a constexpr ctll::fixed_string, which is the mechanism the whole library is built on. For patterns loaded from configuration or user input, a runtime engine such as std::regex or a PCRE binding is the appropriate choice.

How do I enable Unicode support in CTRE?

The README says to include either <ctre-unicode.hpp>, or <ctre.hpp> together with <unicode-db.hpp>. Using Unicode support without those includes produces missing symbols.

Official sources

  1. hanickadot/compile-time-regular-expressions on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hanickadot-compile-time-regular-expressions.svg)](https://hysenlabs.com/projects/hanickadot-compile-time-regular-expressions)