Skip to content

STL-leverage audit: midpoint in seg trees, emplace+exchange in num_distinct_subsequences#230

Merged
cameroncuster merged 1 commit into
devfrom
stl-audit
Jul 7, 2026
Merged

STL-leverage audit: midpoint in seg trees, emplace+exchange in num_distinct_subsequences#230
cameroncuster merged 1 commit into
devfrom
stl-audit

Conversation

@cameroncuster

Copy link
Copy Markdown
Member

Result of a file-by-file vet of all 144 library headers for missed STL leverage (C++11 through C++23). Stacked on #229 — merge after it.

Changes (4 files, 6 sites)

midpoint(tl, tr) in the recursive seg trees (5 sites)

implicit.hpp (×2), persistent.hpp (×2), kth_smallest_query.hpp (×1):

int tm = tl + (tr - tl) / 2;  // before
int tm = midpoint(tl, tr);    // after

Shorter, states intent, and matches the existing use in offline_incremental_scc.hpp. Identical value for tl < tr (std::midpoint rounds toward the first argument). The lazy_seg_tree family is untouched — it deliberately uses split() (power-of-two layout midpoint from the broken-profile blog), which is a different function.

num_distinct_subsequences.hpp: emplace + exchange (1 line shorter, one fewer map lookup)

// before: find, then branch into update-or-insert (double lookup on miss)
auto it = last.find(a[i]);
if (it != end(last)) {
  cur -= dp[it->second];
  if (cur < 0) cur += mod;
  it->second = i;
} else last[a[i]] = i;
// after: single lookup; exchange reads the old index and writes the new one
auto [it, ins] = last.emplace(a[i], i);
if (!ins) {
  cur -= dp[exchange(it->second, i)];
  if (cur < 0) cur += mod;
}

Verified equivalent against the old version and a brute-force distinct-subsequence counter over 3000 randomized cases under ASan/UBSan.

Audit process

Four parallel scans over data_structures_*, math+convolution+loops+contest, trees+graphs+dsu+flow, and strings+monotonic_stack, followed by a pattern sweep for: midpoint/clamp/exchange/minmax candidates, find != endranges::contains, hand-rolled iota/binary-search/min-max loops, sort+unique+erase, map double lookups, __builtin leftovers, adjacent-element loops (views::adjacent/zip candidates), and C++23 additions (fold_left, ranges::to, views::enumerate).

Evaluated and rejected (so the next audit doesn't re-litigate)

  • clamp in hagerup_kth_par.hpp:36min(max(s, 2*K), d[l]+1) is NOT clamp(s, 2*K, d[l]+1): for shallow subtrees d[l]+1 < 2*K and clamp with lo > hi is UB (asserts under _GLIBCXX_DEBUG). The min/max form is load-bearing.
  • views::enumeraterep(i, 0, sz(a)) is 17 chars; for (auto [i, x] : views::enumerate(a)) is 40. Never shorter at ColumnLimit 59; rejected as a class.
  • views::zip_transform + ranges::max for max_rect_histogram — saves one line but is UB on empty input where the loop returns 0, and reads worse.
  • ranges::fold_left — zero accumulate calls exist to convert.
  • ranges::contains — zero find(...) != end(...) patterns exist.
  • exchange for back()+pop_back() pairs (euler_path, complement_graph_ccs) — no STL pop-and-return; no improvement.
  • Everything else already landed in the earlier C++20/ranges/<bit> passes: iota, partial_sum, minmax, mismatch, partition_point, ranges::sort with projections, iterator form for subrange-returning algorithms.

Validation

  • All 5 covering tests (kth_smallest_pst, persistent_seg_tree, persistent_queue_tree, implicit_seg_tree, num_subsequences) compile clean with g++ -std=c++23 -O2 -Wall -Wextra and clang
  • clang-format --dry-run --Werror passes on all changed files
  • Randomized equivalence test for the num_subsequences rewrite (above)

Base automatically changed from cpp23-library to dev July 7, 2026 16:27
@cameroncuster cameroncuster merged commit 03e2eec into dev Jul 7, 2026
8 checks passed
@cameroncuster cameroncuster deleted the stl-audit branch July 7, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant