Clingo
Loading...
Searching...
No Matches
matcher.hh
1#pragma once
2
3#include <clingo/ground/base.hh>
4#include <clingo/ground/instantiator.hh>
5#include <clingo/ground/term.hh>
6
7#include <clingo/util/unordered_map.hh>
8
9#include <clingo/core/core.hh>
10
11#include <limits>
12#include <memory_resource>
13
14namespace CppClingo::Ground {
15
18
27template <class Base>
28concept IsBase = requires(Base &b) {
29 b.begin(std::declval<MatcherType>());
30 b.end(std::declval<MatcherType>());
31 b.contains(std::declval<typename Base::Key>(), std::declval<MatcherType>());
32 { b.nth(std::declval<size_t>()).key() } -> std::same_as<typename Base::Key const &>;
33 b.update(size_t{0});
34 { b.template context<int>() } -> std::same_as<int &>;
35} && requires(Base const &b) {
36 { b.nth(std::declval<size_t>()).key() } -> std::same_as<typename Base::Key const &>;
37};
38
47template <class Match>
48concept IsMatch = requires(Match const &m) {
49 { m.vars() } -> std::same_as<VariableSet>;
50 m.match(std::declval<EvalContext const &>(), std::declval<typename Match::Key>());
51 m.eval(std::declval<EvalContext const &>());
52 m.signature(std::declval<VariableSet const &>(), std::declval<VariableSet const &>());
53 std::declval<std::ostream &>() << m;
54};
55
57template <class Eval>
58concept IsEval = requires(Eval const &m) { m.eval(std::declval<EvalContext const &>()); };
59
60static constexpr auto invalid_offset = std::numeric_limits<size_t>::max();
61
66class OnceMatcher : public Matcher {
67 public:
69 OnceMatcher() = default;
70
71 private:
75 virtual auto do_once([[maybe_unused]] EvalContext const &ctx) -> bool { return true; }
76
77 void do_init([[maybe_unused]] InstantiationContext const &ctx, [[maybe_unused]] size_t gen) override {}
78 void do_match([[maybe_unused]] EvalContext const &ctx) override { match_ = true; }
79 auto do_next(EvalContext const &ctx) -> bool override {
80 if (match_) {
81 match_ = false;
82 return do_once(ctx);
83 }
84 return false;
85 }
86 void do_print(std::ostream &out) const override { out << "#once"; }
87
88 bool match_ = false;
89};
90
93
98auto make_interval_matcher(std::vector<bool> const &bound, Term const &lhs, Term const &lower, Term const &upper)
99 -> UMatcher;
100
105auto make_comp_matcher(std::vector<bool> const &bound, Term const &lhs, Relation rel, Term const &rhs) -> UMatcher;
106
110template <IsEval Expr> auto make_once_matcher(Expr const &expr, typename Expr::Key &target) -> UMatcher;
111
116template <IsBase Base, IsMatch Match>
117auto make_non_fact_matcher(Base &base, Match const &match, typename Match::Key &target) -> UMatcher;
118
122template <IsBase Base, IsMatch Match>
123auto make_atom_matcher(std::pmr::monotonic_buffer_resource &mbr, std::vector<bool> const &bound, Base &base,
124 Match const &atom, MatcherType type, size_t &offset) -> UMatcher;
125
126namespace Detail {
127
128template <IsBase Base> class FullIndex {
129 public:
130 FullIndex(Base &base) : base_{&base} {}
131 void init(size_t gen) { base_->update(gen); }
132 auto match(MatcherType type) -> std::pair<size_t, size_t> {
133 // select the index of the first atom of the matcher's generation
134 auto cur = base_->begin(type);
135 // select the first interval that contains an atom of the matcher's generation
136 return {std::distance(index_.begin(),
137 std::ranges::upper_bound(index_, cur, std::less<>{},
138 [](auto const &a) -> decltype(auto) { return a.second; })),
139 cur};
140 }
141 template <IsMatch Match>
142 auto next(EvalContext const &ctx, Match const &m, VariableVec &free, MatcherType type, size_t &pos, size_t &cur,
143 size_t &prev) -> bool {
144 auto n = base_->end(type);
145 // populate the index if it does not yet hold enough elements
146 // (this also adds elements from previous generations that cannot match)
147 for (; imported_ <= cur; ++imported_) {
148 // the current index can no longer provide a match
149 if (cur >= n) {
150 return false;
151 }
152 for (auto const &var : free) {
153 ctx.ass()[var] = std::nullopt;
154 }
155 // note that matches with `imported_ < cur` populate the index
156 // (but do not match because they are from a previous generation)
157 if (m.match(ctx, base_->nth(imported_).key())) {
158 if (index_.empty() || index_.back().second != imported_) {
159 pos = index_.size();
160 index_.emplace_back(imported_, imported_ + 1);
161 } else {
162 ++index_.back().second;
163 }
164 if (imported_ == cur) {
165 // the current index matches
166 prev = cur++;
167 ++imported_;
168 return true;
169 }
170 } else if (cur == imported_) {
171 // the current index does not match
172 ++cur;
173 }
174 }
175 // obtain a (guaranteed) match from the index
176 for (; pos < index_.size(); ++pos) {
177 // all atoms in the interval have been matched
178 cur = std::max(cur, index_[pos].first);
179 // the current index can no longer provide a match
180 if (cur >= n) {
181 return false;
182 }
183 // match the next atom in the interval
184 if (cur < index_[pos].second) {
185 prev = cur++;
186 for (auto const &var : free) {
187 ctx.ass()[var] = std::nullopt;
188 }
189 return m.match(ctx, base_->nth(prev).key());
190 }
191 }
192 return false;
193 }
194
195 private:
196 Base *base_;
197 std::vector<std::pair<size_t, size_t>> index_;
198 size_t imported_ = 0;
199};
200
201struct BindVals {
202 public:
203 BindVals() = default;
204 void add(Assignment const &ass, VariableVec &bind_vars, size_t index) {
205 symbols_.emplace_back(Symbol::from_rep(index));
206 for (auto const &var : bind_vars) {
207 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
208 symbols_.emplace_back(*ass[var]);
209 }
210 }
211 void match(size_t vars, size_t begin, SymbolVec::iterator &it) {
212 it = symbols_.begin();
213 if (begin > 0) {
214 auto n = static_cast<std::ptrdiff_t>(vars + 1);
215 std::advance(it, offset_);
216 for (; it != symbols_.end() && Symbol::to_rep(*it) < begin; it += n, offset_ += n) {
217 }
218 }
219 }
220 auto next(Assignment &ass, VariableVec &bind_vars, size_t end, SymbolVec::iterator &it, size_t &offset) -> bool {
221 if (it != symbols_.end() && Symbol::to_rep(*it) < end) {
222 offset = Symbol::to_rep(*it++);
223 for (auto const &var : bind_vars) {
224 ass[var] = *it++;
225 }
226 return true;
227 }
228 return false;
229 }
230
231 private:
232 size_t offset_ = 0;
233 SymbolVec symbols_;
234};
235
236template <IsBase Base> class SingleIndex {
237 private:
239
240 public:
241 using KeyIterator = IndexMap::iterator;
242 using ValIterator = SymbolVec::iterator;
243
244 SingleIndex(Base &base) : base_{&base} {}
245 void init(size_t gen) { base_->update(gen); }
246
247 template <IsMatch Match>
248 void match(EvalContext const &ctx, size_t bound_var, VariableVec &bind_vars, Match const &m, MatcherType type,
249 KeyIterator &it, ValIterator &jt) {
250 auto &ass = ctx.ass();
251 // store the bound values
252 auto bound_val = *ass[bound_var];
253 // import
254 if (auto n = base_->end(MatcherType::all_atoms); imported_ < n) {
255 for (; imported_ < n; ++imported_) {
256 // unbind all vars for matching
257 ass[bound_var] = std::nullopt;
258 for (auto const &var : bind_vars) {
259 ass[var] = std::nullopt;
260 }
261 // try to match
262 if (m.match(ctx, base_->nth(imported_).key())) {
263 index_.try_emplace(*ass[bound_var]).first.value().add(ass, bind_vars, imported_);
264 }
265 }
266 // restore the assignment
267 ass[bound_var] = bound_val;
268 }
269 // find match
270 if (it = index_.find(bound_val); it != index_.end()) {
271 it.value().match(bind_vars.size(), base_->begin(type), jt);
272 }
273 }
274
275 auto next(EvalContext const &ctx, VariableVec &bind_vars, MatcherType type, KeyIterator it, ValIterator &jt,
276 size_t &offset) -> bool {
277 return it != index_.end() && it.value().next(ctx.ass(), bind_vars, base_->end(type), jt, offset);
278 }
279
280 private:
281 Base *base_;
282 IndexMap index_;
283 size_t imported_ = 0;
284};
285
286template <IsBase Base> class HashIndex {
287 private:
288 class Key {
289 public:
290 Key(Symbol const *syms, size_t hash)
291 : hash_{hash},
292 // NOLINTNEXTLINE
293 syms_{reinterpret_cast<uintptr_t>(syms) | mask_} {}
294 [[nodiscard]] auto hash() const -> size_t { return hash_; }
295 [[nodiscard]] auto marked() const -> bool { return (syms_ & mask_) != 0; }
296 void unmark() const { syms_ = syms_ & ~mask_; }
297 [[nodiscard]] auto symbols() const -> Symbol const * {
298 // NOLINTNEXTLINE
299 return reinterpret_cast<Symbol const *>(syms_ & ~mask_);
300 }
301
302 private:
303 static constexpr uintptr_t mask_ = 1;
304 size_t hash_;
305 mutable uintptr_t syms_;
306 };
307
308 struct KeyEqual {
309 auto operator()(Key const &a, Key const &b) const {
310 if (a.marked() || b.marked()) {
311 return std::equal(a.symbols(), std::next(a.symbols(), size), b.symbols(), std::next(b.symbols(), size));
312 }
313 return a.symbols() == b.symbols();
314 }
315 size_t size;
316 };
317
319
320 public:
321 using KeyIterator = IndexMap::iterator;
322 using ValIterator = SymbolVec::iterator;
323
324 HashIndex(std::pmr::monotonic_buffer_resource &mbr, Base &base, size_t bound)
325 : mbr_{&mbr}, base_{&base}, index_{0, Util::value_hasher{}, KeyEqual{bound}} {
326 assert(bound > 0);
327 temp_values_.reserve(bound);
328 }
329 void init(size_t gen) { base_->update(gen); }
330
331 template <IsMatch Match>
332 void match(EvalContext const &ctx, VariableVec &bound_vars, VariableVec &bind_vars, Match const &m,
333 MatcherType type, KeyIterator &it, ValIterator &jt) {
334 auto &ass = ctx.ass();
335 // store the bound values
336 temp_values_.clear();
337 for (auto const &var : bound_vars) {
338 temp_values_.emplace_back(*ass[var]);
339 }
340 // import
341 if (auto n = base_->end(MatcherType::all_atoms); imported_ < n) {
342 for (; imported_ < n; ++imported_) {
343 // unbind all vars for matching
344 for (auto const &var : bound_vars) {
345 ass[var] = std::nullopt;
346 }
347 for (auto const &var : bind_vars) {
348 ass[var] = std::nullopt;
349 }
350 // try to match
351 if (m.match(ctx, base_->nth(imported_).key())) {
352 if (key_ == nullptr) {
353 key_ =
354 static_cast<Symbol *>(mbr_->allocate(bound_vars.size() * sizeof(Symbol), alignof(Symbol)));
355 }
356 auto *it = key_;
357 for (auto &var : bound_vars) {
358 std::construct_at(it, *ass[var]);
359 it = std::next(it);
360 }
361 auto key_hash = Util::value_hash(std::span{key_, it});
362 auto [kt, ins] = index_.try_emplace(Key{key_, key_hash});
363 if (ins) {
364 key_ = nullptr;
365 kt.key().unmark();
366 }
367 kt.value().add(ass, bind_vars, imported_);
368 }
369 }
370 // restore the assignment
371 auto kt = temp_values_.begin();
372 for (auto const &var : bound_vars) {
373 ass[var] = *kt++;
374 }
375 }
376 // find match
377 auto bound_hash = Util::value_hash(std::span(temp_values_.data(), bound_vars.size()));
378 if (it = index_.find(Key{temp_values_.data(), bound_hash}); it != index_.end()) {
379 it.value().match(bind_vars.size(), base_->begin(type), jt);
380 }
381 }
382
383 auto next(EvalContext const &ctx, VariableVec &bind_vars, MatcherType type, KeyIterator it, ValIterator &jt,
384 size_t &offset) -> bool {
385 return it != index_.end() && it.value().next(ctx.ass(), bind_vars, base_->end(type), jt, offset);
386 }
387
388 private:
389 std::pmr::monotonic_buffer_resource *mbr_;
390 Base *base_;
391 SymbolVec temp_values_;
392 Symbol *key_ = nullptr;
393 IndexMap index_;
394 size_t imported_ = 0;
395};
396
397template <IsBase Base, IsMatch Match> class LookupMatcher : public OnceMatcher {
398 public:
399 LookupMatcher(Base &base, Match const &m, MatcherType type, size_t &offset)
400 : base_{&base}, match_{&m}, type_{type}, offset_{&offset} {}
401
402 private:
403 void do_init([[maybe_unused]] InstantiationContext const &ctx, size_t gen) override { base_->update(gen); }
404 auto do_once(EvalContext const &ctx) -> bool override {
405 if (auto sym = match_->eval(ctx); sym) {
406 if (auto idx = base_->contains(*sym, type_); idx) {
407 *offset_ = *idx;
408 return true;
409 }
410 }
411 return false;
412 }
413 void do_print(std::ostream &out) const override { out << *match_; }
414 [[nodiscard]] auto do_type() const -> MatcherType override { return type_; }
415
416 Base *base_;
417 Match const *match_;
418 MatcherType type_;
419 size_t *offset_;
420};
421
422template <IsBase Base, IsMatch Match> class FullMatcher : public Matcher {
423 public:
424 using Index = FullIndex<Base>;
425
426 FullMatcher(Index &index, Match const &m, VariableVec free, MatcherType type, size_t &offset)
427 : index_{&index}, offset_{&offset}, match_{&m}, free_{std::move(free)}, type_{type} {}
428
429 private:
430 void do_init([[maybe_unused]] InstantiationContext const &ctx, size_t gen) override { index_->init(gen); }
431 void do_match([[maybe_unused]] EvalContext const &ctx) override { std::tie(pos_, cur_) = index_->match(type_); }
432 auto do_next(EvalContext const &ctx) -> bool override {
433 return index_->next(ctx, *match_, free_, type_, pos_, cur_, *offset_);
434 }
435 void do_print(std::ostream &out) const override { out << *match_; }
436 [[nodiscard]] auto do_type() const -> MatcherType override { return type_; }
437
438 Index *index_;
439 size_t *offset_;
440 Match const *match_;
441 VariableVec free_;
442 size_t pos_ = 0;
443 size_t cur_ = 0;
444 MatcherType type_;
445};
446
447template <IsBase Base, IsMatch Match> class SingleMatcher : public Matcher {
448 public:
449 using Index = SingleIndex<Base>;
450 using KeyIterator = Index::KeyIterator;
451 using ValIterator = Index::ValIterator;
452
453 SingleMatcher(Index &index, Match const &m, size_t bound, VariableVec bind, MatcherType type, size_t &offset)
454 : index_{&index}, match_{&m}, offset_{&offset}, bound_{bound}, bind_{std::move(bind)}, type_{type} {}
455
456 private:
457 void do_init([[maybe_unused]] InstantiationContext const &ctx, size_t gen) override { index_->init(gen); }
458 void do_match([[maybe_unused]] EvalContext const &ctx) override {
459 return index_->match(ctx, bound_, bind_, *match_, type_, pos_, cur_);
460 }
461 auto do_next(EvalContext const &ctx) -> bool override {
462 return index_->next(ctx, bind_, type_, pos_, cur_, *offset_);
463 }
464 void do_print(std::ostream &out) const override { out << *match_; }
465 [[nodiscard]] auto do_type() const -> MatcherType override { return type_; }
466
467 Index *index_;
468 Match const *match_;
469 size_t *offset_;
470 size_t bound_;
471 VariableVec bind_;
472 MatcherType type_;
473 KeyIterator pos_;
474 ValIterator cur_;
475};
476
477template <IsBase Base, IsMatch Match> class HashMatcher : public Matcher {
478 public:
479 using Index = HashIndex<Base>;
480 using KeyIterator = Index::KeyIterator;
481 using ValIterator = Index::ValIterator;
482
483 HashMatcher(Index &index, Match const &m, VariableVec bound, VariableVec bind, MatcherType type, size_t &offset)
484 : index_{&index}, match_{&m}, offset_{&offset}, bound_{std::move(bound)}, bind_{std::move(bind)}, type_{type} {}
485
486 private:
487 void do_init([[maybe_unused]] InstantiationContext const &ctx, size_t gen) override { index_->init(gen); }
488 void do_match([[maybe_unused]] EvalContext const &ctx) override {
489 return index_->match(ctx, bound_, bind_, *match_, type_, pos_, cur_);
490 }
491 auto do_next(EvalContext const &ctx) -> bool override {
492 return index_->next(ctx, bind_, type_, pos_, cur_, *offset_);
493 }
494 void do_print(std::ostream &out) const override { out << *match_; }
495 [[nodiscard]] auto do_type() const -> MatcherType override { return type_; }
496
497 Index *index_;
498 Match const *match_;
499 size_t *offset_;
500 VariableVec bound_;
501 VariableVec bind_;
502 MatcherType type_;
503 KeyIterator pos_;
504 ValIterator cur_;
505};
506
507template <IsBase Base, class Sig> class IndexSet : public BaseContext {
508 public:
509 auto add_full(Base &base, Sig sig) -> FullIndex<Base> & {
510 auto it = full_.try_emplace(std::move(sig), nullptr).first;
511 if (it->second == nullptr) {
512 it.value() = std::make_unique<FullIndex<Base>>(base);
513 }
514 return *it->second;
515 }
516
517 auto add_single(Base &base, Sig sig) -> SingleIndex<Base> & {
518 auto it = single_.try_emplace(std::move(sig), nullptr).first;
519 if (it->second == nullptr) {
520 it.value() = std::make_unique<SingleIndex<Base>>(base);
521 }
522 return *it->second;
523 }
524
525 auto add_hash(std::pmr::monotonic_buffer_resource &mbr, Base &base, Sig sig, size_t bound) -> HashIndex<Base> & {
526 auto it = hash_.try_emplace(std::move(sig), nullptr).first;
527 if (it->second == nullptr) {
528 it.value() = std::make_unique<HashIndex<Base>>(mbr, base, bound);
529 }
530 return *it->second;
531 }
532
533 private:
534 // Note: the full index does not need to capture the bound variables
538};
539
540template <IsBase Base, IsMatch Match> class NonFactMatcher : public OnceMatcher {
541 public:
542 NonFactMatcher(Base &base, Match const &match, typename Match::Key &target)
543 : base_{&base}, match_{&match}, target_{&target} {}
544
545 private:
546 void do_init([[maybe_unused]] InstantiationContext const &ctx, size_t gen) override { base_->update(gen); }
547 auto do_once(EvalContext const &ctx) -> bool override {
548 if (auto sym = match_->eval(ctx)) {
549 *target_ = *sym;
550 return !base_->is_fact(*sym);
551 }
552 return false;
553 }
554 void do_print(std::ostream &out) const override { out << "#not_fact " << *match_; }
555
556 Base *base_;
557 Match const *match_;
558 typename Match::Key *target_;
559};
560
562template <IsMatch Match> class EvalMatcher : public OnceMatcher {
563 public:
565 EvalMatcher(Match const &match, typename Match::Key &target) : match_{&match}, target_{&target} {}
566
567 private:
569 auto do_once(EvalContext const &ctx) -> bool override {
570 if (auto sym = match_->eval(ctx)) {
571 *target_ = *sym;
572 return true;
573 }
574 return false;
575 }
576
577 Match const *match_;
578 typename Match::Key *target_;
579};
580
581} // namespace Detail
582
583template <IsBase Base, IsMatch Match>
584auto make_atom_matcher(std::pmr::monotonic_buffer_resource &mbr, std::vector<bool> const &bound, Base &base,
585 Match const &atom, MatcherType type, size_t &offset) -> UMatcher {
586 VariableSet bind = atom.vars();
587 VariableSet lookup;
588 erase_if(bind, [&bound, &lookup](auto const &var) {
589 if (bound[var]) {
590 lookup.insert(var);
591 }
592 return bound[var];
593 });
594 // all variables are bound: evaluate + lookup in domain
595 if (bind.empty()) {
596 return std::make_unique<Detail::LookupMatcher<Base, Match>>(base, atom, type, offset);
597 }
598 auto &ctx = base.template context<Detail::IndexSet<Base, decltype(atom.signature(lookup, bind))>>();
599 // no variables are bound: build an index optimized for sequences
600 if (lookup.empty()) {
601 auto &full = ctx.add_full(base, atom.signature(lookup, bind));
602 return std::make_unique<Detail::FullMatcher<Base, Match>>(full, atom, bind.release(), type, offset);
603 }
604 // exactly one variable is bound and some are unbound: optimized special case
605 if (lookup.size() == 1) {
606 auto &hash = ctx.add_single(base, atom.signature(lookup, bind));
607 return std::make_unique<Detail::SingleMatcher<Base, Match>>(hash, atom, lookup.front(), bind.release(), type,
608 offset);
609 }
610 // at least two variables are bound and some are unbound: general case
611 auto &hash = ctx.add_hash(mbr, base, atom.signature(lookup, bind), lookup.size());
612 return std::make_unique<Detail::HashMatcher<Base, Match>>(hash, atom, lookup.release(), bind.release(), type,
613 offset);
614}
615
616template <IsBase Base, IsMatch Match>
617auto make_non_fact_matcher(Base &base, Match const &match, typename Match::Key &target) -> UMatcher {
618 return std::make_unique<Detail::NonFactMatcher<Base, Match>>(base, match, target);
619}
620
621template <IsEval Expr> auto make_once_matcher(Expr const &expr, typename Expr::Key &target) -> UMatcher {
622 return std::make_unique<Detail::EvalMatcher<Expr>>(expr, target);
623}
624
626
627} // namespace CppClingo::Ground
A context object.
Definition base.hh:139
Context object to capture state used during instantiation.
Definition instantiator.hh:35
auto ass() const -> Assignment &
Get the assignment.
Definition instantiator.hh:41
Context object to capture state used during instantiation.
Definition instantiator.hh:17
A matcher to match expressions.
Definition instantiator.hh:74
void match(EvalContext const &ctx)
Initialize matching.
Definition instantiator.hh:81
auto type() const -> MatcherType
Get the type of the matcher.
Definition instantiator.hh:90
A matcher that matches only provides one match.
Definition matcher.hh:66
OnceMatcher()=default
Construct the matcher.
Term interface.
Definition term.hh:67
Variant-like class to store symbols stored in a symbol store.
Definition symbol.hh:225
static auto from_rep(uint64_t rep) noexcept -> Symbol
Create a symbol from its representation.
Definition symbol.hh:284
static auto to_rep(Symbol sym) noexcept -> uint64_t
Get the representation of the symbol.
Definition symbol.hh:282
Concept for atom bases.
Definition matcher.hh:28
Concept for evaluable expressions.
Definition matcher.hh:58
Concept for matchable expressions.
Definition matcher.hh:48
Base
The base of a number.
Definition number.hh:17
std::vector< std::optional< Symbol > > Assignment
Assignment mapping variables to symbols.
Definition symbol.hh:222
std::vector< Symbol > SymbolVec
A vector of symbols.
Definition symbol.hh:220
Relation
Enumeration of supported relations.
Definition core.hh:35
VariableSet::values_container_type VariableVec
A vector of variables.
Definition base.hh:21
Util::ordered_set< size_t > VariableSet
A set of variables.
Definition base.hh:19
MatcherType
Enumeration of matcher types.
Definition instantiator.hh:48
std::unique_ptr< Matcher > UMatcher
A unique pointer to a matcher.
Definition instantiator.hh:100
@ all_atoms
Indicates a matcher for previously added atoms.
auto make_comp_matcher(std::vector< bool > const &bound, Term const &lhs, Relation rel, Term const &rhs) -> UMatcher
Construct a matcher for comparisons.
auto make_non_fact_matcher(Base &base, Match const &match, typename Match::Key &target) -> UMatcher
Construct a matcher for facts.
Definition matcher.hh:617
auto make_atom_matcher(std::pmr::monotonic_buffer_resource &mbr, std::vector< bool > const &bound, Base &base, Match const &atom, MatcherType type, size_t &offset) -> UMatcher
Construct a matcher for an atom.
Definition matcher.hh:584
auto make_interval_matcher(std::vector< bool > const &bound, Term const &lhs, Term const &lower, Term const &upper) -> UMatcher
Construct an interval matcher.
auto make_once_matcher() -> UMatcher
Construct a matcher matching only once.
tsl::hopscotch_map< Key, T, Hash, KeyEqual, Allocator, NeighborhoodSize, StoreHash, GrowthPolicy > unordered_map
Alias for unordered maps.
Definition unordered_map.hh:17
auto value_hash(std::type_info const &x) -> size_t
Compute a hash for type_info.
Definition hash.hh:277
Compute a hash using std::hash.
Definition hash.hh:85