@@ -134,12 +134,25 @@ class radix_tree {
134134 // 1. exact_terminus_ on the matched node, if every request segment
135135 // consumed by exact-or-wildcard descent.
136136 // 2. prefix_terminus_ on the deepest ancestor that has one.
137+ //
138+ // Hot-path implementation: iterates `path` directly without calling
139+ // tokenize(), avoiding the std::vector<std::string> allocation and
140+ // per-segment string copies. Each segment is extracted as a
141+ // std::string_view and compared against children_ keys (std::string)
142+ // by std::unordered_map::find(std::string_view) — valid because
143+ // std::string is implicitly comparable to std::string_view.
144+ // The wildcard path copies the segment into captures<string,string>
145+ // only when a wildcard node is taken.
137146 bool find (std::string_view path, radix_match<T>& out) const {
138147 out = {};
139- const auto segments = tokenize (path);
140- if (segments.empty ()) return match_root_terminus (out);
141-
142148 const radix_node<T>* node = root_.get ();
149+
150+ // Strip a single leading slash; a bare "/" normalises to empty.
151+ std::string_view rest = path;
152+ if (!rest.empty () && rest.front () == ' /' ) rest.remove_prefix (1 );
153+
154+ if (rest.empty ()) return match_root_terminus (out);
155+
143156 // Track best prefix candidate seen during descent (deepest wins).
144157 // Root prefix terminus: a `register_prefix("/")` matches every
145158 // request, so seed best_prefix with it before walking deeper.
@@ -148,15 +161,38 @@ class radix_tree {
148161 std::vector<std::pair<std::string, std::string>> best_prefix_caps;
149162 std::vector<std::pair<std::string, std::string>> caps;
150163
151- for (std::size_t i = 0 ; i < segments.size (); ++i) {
152- const std::string& seg = segments[i];
153- // Prefer exact child over wildcard.
154- auto it = node->children_ .find (seg);
164+ while (!rest.empty ()) {
165+ // Extract the next segment up to the next '/' (or end).
166+ std::string_view seg;
167+ std::string_view::size_type slash = rest.find (' /' );
168+ if (slash == std::string_view::npos) {
169+ seg = rest;
170+ rest = {};
171+ } else {
172+ seg = rest.substr (0 , slash);
173+ rest = rest.substr (slash + 1 );
174+ }
175+
176+ // Prefer exact child over wildcard. std::unordered_map::find
177+ // accepts a key_type const reference; we provide a temporary
178+ // std::string constructed from the view only when the
179+ // transparent lookup below fails.
180+ //
181+ // Use heterogeneous lookup: children_ is keyed by std::string
182+ // and std::string is implicitly constructible from std::string_view,
183+ // so passing a std::string_view to find() works via the key_equal
184+ // (std::equal_to<std::string> compares against std::string_view
185+ // through the implicit conversion on one side — but this requires
186+ // a full std::string construction for the map lookup since
187+ // std::unordered_map does not support heterogeneous lookup without
188+ // a transparent hasher). Use string(seg) only here to avoid the
189+ // full vector allocation while still performing the lookup.
190+ auto it = node->children_ .find (std::string (seg));
155191 if (it != node->children_ .end ()) {
156192 node = it->second .get ();
157193 } else if (node->wildcard_child_ ) {
158194 node = node->wildcard_child_ .get ();
159- caps.emplace_back (node->wildcard_name_ , seg);
195+ caps.emplace_back (node->wildcard_name_ , std::string ( seg) );
160196 } else {
161197 // No way forward: best we can do is the deepest prefix
162198 // candidate seen (or nothing).
@@ -168,7 +204,7 @@ class radix_tree {
168204 }
169205 // If we just consumed the last request segment AND this node
170206 // carries an exact terminus, that beats any prefix candidate.
171- if (i + 1 == segments. size () && node->exact_terminus_ .has_value ()) {
207+ if (rest. empty () && node->exact_terminus_ .has_value ()) {
172208 out.entry = &node->exact_terminus_ .value ();
173209 out.captures = std::move (caps);
174210 out.is_prefix_match = false ;
0 commit comments