clusterlin: adopt STL ranges algorithms (refactor)
What changed, and why it matters
This commit is a straightforward modernization of the Bitcoin Core codebase: it replaces older C++ standard-library iterator-based calls like std::sort and std::unique with their newer C++20 'ranges' equivalents (std::ranges::sort, std::ranges::unique). The behavior of the code is intended to stay exactly the same; only the syntax is updated. There is no security fix or vulnerability being addressed here.
No security action required. Treat as normal code-quality/maintenance refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors transaction-graph and cluster-linearization code to use C++20 ranges algorithms. Examples include std::ranges::sort(container, comparator), std::ranges::unique(container), and replacing list.begin()+offset with std::span{list}.subspan(old_len). The change is purely stylistic/idiomatic and does not alter algorithmic behavior, bounds, or memory safety. No bug fixes, no validation changes, no consensus-critical modifications.
Changed components
src/cluster_linearize.hsrc/txgraph.cppsrc/test/fuzz/txgraph.cppInspect captured patch +35 / −32
diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h
index 1bf3d475..4da4703f 100644
--- a/src/cluster_linearize.h
+++ b/src/cluster_linearize.h
@@ -9,6 +9,7 @@
#include <cstdint>
#include <numeric>
#include <optional>
+#include <ranges>
#include <utility>
#include <vector>
@@ -316,7 +317,7 @@ public:
{
DepGraphIndex old_len = list.size();
for (auto i : select) list.push_back(i);
- std::sort(list.begin() + old_len, list.end(), [&](DepGraphIndex a, DepGraphIndex b) noexcept {
+ std::ranges::sort(std::span{list}.subspan(old_len), [&](DepGraphIndex a, DepGraphIndex b) noexcept {
const auto a_anc_count = entries[a].ancestors.Count();
const auto b_anc_count = entries[b].ancestors.Count();
if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count;
@@ -1618,7 +1619,7 @@ public:
for (auto chunk_idx : m_chunk_idxs) {
ret.push_back(m_set_info[chunk_idx].feerate);
}
- std::sort(ret.begin(), ret.end(), std::greater<ByRatioNegSize<FeeFrac>>{});
+ std::ranges::sort(ret, std::greater<ByRatioNegSize<FeeFrac>>{});
return ret;
}
@@ -1647,8 +1648,8 @@ public:
}
}
}
- std::sort(expected_dependencies.begin(), expected_dependencies.end());
- std::sort(all_dependencies.begin(), all_dependencies.end());
+ std::ranges::sort(expected_dependencies);
+ std::ranges::sort(all_dependencies);
assert(expected_dependencies == all_dependencies);
//
diff --git a/src/test/fuzz/txgraph.cpp b/src/test/fuzz/txgraph.cpp
index e3eb0dd1..88091fb0 100644
--- a/src/test/fuzz/txgraph.cpp
+++ b/src/test/fuzz/txgraph.cpp
@@ -16,6 +16,7 @@
#include <iterator>
#include <map>
#include <memory>
+#include <ranges>
#include <set>
#include <utility>
@@ -433,7 +434,7 @@ FUZZ_TARGET(txgraph)
assert(num_tx == sim.GetTransactionCount());
// Sort by feerate only, since violating topological constraints within same-feerate
// chunks won't affect diagram comparisons.
- std::sort(chunk_feerates.begin(), chunk_feerates.end(), std::greater<ByRatioNegSize<FeeFrac>>{});
+ std::ranges::sort(chunk_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
return chunk_feerates;
};
@@ -1006,7 +1007,7 @@ FUZZ_TARGET(txgraph)
for (auto i : cluster) sizes.push_back(top_sim.graph.FeeRate(i).size);
auto sum_sizes = std::accumulate(sizes.begin(), sizes.end(), uint64_t{0});
// Sort from large to small.
- std::sort(sizes.begin(), sizes.end(), std::greater{});
+ std::ranges::sort(sizes, std::greater{});
// In the worst case, only the smallest transactions are removed.
while (sizes.size() > max_cluster_count || sum_sizes > max_cluster_size) {
sum_sizes -= sizes.back();
@@ -1075,8 +1076,8 @@ FUZZ_TARGET(txgraph)
auto cmp = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
return real->CompareMainOrder(*sims[0].GetRef(a), *sims[0].GetRef(b)) < 0;
};
- std::sort(vec1.begin(), vec1.end(), cmp);
- std::sort(vec2.begin(), vec2.end(), cmp);
+ std::ranges::sort(vec1, cmp);
+ std::ranges::sort(vec2, cmp);
// Verify the resulting orderings are identical. This could only fail if the ordering was
// not total.
@@ -1199,7 +1200,7 @@ FUZZ_TARGET(txgraph)
auto cmp_redo = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
return real_redo->CompareMainOrder(*txobjects_redo[a], *txobjects_redo[b]) < 0;
};
- std::sort(vec_redo.begin(), vec_redo.end(), cmp_redo);
+ std::ranges::sort(vec_redo, cmp_redo);
// Compare with the ordering we got from real.
assert(vec1 == vec_redo);
}
@@ -1274,8 +1275,8 @@ FUZZ_TARGET(txgraph)
// std::set_difference can be used on them below. The exact ordering does not matter
// here, but it has to be consistent with the one used in main_real_diagram and
// stage_real_diagram).
- std::sort(main_cmp_diagram.begin(), main_cmp_diagram.end(), std::greater<ByRatioNegSize<FeeFrac>>{});
- std::sort(stage_cmp_diagram.begin(), stage_cmp_diagram.end(), std::greater<ByRatioNegSize<FeeFrac>>{});
+ std::ranges::sort(main_cmp_diagram, std::greater<ByRatioNegSize<FeeFrac>>{});
+ std::ranges::sort(stage_cmp_diagram, std::greater<ByRatioNegSize<FeeFrac>>{});
// Find the chunks that appear in main_diagram but are missing from main_cmp_diagram.
// This is allowed, because GetMainStagingDiagrams omits clusters in main unaffected
// by staging.
diff --git a/src/txgraph.cpp b/src/txgraph.cpp
index 8993947a..39a8a881 100644
--- a/src/txgraph.cpp
+++ b/src/txgraph.cpp
@@ -14,6 +14,7 @@
#include <compare>
#include <functional>
#include <memory>
+#include <ranges>
#include <set>
#include <span>
#include <unordered_set>
@@ -1212,8 +1213,8 @@ std::vector<Cluster*> TxGraphImpl::GetConflicts() const noexcept
}
}
// Deduplicate the result (the same Cluster may appear multiple times).
- std::sort(ret.begin(), ret.end(), [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
- ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
+ std::ranges::sort(ret, [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
+ ret.erase(std::ranges::unique(ret).begin(), ret.end());
return ret;
}
@@ -1558,7 +1559,7 @@ void SingletonClusterImpl::Merge(TxGraphImpl&, int, Cluster&) noexcept
void GenericClusterImpl::ApplyDependencies(TxGraphImpl& graph, int level, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept
{
// Sort the list of dependencies to apply by child, so those can be applied in batch.
- std::sort(to_apply.begin(), to_apply.end(), [](auto& a, auto& b) { return a.second < b.second; });
+ std::ranges::sort(to_apply, [](auto& a, auto& b) { return a.second < b.second; });
// Iterate over groups of to-be-added dependencies with the same child.
auto it = to_apply.begin();
while (it != to_apply.end()) {
@@ -1720,7 +1721,7 @@ void TxGraphImpl::ApplyRemovals(int up_to_level) noexcept
}
}
// Group the set of to-be-removed entries by Cluster::m_sequence.
- std::sort(to_remove.begin(), to_remove.end(), [&](GraphIndex a, GraphIndex b) noexcept {
+ std::ranges::sort(to_remove, [&](GraphIndex a, GraphIndex b) noexcept {
Cluster* cluster_a = m_entries[a].m_locator[level].cluster;
Cluster* cluster_b = m_entries[b].m_locator[level].cluster;
return CompareClusters(cluster_a, cluster_b) < 0;
@@ -1791,7 +1792,7 @@ void TxGraphImpl::Compact() noexcept
// ones get processed first. This means earlier-processed GraphIndexes will not cause moving of
// later-processed ones during the "swap with end of m_entries" step below (which might
// invalidate them).
- std::sort(m_unlinked.begin(), m_unlinked.end(), std::greater{});
+ std::ranges::sort(m_unlinked, std::greater{});
std::vector<Cluster*> affected_main;
auto last = GraphIndex(-1);
@@ -1817,7 +1818,7 @@ void TxGraphImpl::Compact() noexcept
// Update the affected clusters, to fixup Entry::m_main_max_chunk_fallback values which may
// have become outdated due to the compaction above.
- std::sort(affected_main.begin(), affected_main.end());
+ std::ranges::sort(affected_main);
affected_main.erase(std::unique(affected_main.begin(), affected_main.end()), affected_main.end());
for (Cluster* cluster : affected_main) {
cluster->Updated(*this, /*level=*/0, /*rename=*/true);
@@ -1901,10 +1902,10 @@ void TxGraphImpl::GroupClusters(int level) noexcept
}
// Sort and deduplicate an_clusters, so we end up with a sorted list of all involved Clusters
// to which dependencies apply, or which are oversized.
- std::sort(an_clusters.begin(), an_clusters.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
- an_clusters.erase(std::unique(an_clusters.begin(), an_clusters.end()), an_clusters.end());
+ std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; });
+ an_clusters.erase(std::ranges::unique(an_clusters).begin(), an_clusters.end());
// Sort an_deps by applying the same order to the involved child cluster.
- std::sort(an_deps.begin(), an_deps.end(), [&](auto& a, auto& b) noexcept { return a.second < b.second; });
+ std::ranges::sort(an_deps, [&](auto& a, auto& b) noexcept { return a.second < b.second; });
// Run the union-find algorithm to find partitions of the input Clusters which need to be
// grouped together. See https://en.wikipedia.org/wiki/Disjoint-set_data_structure.
@@ -2017,8 +2018,8 @@ void TxGraphImpl::GroupClusters(int level) noexcept
// Sort both an_clusters and an_deps by sequence number of the representative of the
// partition they are in, grouping all those applying to the same partition together.
- std::sort(an_deps.begin(), an_deps.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
- std::sort(an_clusters.begin(), an_clusters.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
+ std::ranges::sort(an_deps, [](auto& a, auto& b) noexcept { return a.second < b.second; });
+ std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; });
// Translate the resulting cluster groups to the m_group_data structure, and the dependencies
// back to m_deps_to_add.
@@ -2489,7 +2490,7 @@ std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestorsUnion(std::span<const Ref* c
matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster_level].index);
}
// Group by Cluster.
- std::sort(matches.begin(), matches.end(), [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
+ std::ranges::sort(matches, [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
// Dispatch to the Clusters.
std::span match_span(matches);
std::vector<TxGraph::Ref*> ret;
@@ -2522,7 +2523,7 @@ std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendantsUnion(std::span<const Ref*
matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster_level].index);
}
// Group by Cluster.
- std::sort(matches.begin(), matches.end(), [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
+ std::ranges::sort(matches, [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
// Dispatch to the Clusters.
std::span match_span(matches);
std::vector<TxGraph::Ref*> ret;
@@ -2796,7 +2797,7 @@ TxGraph::GraphIndex TxGraphImpl::CountDistinctClusters(std::span<const Ref* cons
if (cluster != nullptr) clusters.push_back(cluster);
}
// Count the number of distinct elements in clusters.
- std::sort(clusters.begin(), clusters.end(), [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
+ std::ranges::sort(clusters, [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
Cluster* last{nullptr};
GraphIndex ret{0};
for (Cluster* cluster : clusters) {
@@ -2827,8 +2828,8 @@ std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>> TxGraphImpl::GetMainStagin
}
}
// Sort both by decreasing feerate to obtain diagrams, and return them.
- std::sort(main_feerates.begin(), main_feerates.end(), std::greater<ByRatioNegSize<FeeFrac>>{});
- std::sort(staging_feerates.begin(), staging_feerates.end(), std::greater<ByRatioNegSize<FeeFrac>>{});
+ std::ranges::sort(main_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
+ std::ranges::sort(staging_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
return std::make_pair(std::move(main_feerates), std::move(staging_feerates));
}
@@ -3401,7 +3402,7 @@ std::vector<TxGraph::Ref*> TxGraphImpl::Trim() noexcept
// Sort the trim data by GraphIndex. In what follows, we will treat this sorted vector as
// a map from GraphIndex to TrimTxData via locate_fn, and its ordering will not change
// anymore.
- std::sort(trim_data.begin(), trim_data.end(), [](auto& a, auto& b) noexcept { return a.m_index < b.m_index; });
+ std::ranges::sort(trim_data, [](auto& a, auto& b) noexcept { return a.m_index < b.m_index; });
// Add the explicitly added dependencies to deps_by_child.
deps_by_child.insert(deps_by_child.end(),
@@ -3410,7 +3411,7 @@ std::vector<TxGraph::Ref*> TxGraphImpl::Trim() noexcept
// Sort deps_by_child by child transaction GraphIndex. The order will not be changed
// anymore after this.
- std::sort(deps_by_child.begin(), deps_by_child.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
+ std::ranges::sort(deps_by_child, [](auto& a, auto& b) noexcept { return a.second < b.second; });
// Fill m_parents_count and m_parents_offset in trim_data, as well as m_deps_left, and
// initially populate trim_heap. Because of the sort above, all dependencies involving the
// same child are grouped together, so a single linear scan suffices.
@@ -3434,7 +3435,7 @@ std::vector<TxGraph::Ref*> TxGraphImpl::Trim() noexcept
// Construct deps_by_parent, sorted by parent transaction GraphIndex. The order will not be
// changed anymore after this.
deps_by_parent = deps_by_child;
- std::sort(deps_by_parent.begin(), deps_by_parent.end(), [](auto& a, auto& b) noexcept { return a.first < b.first; });
+ std::ranges::sort(deps_by_parent, [](auto& a, auto& b) noexcept { return a.first < b.first; });
// Fill m_children_offset and m_children_count in trim_data. Because of the sort above, all
// dependencies involving the same parent are grouped together, so a single linear scan
// suffices.
@@ -3482,8 +3483,8 @@ std::vector<TxGraph::Ref*> TxGraphImpl::Trim() noexcept
Assume(chl == entry.m_index);
current_deps.push_back(find_fn(&*locate_fn(par)));
}
- std::sort(current_deps.begin(), current_deps.end());
- current_deps.erase(std::unique(current_deps.begin(), current_deps.end()), current_deps.end());
+ std::ranges::sort(current_deps);
+ current_deps.erase(std::ranges::unique(current_deps).begin(), current_deps.end());
// Compute resource counts.
uint32_t new_count = 1;
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.