The C++20 feature that gave CTRE its interface
The CTRE call everyone recognises looks like this:
ctre::match<"[a-z]+([0-9]+)">(subject)
A string literal, sitting where a type or a number usually goes. That spelling was impossible before C++20, and the reason it became possible is a small feature with a large effect on how libraries read.
What changed
Template parameters that are values rather than types have always been restricted. Integers, enumerators, pointers with linkage, and not much else. A string literal was firmly outside the list, because the rules had no way to compare two of them for template-argument equivalence.
P0732 widened it to class types whose members are all public and themselves usable this way, with P1907 repairing the specification afterwards. That is enough for a small struct holding a character array, which is what ctll::fixed_string is. Once such a type can be a template parameter, and a string literal can initialise it, the pattern can travel as a template argument.
Both spellings, side by side
Before C++20, the pattern needed a name and a constexpr variable:
static constexpr auto pattern = ctll::fixed_string{"[a-z]+([0-9]+)"};
if (auto m = ctre::match<pattern>(subject)) { ... }
After, the literal goes straight in:
if (auto m = ctre::match<"[a-z]+([0-9]+)">(subject)) { ... }
Both instantiate the same matcher. The literal is converted to the same ctll::fixed_string; it simply no longer needs somewhere to live.
Worth knowing: the older form is not deprecated history. On current CTRE it still compiles under -std=c++17 on both GCC and Clang, which matters if you are on a codebase that has not moved yet. What C++17 cannot do is the direct-literal spelling.
Why the ergonomics matter more than they look
A named constant per pattern sounds like a minor tax until patterns are used the way regexes actually are, which is inline and locally. Compare a function that validates three fields:
static constexpr auto p_year = ctll::fixed_string{R"(\d{4})"};
static constexpr auto p_month = ctll::fixed_string{R"(\d{1,2})"};
static constexpr auto p_day = ctll::fixed_string{R"(\d{1,2})"};
// ... three names to invent, three declarations to scroll past
against three inline literals at the point of use. The feature did not make anything newly possible, since the fixed_string form was already there. It removed a naming ceremony that discouraged using the library in small ways.
A named pattern still needs the right type
One trap worth naming, because the error is unhelpful. This does not work:
constexpr auto kSemver = R"((\d+)\.(\d+)\.(\d+))"; // const char*
ctre::match<kSemver>(s); // does not compile
A pointer to a string literal is not a valid template argument, C++20 or otherwise. Naming a pattern means naming a ctll::fixed_string:
static constexpr auto kSemver = ctll::fixed_string{R"((\d+)\.(\d+)\.(\d+))"};
The next episode uses exactly that, because a pattern worth naming is usually a pattern worth checking at compile time.
Sources: P0732 “Class Types in Non-Type Template Parameters” · P1907 “Inconsistencies with non-type template parameters” · CTRE