diff --git a/README.md b/README.md index c5ac828..1763a67 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,14 @@ + + + +
graph-v3 logo + # graph-v3 **A modern C++20 graph library — header-only, descriptor-based, works with your containers.** +
+ [![C++ Standard](https://img.shields.io/badge/C%2B%2B-20-blue.svg)](https://en.cppreference.com/w/cpp/20) @@ -17,7 +24,7 @@ - **13 algorithms** — Dijkstra, Bellman-Ford, BFS, DFS, topological sort, connected components, articulation points, biconnected components, MST, triangle counting, MIS, label propagation, Jaccard coefficient - **7 lazy views** — vertexlist, edgelist, incidence, neighbors, BFS, DFS, topological sort — all composable with range adaptors - **Customization Point Objects (CPOs)** — adapt existing data structures without modifying them -- **3 containers, 26 trait combinations** — `dynamic_graph`, `compressed_graph`, `undirected_adjacency_list` with mix-and-match vertex/edge storage +- **3 containers, 27 trait combinations** — `dynamic_graph`, `compressed_graph`, `undirected_adjacency_list` with mix-and-match vertex/edge storage - **4261 tests passing** — comprehensive Catch2 test suite --- @@ -78,7 +85,7 @@ Both share a common descriptor system and customization-point interface. |----------|-----------------|---------| | **Algorithms** | Dijkstra, Bellman-Ford, BFS, DFS, topological sort, connected components, articulation points, biconnected components, MST, triangle counting, MIS, label propagation, Jaccard | [Algorithm reference](docs/status/implementation_matrix.md#algorithms) | | **Views** | vertexlist, edgelist, incidence, neighbors, BFS, DFS, topological sort | [View reference](docs/status/implementation_matrix.md#views) | -| **Containers** | `dynamic_graph` (26 trait combos), `compressed_graph` (CSR), `undirected_adjacency_list` | [Container reference](docs/status/implementation_matrix.md#containers) | +| **Containers** | `dynamic_graph` (27 trait combos), `compressed_graph` (CSR), `undirected_adjacency_list` | [Container reference](docs/status/implementation_matrix.md#containers) | | **CPOs** | 19 customization point objects (vertices, edges, target_id, vertex_value, edge_value, …) | [CPO reference](docs/reference/cpo-reference.md) | | **Concepts** | 9 graph concepts (edge, vertex, adjacency_list, …) | [Concepts reference](docs/reference/concepts.md) | @@ -174,4 +181,4 @@ Distributed under the [Boost Software License 1.0](LICENSE). --- -**Status:** 4261 / 4261 tests passing · 13 algorithms · 7 views · 3 containers · 26 trait combinations · C++20 · BSL-1.0 +**Status:** 4261 / 4261 tests passing · 13 algorithms · 7 views · 3 containers · 27 trait combinations · C++20 · BSL-1.0 diff --git a/agents/archive/dynamic_graph_todo.md b/agents/archive/dynamic_graph_todo.md index af5b227..d7cb4a1 100644 --- a/agents/archive/dynamic_graph_todo.md +++ b/agents/archive/dynamic_graph_todo.md @@ -20,8 +20,8 @@ - ✅ uous (unordered_map + unordered_set): Basic + CPO tests COMPLETE (53 test cases, 563 assertions) **Phase 4.3: Map-Based Edge Containers - COMPLETE ✅** -- ✅ voem (vector + map edges): Basic + CPO tests COMPLETE (46 test cases, 292 assertions) -- ✅ moem (map + map edges): Basic + CPO tests COMPLETE (53 test cases, 578 assertions) +- ✅ vom (vector + map edges): Basic + CPO tests COMPLETE (46 test cases, 292 assertions) +- ✅ mom (map + map edges): Basic + CPO tests COMPLETE (53 test cases, 578 assertions) **Phase 4 Overall: 100% COMPLETE ✅ (10/10 traits implemented)** @@ -102,8 +102,8 @@ - test_dynamic_graph_uous.cpp + test_dynamic_graph_cpo_uous.cpp ✅ *Map Edge Containers (4 files):* -- test_dynamic_graph_voem.cpp + test_dynamic_graph_cpo_voem.cpp ✅ -- test_dynamic_graph_moem.cpp + test_dynamic_graph_cpo_moem.cpp ✅ +- test_dynamic_graph_vom.cpp + test_dynamic_graph_cpo_vom.cpp ✅ +- test_dynamic_graph_mom.cpp + test_dynamic_graph_cpo_mom.cpp ✅ *Additional Test Files:* - test_dynamic_graph_common.cpp ✅ @@ -965,42 +965,42 @@ All prerequisites for both std::set (Phase 4.1) and std::unordered_set (Phase 4. | 4.3.1b | Identify changes needed for map-based edge access (key vs iterator) | ✅ DONE | | 4.3.1c | Design edge_descriptor changes for map-based edges (if any) | ✅ DONE | -**Step 4.3.2: Create voem_graph_traits (vector + edge map)** ✅ **COMPLETE** (2024-12-28) +**Step 4.3.2: Create vom_graph_traits (vector + edge map)** ✅ **COMPLETE** (2024-12-28) **Implementation Summary:** - Added is_map_based_edge_container concept to container_utility.hpp - Added emplace_edge helper for pair-wrapped edge insertion - Updated edge_descriptor::target_id() to unwrap map pairs - Updated edge_value CPO to extract values from map pairs -- Created voem_graph_traits.hpp with std::map edges -- Created test_dynamic_graph_voem.cpp (~741 lines, 25 test cases) -- Created test_dynamic_graph_cpo_voem.cpp (~1273 lines, 21 test cases) +- Created vom_graph_traits.hpp with std::map edges +- Created test_dynamic_graph_vom.cpp (~741 lines, 25 test cases) +- Created test_dynamic_graph_cpo_vom.cpp (~1273 lines, 21 test cases) - All 46 test cases passing | Step | Task | Status | |------|------|--------| -| 4.3.2a | Create voem_graph_traits.hpp | ✅ DONE | +| 4.3.2a | Create vom_graph_traits.hpp | ✅ DONE | | 4.3.2b | Update load_edges or edge insertion for map semantics | ✅ DONE | -| 4.3.2c | Create test_dynamic_graph_voem.cpp basic tests (~800 lines) | ✅ DONE | -| 4.3.2d | Create test_dynamic_graph_cpo_voem.cpp CPO tests (~1200 lines) | ✅ DONE | +| 4.3.2c | Create test_dynamic_graph_vom.cpp basic tests (~800 lines) | ✅ DONE | +| 4.3.2d | Create test_dynamic_graph_cpo_vom.cpp CPO tests (~1200 lines) | ✅ DONE | | 4.3.2e | Update CMakeLists.txt and verify tests pass | ✅ DONE | -**Step 4.3.3: Create moem_graph_traits (map vertices + edge map)** ✅ **COMPLETE** (2024-12-28) +**Step 4.3.3: Create mom_graph_traits (map vertices + edge map)** ✅ **COMPLETE** (2024-12-28) **Implementation Summary:** -- Created moem_graph_traits.hpp with std::map vertices and std::map edges +- Created mom_graph_traits.hpp with std::map vertices and std::map edges - Simplified operator[] using at() for both container types (throws if not found) - Added is_map_based_vertex_container concept - Tests derived from mos (which also has map vertices) -- Created test_dynamic_graph_moem.cpp (~1123 lines, 27 test cases) -- Created test_dynamic_graph_cpo_moem.cpp (~1274 lines, 26 test cases) +- Created test_dynamic_graph_mom.cpp (~1123 lines, 27 test cases) +- Created test_dynamic_graph_cpo_mom.cpp (~1274 lines, 26 test cases) - All 53 test cases passing | Step | Task | Status | |------|------|--------| -| 4.3.3a | Create moem_graph_traits.hpp | ✅ DONE | -| 4.3.3b | Create test_dynamic_graph_moem.cpp (~800 lines) | ✅ DONE | -| 4.3.3c | Create test_dynamic_graph_cpo_moem.cpp (~1200 lines) | ✅ DONE | +| 4.3.3a | Create mom_graph_traits.hpp | ✅ DONE | +| 4.3.3b | Create test_dynamic_graph_mom.cpp (~800 lines) | ✅ DONE | +| 4.3.3c | Create test_dynamic_graph_cpo_mom.cpp (~1200 lines) | ✅ DONE | | 4.3.3d | Update CMakeLists.txt and verify tests pass | ✅ DONE | --- @@ -1010,7 +1010,7 @@ All prerequisites for both std::set (Phase 4.1) and std::unordered_set (Phase 4. **Total New Traits:** - Set edges: vos, dos, mos, uos (4 traits) - Unordered set edges: vous, dous, mous, uous (4 traits) -- Map edges: voem, moem (2 traits) +- Map edges: vom, mom (2 traits) **Implementation Changes:** - operator<=> for dynamic_edge (generates <, >, <=, >=) @@ -1038,8 +1038,8 @@ that map/unordered_map-based vertex containers work correctly with various ID ty the standard integral types. **Current State Analysis:** -- Map-based containers (mos, moem, mol, etc.) already support `std::string` vertex IDs -- Tests exist with `std::string` IDs in: mos, mous, moem, mol, mov, mod, mofl tests +- Map-based containers (mos, mom, mol, etc.) already support `std::string` vertex IDs +- Tests exist with `std::string` IDs in: mos, mous, mom, mol, mov, mod, mofl tests - Basic string ID functionality is validated but not comprehensively tested - No tests exist for: double IDs, compound types, custom types with complex comparison @@ -1088,7 +1088,7 @@ the standard integral types. |-------|-------------|-----------|-----------------| | mos | 3 test cases | 22 sections | ✅ Comprehensive | | mous | 3 test cases | 22 sections | ✅ Comprehensive | -| moem | 3 test cases | 22 sections | ✅ Comprehensive | +| mom | 3 test cases | 22 sections | ✅ Comprehensive | | mol | 3 test cases | ~20 sections | ✅ Comprehensive | | mov | 3 test cases | ~20 sections | ✅ Comprehensive | | mod | 3 test cases | ~20 sections | ✅ Comprehensive | @@ -2080,7 +2080,7 @@ For Phase 5 (non-integral): 1. **Finish Phase 4.1.4** - Complete mos CPO tests (1-2 days) 2. **Phase 4.1.5** - uos (unordered_map + set) basic + CPO tests (2-3 days) 3. **Phase 4.2** - Unordered set edge containers (vous, dous, mous, uous) (4-5 days) -4. **Phase 4.3** - Map-based edge containers (voem, moem) (3-4 days) +4. **Phase 4.3** - Map-based edge containers (vom, mom) (3-4 days) 5. **Phase 7.3** - Edge cases (ongoing, 2-3 days) 6. **Phase 6** - Integration tests (2-3 days) 7. **Phase 7.1** - Mutation operations (2-3 days) diff --git a/agents/archive/test_reorganization_execution_plan.md b/agents/archive/test_reorganization_execution_plan.md index c567000..9569e0d 100644 --- a/agents/archive/test_reorganization_execution_plan.md +++ b/agents/archive/test_reorganization_execution_plan.md @@ -149,8 +149,8 @@ git mv tests/test_dynamic_graph_uol.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_uov.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_uod.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_vos.cpp tests/container/dynamic_graph/ -git mv tests/test_dynamic_graph_voem.cpp tests/container/dynamic_graph/ -git mv tests/test_dynamic_graph_moem.cpp tests/container/dynamic_graph/ +git mv tests/test_dynamic_graph_vom.cpp tests/container/dynamic_graph/ +git mv tests/test_dynamic_graph_mom.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_dos.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_mos.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_uos.cpp tests/container/dynamic_graph/ @@ -190,8 +190,8 @@ git mv tests/test_dynamic_graph_cpo_uol.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_uov.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_uod.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_vos.cpp tests/container/dynamic_graph/ -git mv tests/test_dynamic_graph_cpo_voem.cpp tests/container/dynamic_graph/ -git mv tests/test_dynamic_graph_cpo_moem.cpp tests/container/dynamic_graph/ +git mv tests/test_dynamic_graph_cpo_vom.cpp tests/container/dynamic_graph/ +git mv tests/test_dynamic_graph_cpo_mom.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_dos.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_mos.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_uos.cpp tests/container/dynamic_graph/ @@ -309,8 +309,8 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_uov.cpp dynamic_graph/test_dynamic_graph_uod.cpp dynamic_graph/test_dynamic_graph_vos.cpp - dynamic_graph/test_dynamic_graph_voem.cpp - dynamic_graph/test_dynamic_graph_moem.cpp + dynamic_graph/test_dynamic_graph_vom.cpp + dynamic_graph/test_dynamic_graph_mom.cpp dynamic_graph/test_dynamic_graph_dos.cpp dynamic_graph/test_dynamic_graph_mos.cpp dynamic_graph/test_dynamic_graph_uos.cpp @@ -350,8 +350,8 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_cpo_uov.cpp dynamic_graph/test_dynamic_graph_cpo_uod.cpp dynamic_graph/test_dynamic_graph_cpo_vos.cpp - dynamic_graph/test_dynamic_graph_cpo_voem.cpp - dynamic_graph/test_dynamic_graph_cpo_moem.cpp + dynamic_graph/test_dynamic_graph_cpo_vom.cpp + dynamic_graph/test_dynamic_graph_cpo_mom.cpp dynamic_graph/test_dynamic_graph_cpo_dos.cpp dynamic_graph/test_dynamic_graph_cpo_mos.cpp dynamic_graph/test_dynamic_graph_cpo_uos.cpp diff --git a/agents/archive/view_plan.md b/agents/archive/view_plan.md index ed155ba..727bbad 100644 --- a/agents/archive/view_plan.md +++ b/agents/archive/view_plan.md @@ -739,7 +739,7 @@ Created `tests/views/test_edgelist.cpp`: - Value function receives descriptor - Structured bindings work: `for (auto [e] : edgelist(g))` and `for (auto [e, val] : edgelist(g, evf))` - Tests pass with sanitizers -- Map-based containers supported (vov, voem, mov, moem) +- Map-based containers supported (vov, vom, mov, mom) **Commit Message**: ``` diff --git a/agents/archive/view_strategy.md b/agents/archive/view_strategy.md index 216389c..7c80212 100644 --- a/agents/archive/view_strategy.md +++ b/agents/archive/view_strategy.md @@ -666,11 +666,11 @@ with both index-based and iterator-based vertex/edge storage: **Edge Container Types**: - `vector` - Random-access edges -- `map` (voem, moem, etc.) - Sorted edges by target, deduplicated +- `map` (vom, mom, etc.) - Sorted edges by target, deduplicated - `list` - Forward-only edges **Test Matrix** (minimum coverage per view): -| View | vov | mov | voem | moem | +| View | vov | mov | vom | mom | |------|-----|-----|------|------| | vertexlist | ✓ | ✓ | ✓ | ✓ | | incidence | ✓ | ✓ | ✓ | ✓ | diff --git a/agents/bgl2_comparison_result.md b/agents/bgl2_comparison_result.md index 416e1ec..9caed7a 100644 --- a/agents/bgl2_comparison_result.md +++ b/agents/bgl2_comparison_result.md @@ -435,9 +435,9 @@ using G = dynamic_graph` | Traits-configured vertex and edge containers | Mutable | General purpose, flexible container choice | | `compressed_graph` | CSR (compressed sparse row) | Immutable after construction | Read-only, high-performance, memory-compact | -| `undirected_adjacency_list` | Dual doubly-linked lists per edge | Mutable, O(1) edge removal | Undirected graphs, frequent edge insertion/removal | +| `undirected_adjacency_list` | Dual doubly-linked lists per edge | Mutable, O(1) edge removal | Undirected graphs, frequent edge insertion/removal | **Full `dynamic_graph` container matrix (26 combinations):** @@ -278,7 +278,7 @@ Edge containers: Traits naming convention: `{vertex}o{edge}_graph_traits` (e.g., `vov_graph_traits` = vector vertices, vector edges). -All 26 combinations listed with trait file names (vov, vod, vofl, vol, vos, vous, voem, dov, dod, dofl, dol, dos, dous, mov, mod, mofl, mol, mos, mous, moem, uov, uod, uofl, uol, uos, uous). +All 27 combinations listed with trait file names (vov, vod, vofl, vol, vos, vous, vom, voum, dov, dod, dofl, dol, dos, dous, mov, mod, mofl, mol, mos, mous, mom, uov, uod, uofl, uol, uos, uous). ### Phase 4: Reference diff --git a/agents/incoming_edges_design.md b/agents/incoming_edges_design.md new file mode 100644 index 0000000..f32acf9 --- /dev/null +++ b/agents/incoming_edges_design.md @@ -0,0 +1,833 @@ +# Incoming Edge Support — Design Document + +## 1. Problem Statement + +The graph-v3 library currently provides **outgoing edge access only**. All edge CPOs +(`edges()`, `degree()`, `target_id()`), views (`incidence`, `edgelist`, `neighbors`), +concepts (`adjacency_list`, `vertex_edge_range`), and algorithms are implicitly outgoing. +There is no way to query "which edges point **to** a vertex." + +This document proposes extending the library to support **incoming edges** while +preserving full backward compatibility with existing code. + +--- + +## 2. Design Principles + +1. **Mirror, don't replace.** Every new incoming-edge facility mirrors an existing + outgoing-edge facility with a consistent naming convention. +2. **Backward compatible.** Existing code compiles unchanged. The current `edges()`, + `degree()`, etc. continue to mean "outgoing." +3. **Opt-in.** Graphs that don't store reverse adjacency data simply don't model the + new concepts — algorithms gracefully fall back to outgoing-only behavior. +4. **Consistent naming.** Incoming counterparts use an `in_` prefix; where ambiguity + arises, outgoing counterparts gain an `out_` alias. +5. **Independent edge types.** `in_edge_t` and `edge_t` (i.e. `out_edge_t`) + are independently deduced from their respective ranges. They are commonly the same + type but this is **not** required. For example, in a distributed graph the outgoing + edges may carry full property data while incoming edges are lightweight back-pointers + (just a source vertex ID). In a CSR+CSC compressed graph, the CSC incoming entries + may store only a column index plus a back-reference into the CSR value array. + +--- + +## 3. Naming Convention + +### 3.1 Existing names (no rename required) + +The current names remain as-is and **continue to mean "outgoing edges"**: + +| Current Name | Stays | Meaning | +|---|---|---| +| `edges(g, u)` | Yes | Outgoing edges from vertex `u` | +| `degree(g, u)` | Yes | Out-degree of `u` | +| `target_id(g, uv)` | Yes | Target vertex of edge `uv` | +| `source_id(g, uv)` | Yes | Source vertex of edge `uv` | +| `target(g, uv)` | Yes | Target vertex descriptor | +| `source(g, uv)` | Yes | Source vertex descriptor | +| `num_edges(g)` | Yes | Total edge count (direction-agnostic) | +| `find_vertex_edge(g, u, v)` | Yes | Find outgoing edge from `u` to `v` | +| `contains_edge(g, u, v)` | Yes | Check outgoing edge from `u` to `v` | +| `has_edge(g)` | Yes | Check whether graph has at least one edge | +| `edge_value(g, uv)` | Yes | Edge property (direction-agnostic) | + +### 3.2 New outgoing aliases (optional clarity) + +For code that wants to be explicit, introduce **aliases** that forward to the +existing CPOs. These are convenience only — not required: + +| New Alias | Forwards To | +|---|---| +| `out_edges(g, u)` | `edges(g, u)` | +| `out_degree(g, u)` | `degree(g, u)` | +| `find_out_edge(g, u, v)` | `find_vertex_edge(g, u, v)` | + +These are defined as inline constexpr CPO aliases or thin wrapper functions in +`graph::adj_list` and re-exported to `graph::`. + +> **Design decision (2026-02-21):** These aliases are **retained**. See +> [Appendix D](#appendix-d-out_-alias-retention-decision) for the full trade-off +> analysis. + +### 3.3 New incoming names + +| New Name | Meaning | +|---|---| +| `in_edges(g, u)` / `in_edges(g, uid)` | Incoming edges to vertex `u` | +| `in_degree(g, u)` / `in_degree(g, uid)` | In-degree of `u` | +| `find_in_edge(g, u, v)` | Find incoming edge from `v` to `u` (both descriptors) | +| `find_in_edge(g, u, vid)` | Find incoming edge from `vid` to `u` (descriptor + ID) | +| `find_in_edge(g, uid, vid)` | Find incoming edge from `vid` to `uid` (both IDs) | +| `contains_in_edge(g, u, v)` | Check incoming edge from `v` to `u` (both descriptors) | +| `contains_in_edge(g, uid, vid)` | Check incoming edge from `vid` to `uid` (both IDs) | + +Parameter convention for incoming-edge queries: + +- `u`/`uid` = **target** vertex (the vertex receiving incoming edges) +- `v`/`vid` = **source** vertex (the vertex the edge comes from) + +Example: + +```cpp +// true if edge 7 -> 3 exists +bool b = contains_in_edge(g, /*target=*/3, /*source=*/7); +``` + +--- + +## 4. New CPOs + +### 4.1 `in_edges(g, u)` / `in_edges(g, uid)` + +**File:** `include/graph/adj_list/detail/graph_cpo.hpp` (new section) + +**Resolution order** (mirrors `edges()`): + +| Priority | Strategy | Expression | +|---|---|---| +| 1 | `_vertex_member` | `u.inner_value(g).in_edges()` | +| 2 | `_adl` | ADL `in_edges(g, u)` | + +For the `(g, uid)` overload: + +| Priority | Strategy | Expression | +|---|---|---| +| 1 | `_adl` | ADL `in_edges(g, uid)` | +| 2 | `_default` | `in_edges(g, *find_vertex(g, uid))` | + +**Return type:** `in_vertex_edge_range_t` — i.e. the exact range type returned by +`in_edges(g, u)` after CPO resolution. This may be an `edge_descriptor_view`, but +custom member/ADL implementations may return a different valid range type. + +### 4.2 `in_degree(g, u)` / `in_degree(g, uid)` + +**File:** `include/graph/adj_list/detail/graph_cpo.hpp` (new section) + +**Resolution order** (mirrors `degree()`): + +| Priority | Strategy | Expression | +|---|---|---| +| 1 | `_member` | `g.in_degree(u)` | +| 2 | `_adl` | ADL `in_degree(g, u)` | +| 3 | `_default` | `size(in_edges(g, u))` or `distance(in_edges(g, u))` | + +### 4.3 `out_edges` / `out_degree` / `find_out_edge` (aliases) + +**File:** `include/graph/adj_list/detail/graph_cpo.hpp` + +```cpp +inline constexpr auto& out_edges = edges; +inline constexpr auto& out_degree = degree; +inline constexpr auto& find_out_edge = find_vertex_edge; +``` + +### 4.4 `find_in_edge`, `contains_in_edge` + +These mirror `find_vertex_edge` (`find_out_edge`) / `contains_edge` but operate +on the reverse adjacency structure. Implementation follows the same +member → ADL → default cascade pattern. + +`find_in_edge` has three overloads mirroring `find_vertex_edge`: +- `find_in_edge(g, u, v)` — both vertex descriptors +- `find_in_edge(g, u, vid)` — descriptor + vertex ID +- `find_in_edge(g, uid, vid)` — both vertex IDs + +`contains_in_edge` has two overloads mirroring `contains_edge`: +- `contains_in_edge(g, u, v)` — both vertex descriptors +- `contains_in_edge(g, uid, vid)` — both vertex IDs + +The default implementation of `find_in_edge` iterates `in_edges(g, u)` and +matches `source_id(g, ie)` against the target vertex (the in-neighbor). The default +`contains_in_edge` calls `find_in_edge` and checks the result. + +> **Note:** There is no `has_in_edge` counterpart. The existing `has_edge(g)` takes +> only the graph and means "graph has at least one edge" — a direction-agnostic +> query. A `has_in_edge(g)` variant would be redundant with `has_edge(g)`, and +> `has_in_edge(g, uid, vid)` would duplicate `contains_in_edge(g, uid, vid)`. + +--- + +## 5. New Type Aliases + +**File:** `include/graph/adj_list/detail/graph_cpo.hpp` (after `in_edges` CPO) + +| Alias | Definition | +|---|---| +| `in_vertex_edge_range_t` | `decltype(in_edges(g, vertex_t))` | +| `in_vertex_edge_iterator_t` | `ranges::iterator_t>` | +| `in_edge_t` | `ranges::range_value_t>` | + +`in_edge_t` is **independently deduced** from the `in_edges()` return range. +It is commonly the same type as `edge_t` but this is not required. See +Design Principle 5 and Appendix C for rationale. + +Also add outgoing aliases for explicitness: + +| Alias | Definition | +|---|---| +| `out_vertex_edge_range_t` | Same as `vertex_edge_range_t` | +| `out_vertex_edge_iterator_t` | Same as `vertex_edge_iterator_t` | +| `out_edge_t` | Same as `edge_t` | + +--- + +## 6. New Concepts + +**File:** `include/graph/adj_list/adjacency_list_concepts.hpp` + +### 6.1 `in_vertex_edge_range` + +```cpp +template +concept in_vertex_edge_range = + std::ranges::forward_range; +``` + +Note: this is intentionally **less constrained** than `vertex_edge_range` (which +requires `edge>`). Incoming edges need only be iterable — +their element type (`in_edge_t`) may be a lightweight back-pointer that does +not satisfy the full `edge` concept. The minimum requirement is that +`source_id(g, ie)` can extract the neighbor vertex ID (see §6.2). + +When `in_edge_t` and `edge_t` are the same type, the range will naturally +model `vertex_edge_range` as well. + +### 6.2 `bidirectional_adjacency_list` + +```cpp +template +concept bidirectional_adjacency_list = + adjacency_list && + requires(G& g, vertex_t u, in_edge_t ie) { + { in_edges(g, u) } -> in_vertex_edge_range; + { source_id(g, ie) } -> std::convertible_to>; + // Note: no edge_value() requirement on incoming edges + }; +``` + +A graph that models `bidirectional_adjacency_list` supports both `edges(g, u)` +(outgoing) and `in_edges(g, u)` (incoming). The only requirement on incoming +edge elements is that the neighbor vertex ID (the edge's source) can be extracted +via `source_id()`. Notably, `edge_value(g, ie)` is **not** required — incoming +edges may be valueless back-pointers. + +When `in_edge_t == edge_t`, `edge_value()` will work on incoming edges +automatically. + +### 6.3 `index_bidirectional_adjacency_list` + +```cpp +template +concept index_bidirectional_adjacency_list = + bidirectional_adjacency_list && index_vertex_range; +``` + +--- + +## 7. New Traits + +**File:** `include/graph/adj_list/adjacency_list_traits.hpp` + +| Trait | Constraint | +|---|---| +| `has_in_degree` | `in_degree(g, u)` and `in_degree(g, uid)` both return integral | +| `has_in_degree_v` | `bool` shorthand | +| `has_find_in_edge` | `find_in_edge` CPO resolves for graph type `G` | +| `has_find_in_edge_v` | `bool` shorthand | +| `has_contains_in_edge` | `contains_in_edge` CPO resolves for graph type `G` | +| `has_contains_in_edge_v` | `bool` shorthand | + +--- + +## 8. New Views + +### 8.1 `in_incidence` view + +**File:** `include/graph/views/in_incidence.hpp` (new file) + +Mirrors `incidence_view` from `include/graph/views/incidence.hpp`. + +| Class | Yields | Description | +|---|---|---| +| `in_incidence_view` | `edge_info{sid, uv}` | Iterates incoming edges to a vertex, yielding **source_id** + edge descriptor | +| `in_incidence_view` | `edge_info{sid, uv, val}` | Same, with value function | +| `basic_in_incidence_view` | `edge_info{sid}` | Source ID only | +| `basic_in_incidence_view` | `edge_info{sid, val}` | Source ID + value function | + +**Factory functions:** +```cpp +namespace graph::views { + in_incidence(g, u) // → in_incidence_view + in_incidence(g, uid) // → in_incidence_view + in_incidence(g, u, evf) // → in_incidence_view + in_incidence(g, uid, evf) // → in_incidence_view + basic_in_incidence(g, uid) // → basic_in_incidence_view + basic_in_incidence(g, uid, evf) // → basic_in_incidence_view +} +``` + +**Key difference from `incidence_view`:** The standard `incidence_view` iterates +`edges(g, u)` and yields `target_id` per edge. The `in_incidence_view` iterates +`in_edges(g, u)` and yields `source_id` per edge (the vertex the edge comes from). + +**Edge type note:** The `EVF` value function receives `in_edge_t`, which may +differ from `edge_t`. When incoming edges are valueless back-pointers, the +`void` (no value function) variants are the natural choice. + +### 8.2 `in_neighbors` view + +**File:** `include/graph/views/in_neighbors.hpp` (new file) + +Mirrors `neighbors_view` from `include/graph/views/neighbors.hpp`. + +| Class | Yields | Description | +|---|---|---| +| `in_neighbors_view` | `neighbor_info{sid, n}` | Iterates source vertices of incoming edges | +| `in_neighbors_view` | `neighbor_info{sid, n, val}` | Same, with vertex value function | +| `basic_in_neighbors_view` | `neighbor_info{sid}` | Source ID only | +| `basic_in_neighbors_view` | `neighbor_info{sid, val}` | Source ID + value function | + +### 8.3 Outgoing aliases (optional) + +For symmetry, add aliases in the `graph::views` namespace: + +```cpp +namespace graph::views { + inline constexpr auto& out_incidence = incidence; + inline constexpr auto& basic_out_incidence = basic_incidence; + inline constexpr auto& out_neighbors = neighbors; + inline constexpr auto& basic_out_neighbors = basic_neighbors; +} +``` + +### 8.4 Pipe-syntax adaptors + +**File:** `include/graph/views/adaptors.hpp` (extend existing) + +The existing `adaptors.hpp` provides pipe-syntax closures for all outgoing views +(`g | incidence(uid)`, `g | neighbors(uid)`, etc.). Corresponding closures must +be added for the new incoming views: + +| Pipe expression | Expands to | +|---|---| +| `g \| in_incidence(uid)` | `in_incidence(g, uid)` | +| `g \| in_incidence(uid, evf)` | `in_incidence(g, uid, evf)` | +| `g \| basic_in_incidence(uid)` | `basic_in_incidence(g, uid)` | +| `g \| basic_in_incidence(uid, evf)` | `basic_in_incidence(g, uid, evf)` | +| `g \| in_neighbors(uid)` | `in_neighbors(g, uid)` | +| `g \| in_neighbors(uid, vvf)` | `in_neighbors(g, uid, vvf)` | +| `g \| basic_in_neighbors(uid)` | `basic_in_neighbors(g, uid)` | +| `g \| basic_in_neighbors(uid, vvf)` | `basic_in_neighbors(g, uid, vvf)` | + +Outgoing aliases for symmetry: + +| Pipe alias | Forwards to | +|---|---| +| `g \| out_incidence(uid)` | `g \| incidence(uid)` | +| `g \| out_neighbors(uid)` | `g \| neighbors(uid)` | +| `g \| basic_out_incidence(uid)` | `g \| basic_incidence(uid)` | +| `g \| basic_out_neighbors(uid)` | `g \| basic_neighbors(uid)` | + +--- + +## 9. BFS/DFS/Topological-Sort Parameterization + +### Problem + +`bfs.hpp`, `dfs.hpp`, and `topological_sort.hpp` hardcode `adj_list::edges(g, vertex)` +(5 locations in BFS, 5 in DFS, 7 in topological sort). +To support reverse traversal (following incoming edges), they need parameterization. + +### Solution + +Add an **edge accessor** template parameter defaulting to the current outgoing behavior: + +```cpp +// Default edge accessor — current behavior +struct out_edges_accessor { + template + constexpr auto operator()(G& g, vertex_t u) const { + return adj_list::edges(g, u); + } +}; + +// Reverse edge accessor +struct in_edges_accessor { + template + constexpr auto operator()(G& g, vertex_t u) const { + return adj_list::in_edges(g, u); + } +}; + +template +class vertices_bfs_view { ... }; + +template +class edges_bfs_view { ... }; +``` + +Factory functions add overloads accepting the accessor: +```cpp +// Existing (unchanged) +vertices_bfs(g, seed) +vertices_bfs(g, seed, vvf) + +// New +vertices_bfs(g, seed, vvf, in_edges_accessor{}) // reverse BFS +edges_bfs(g, seed, evf, in_edges_accessor{}) // reverse BFS +``` + +Same pattern applies to DFS views (`vertices_dfs_view`, `edges_dfs_view`) and +topological sort views (`vertices_topological_sort_view`, `edges_topological_sort_view`). +All three view families receive identical `EdgeAccessor` parameterization. + +--- + +## 10. Container Support + +### 10.1 `dynamic_graph` — Reverse Adjacency Storage + +The simplest approach is a **separate reverse adjacency list** stored alongside the +forward list. This is a well-known pattern (e.g., Boost.Graph's `bidirectionalS`). + +**Option A: Built-in template parameter** + +Add a `bool Bidirectional = false` template parameter to `dynamic_graph_base`: + +```cpp +template +class dynamic_graph_base; +``` + +When `Bidirectional = true`: +- Each vertex stores two edge containers: `out_edges_` and `in_edges_` +- `create_edge(u, v)` inserts into `u.out_edges_` and `v.in_edges_` +- `erase_edge` removes from both lists +- The `edges(g, u)` CPO returns `u.out_edges_` (unchanged) +- The `in_edges(g, u)` CPO returns `u.in_edges_` + +The in-edge container element type may differ from the out-edge element type. +The default stores the same `EV` (so `in_edge_t == edge_t`), but an +additional template parameter (e.g., `InEV = EV`) can allow lightweight +back-pointer in-edges (e.g., just a source vertex ID) for reduced storage. + +When `Bidirectional = false`: +- Behavior is identical to today (no storage overhead) + +**Option B: External reverse index wrapper** + +A `reverse_adjacency` adaptor that wraps an existing graph and provides +`in_edges()` via a separately-constructed reverse index. This avoids modifying +`dynamic_graph` at all but adds a construction cost: + +```cpp +auto rev = graph::reverse_adjacency(g); +for (auto [sid, uv] : views::in_incidence(rev, target_vertex)) { ... } +``` + +**Recommendation:** Option A for tight integration. Option B as an additional +utility for read-only reverse queries on existing graphs. + +### 10.2 `compressed_graph` — CSC (Compressed Sparse Column) Format + +The CSR format stores outgoing edges efficiently. The CSC (Compressed Sparse Column) +format stores incoming edges. A bidirectional `compressed_graph` would store both: + +- `row_index_` + `col_index_` (CSR, outgoing — existing) +- `col_ptr_` + `row_index_csc_` (CSC, incoming — new) + +This is standard in sparse matrix libraries (e.g., Eigen stores both CSR and CSC). + +**Implementation:** Add a `bool Bidirectional = false` template parameter. When enabled, +the constructor builds the CSC representation from the input edges alongside the CSR. + +### 10.3 `undirected_adjacency_list` — Already bidirectional + +The `undirected_adjacency_list` already stores edges in both endpoint vertex lists +via `inward_list`/`outward_list` links. For undirected graphs, `edges(g, u)` and +`in_edges(g, u)` return the **same** range — every edge is both incoming and outgoing. + +**Implementation:** Provide `in_edges()` as an ADL friend that returns the same +`edge_descriptor_view` as `edges()`. Similarly, `in_degree()` returns the same value +as `degree()`. This allows undirected graphs to model `bidirectional_adjacency_list` +at zero cost. + +### 10.4 New trait type combinations for `dynamic_graph` + +For each existing trait file (27 total in `include/graph/container/traits/`), a +corresponding `b` (bidirectional) variant may be added: + +| Existing | Bidirectional Variant | Description | +|---|---|---| +| `vov_graph_traits` | `bvov_graph_traits` | Bidirectional vector-of-vectors | +| `vol_graph_traits` | `bvol_graph_traits` | Bidirectional vector-of-lists | +| ... | ... | ... | + +Alternatively, a single set of traits with `Bidirectional` as a template parameter +avoids the combinatorial explosion. + +### 10.5 Mutator Invariants & Storage/Complexity Budget + +To keep the implementation predictable, reverse adjacency support must define and +enforce container invariants explicitly. + +**Mutator invariants (Bidirectional = true):** + +1. `create_edge(u, v)` inserts one forward entry in `u.out_edges_` and one reverse + entry in `v.in_edges_` representing the same logical edge. +2. `erase_edge(u, v)` removes both forward and reverse entries. +3. `erase_vertex(x)` removes all incident edges and corresponding mirrored entries + in opposite adjacency lists. +4. `clear_vertex(x)` removes both outgoing and incoming incidence for `x`. +5. `clear()` empties both forward and reverse structures. +6. Copy/move/swap preserve forward↔reverse consistency. + +**Complexity/storage targets:** + +- Outgoing-only mode (`Bidirectional = false`) remains unchanged. +- Bidirectional mode targets O(1) additional work per edge insertion/erasure in + adjacency-list containers (plus container-specific costs). +- Storage overhead target is roughly one additional reverse entry per edge + (`O(E)` extra), with optional reduced footprint via lightweight `in_edge_t`. + +--- + +## 11. Algorithm Updates + +Most algorithms only need outgoing edges and require **no changes**. The following +would benefit from incoming edge support: + +| Algorithm | Benefit | +|---|---| +| **Kosaraju's SCC** (`connected_components.hpp`) | Currently simulates reverse graph by rebuilding edges. With `in_edges()`, the second DFS pass can use `in_edges_accessor` directly. | +| **PageRank** (future) | Requires iterating incoming edges for each vertex. | +| **Reverse BFS/DFS** | Enable with `in_edges_accessor` parameter (§9). | +| **Transpose graph** | Can be implemented as a zero-cost view that swaps `edges()`↔`in_edges()`. | +| **Dominator trees** | Require reverse control flow graph (incoming edges). | + +No existing algorithm needs to be **renamed**. They all operate on outgoing +edges by default and remain unchanged. + +--- + +## 12. Namespace & Re-export Updates + +**File:** `include/graph/graph.hpp` + +Add to the `graph::` re-exports: + +```cpp +// New incoming-edge CPOs +using adj_list::in_edges; +using adj_list::in_degree; +using adj_list::out_edges; +using adj_list::out_degree; +using adj_list::find_out_edge; +using adj_list::find_in_edge; +using adj_list::contains_in_edge; + +// New concepts +using adj_list::in_vertex_edge_range; +using adj_list::bidirectional_adjacency_list; +using adj_list::index_bidirectional_adjacency_list; + +// New traits +using adj_list::has_in_degree; +using adj_list::has_in_degree_v; + +// New type aliases +using adj_list::in_vertex_edge_range_t; +using adj_list::in_vertex_edge_iterator_t; +using adj_list::in_edge_t; +using adj_list::out_vertex_edge_range_t; +using adj_list::out_vertex_edge_iterator_t; +using adj_list::out_edge_t; +``` + +**File:** `include/graph/graph.hpp` — add includes: + +```cpp +#include +#include +``` + +--- + +## 13. Documentation Updates + +### 13.1 Existing docs to update + +| File | Change | +|---|---| +| `docs/index.md` | Add "Bidirectional edge access" to feature list | +| `docs/getting-started.md` | Add section on incoming edges; update "outgoing edges" mentions to be explicit | +| `docs/user-guide/views.md` | Add `in_incidence` and `in_neighbors` views; add `out_incidence`/`out_neighbors` aliases | +| `docs/user-guide/algorithms.md` | Note which algorithms benefit from bidirectional access | +| `docs/user-guide/containers.md` | Document bidirectional dynamic_graph and compressed_graph | +| `docs/reference/concepts.md` | Add `bidirectional_adjacency_list`, `in_vertex_edge_range` | +| `docs/reference/cpo-reference.md` | Add `in_edges`, `in_degree`, `out_edges`, `out_degree`, `find_in_edge`, `contains_in_edge` | +| `docs/reference/type-aliases.md` | Add `in_vertex_edge_range_t`, `in_edge_t`, etc. | +| `docs/reference/adjacency-list-interface.md` | Add incoming edge section | +| `docs/contributing/cpo-implementation.md` | Add `in_edges` as an example of the CPO pattern | +| `README.md` | Update feature highlights with bidirectional support | + +### 13.2 New docs + +| File | Content | +|---|---| +| `docs/user-guide/bidirectional-access.md` | Tutorial-style guide on using incoming edges | + +--- + +## 14. Implementation Phases + +### Phase 1: Core CPOs & Concepts (~2-3 days) + +1. Implement `in_edges` CPO in `graph_cpo.hpp` (mirror `edges` CPO) +2. Implement `in_degree` CPO (mirror `degree` CPO) +3. Add `out_edges` / `out_degree` aliases +4. Define `in_vertex_edge_range_t`, `in_edge_t`, etc. +5. Define `bidirectional_adjacency_list` concept +6. Add `has_in_degree` trait +7. Update `graph.hpp` re-exports +8. Add unit tests for CPO resolution (stub graph with `in_edges()` member) +9. Add compile-time concept tests for `bidirectional_adjacency_list` +10. Add mixed-type tests where `in_edge_t != edge_t` (source-only incoming edges) + +### Phase 2: Views (~2 days) + +1. Create `in_incidence.hpp` (copy+adapt `incidence.hpp`) +2. Create `in_neighbors.hpp` (copy+adapt `neighbors.hpp`) +3. Add `out_incidence` / `out_neighbors` aliases +4. Add pipe-syntax adaptors to `adaptors.hpp` (§8.4) +5. Unit tests for all new views (including pipe-syntax adaptor tests) + +### Phase 3: Container Support (~3-4 days) + +1. Add `Bidirectional` parameter to `dynamic_graph_base` +2. Update `create_edge` / `erase_edge` / `clear` to maintain reverse lists +3. Update `erase_vertex` / `clear_vertex` / copy-move-swap paths to preserve reverse invariants +4. Add ADL `in_edges()` friend to bidirectional dynamic_graph +5. Add CSC support to `compressed_graph` +6. Add `in_edges()` to `undirected_adjacency_list` (returns same as `edges()`) +7. Unit tests for all three containers (including mutator invariant checks) + +### Phase 4: BFS/DFS Parameterization (~1-2 days) + +1. Add `EdgeAccessor` template parameter to BFS/DFS/topological sort views +2. Define `out_edges_accessor` and `in_edges_accessor` functors +3. Add factory function overloads +4. Unit tests for reverse BFS/DFS + +### Phase 5: Algorithm Updates (~1-2 days) + +1. Refactor Kosaraju's SCC to use `in_edges()` when available +2. Add `transpose_view` (zero-cost view swapping `edges()`↔`in_edges()`) +3. Unit tests + +### Phase 6: Documentation (~2 days) + +1. Update all docs listed in §13.1 +2. Create `bidirectional-access.md` tutorial +3. Update CHANGELOG.md + +### Recommended execution order (risk-first) + +To reduce integration risk and keep PRs reviewable, execute phases with strict +merge gates and defer high-churn container internals until API contracts are +proven by tests. + +1. **Phase 1 (CPOs/concepts/traits) first, in one or two PRs max.** +2. **Phase 2 (views) second**, consuming only the new public CPOs. +3. **Phase 4 (BFS/DFS parameterization) third**, before container internals, + to validate the accessor approach on existing graphs. +4. **Phase 3 (container support) fourth**, split by container: + `undirected_adjacency_list` → `dynamic_graph` → `compressed_graph`. +5. **Phase 5 (algorithm updates) fifth**, after reverse traversal behavior is + stable and benchmarked. +6. **Phase 6 (docs/changelog) last**, plus updates in each phase PR where needed. + +### Merge gates (go/no-go criteria) + +Each phase must satisfy all gates before proceeding: + +- `linux-gcc-debug` full test suite passes. +- New compile-time concept tests pass (`requires`/`static_assert` coverage). +- No regressions in existing outgoing-edge APIs (`edges`, `degree`, `find_vertex_edge`). +- New incoming APIs are documented in reference docs for that phase. + +Additional phase-specific gates: + +- **After Phase 1:** + - Mixed-type incoming-edge test (`in_edge_t != edge_t`) passes. + - `in_edges`/`in_degree` ADL and member dispatch paths are covered. +- **After Phase 4:** + - Reverse BFS/DFS outputs validated against expected traversal sets. + - Existing BFS/DFS/topological-sort signatures remain source-compatible. +- **After Phase 3:** + - Mutator invariant tests pass (`create_edge`, `erase_edge`, `erase_vertex`, + `clear_vertex`, `clear`, copy/move/swap consistency). + +### Fallback and scope control + +- If CSC integration in `compressed_graph` slips, ship `dynamic_graph` + + `undirected_adjacency_list` incoming support first and keep CSC behind a + follow-up milestone. +- Keep `transpose_view` optional for initial release; prioritize core CPOs, + views, and reverse traversal correctness. +- If API pressure grows, keep only the decided aliases (`out_*`) and avoid + introducing additional directional synonyms. + +--- + +## 15. Summary of All Changes + +| Category | New | Modified | Deleted | +|---|---|---|---| +| **CPOs** | `in_edges`, `in_degree`, `out_edges` (alias), `out_degree` (alias), `find_out_edge` (alias), `find_in_edge`, `contains_in_edge` | — | — | +| **Concepts** | `in_vertex_edge_range`, `bidirectional_adjacency_list`, `index_bidirectional_adjacency_list` | — | — | +| **Traits** | `has_in_degree`, `has_in_degree_v`, `has_find_in_edge`, `has_contains_in_edge` | — | — | +| **Type Aliases** | `in_vertex_edge_range_t`, `in_vertex_edge_iterator_t`, `in_edge_t`, `out_vertex_edge_range_t`, `out_vertex_edge_iterator_t`, `out_edge_t` | — | — | +| **Views** | `in_incidence.hpp`, `in_neighbors.hpp` | `bfs.hpp`, `dfs.hpp`, `topological_sort.hpp` (add `EdgeAccessor` param), `adaptors.hpp` (add pipe closures) | — | +| **Traversal policies** | `out_edges_accessor`, `in_edges_accessor` | — | — | +| **Containers** | — | `dynamic_graph.hpp` (add `Bidirectional`), `compressed_graph.hpp` (add CSC), `undirected_adjacency_list.hpp` (add `in_edges` friend) | — | +| **Algorithms** | `transpose_view` | `connected_components.hpp` (Kosaraju optimization) | — | +| **Headers** | — | `graph.hpp` (new includes & re-exports) | — | +| **Docs** | `bidirectional-access.md` | 11 existing docs (§13.1) | — | +| **Tests** | CPO tests, view tests, container tests, BFS/DFS reverse tests | — | — | + +**Estimated effort:** 11–15 days + +--- + +## Appendix A: Full CPO Resolution Table + +| CPO | Tier 1 | Tier 2 | Tier 3 | +|---|---|---|---| +| `edges(g, u)` | `u.inner_value(g).edges()` | ADL `edges(g, u)` | inner_value is forward_range → wrap | +| `in_edges(g, u)` | `u.inner_value(g).in_edges()` | ADL `in_edges(g, u)` | *(no default)* | +| `degree(g, u)` | `g.degree(u)` | ADL `degree(g, u)` | `size(edges(g, u))` | +| `in_degree(g, u)` | `g.in_degree(u)` | ADL `in_degree(g, u)` | `size(in_edges(g, u))` | +| `find_vertex_edge(g, u, v)` | `g.find_vertex_edge(u, v)` | ADL `find_vertex_edge(g, u, v)` | iterate `edges(g, u)`, match `target_id` | +| `find_in_edge(g, u, v)` | `g.find_in_edge(u, v)` | ADL `find_in_edge(g, u, v)` | iterate `in_edges(g, u)`, match `source_id` | +| `contains_edge(g, u, v)` | `g.contains_edge(u, v)` | ADL `contains_edge(g, u, v)` | iterate `edges(g, u)`, match `target_id` | +| `contains_in_edge(g, u, v)` | `g.contains_in_edge(u, v)` | ADL `contains_in_edge(g, u, v)` | iterate `in_edges(g, u)`, match `source_id` | +| `has_edge(g)` | `g.has_edge()` | ADL `has_edge(g)` | iterate vertices, find non-empty edges | +| `out_edges(g, u)` | → `edges(g, u)` | | | +| `out_degree(g, u)` | → `degree(g, u)` | | | +| `find_out_edge(g, u, v)` | → `find_vertex_edge(g, u, v)` | | | + +## Appendix B: No-Rename Justification + +Several graph libraries (Boost.Graph, NetworkX, LEDA) distinguish `out_edges` / +`in_edges` explicitly. In graph-v3, `edges()` has **always** meant outgoing (this is +documented and all algorithms depend on it). Renaming `edges()` to `out_edges()` would: + +- Break every existing user's code +- Require renaming `vertex_edge_range_t` → `out_vertex_edge_range_t` across 50+ files +- Create churn in all 14 algorithms + +Instead we keep `edges()` as the primary outgoing CPO (matching the "default is outgoing" +convention) and add `out_edges` as a convenience alias for codebases that want explicit +directionality. + +## Appendix C: Independent In/Out Edge Types + +`in_edge_t` and `edge_t` are independently deduced: + +```cpp +template using edge_t = ranges::range_value_t>; // from edges(g,u) +template using in_edge_t = ranges::range_value_t>; // from in_edges(g,u) +``` + +They are commonly the same type, but this is **not required**. Scenarios where +they differ: + +| Scenario | `edge_t` | `in_edge_t` | +|---|---|---| +| Symmetric (common case) | `pair` | `pair` (same) | +| Distributed graph | `pair` | `VId` (back-pointer only) | +| CSR + CSC compressed | Full edge with value | Column index + CSR back-ref | +| Lightweight reverse index | `pair` | `VId` (source ID only) | + +The `bidirectional_adjacency_list` concept (§6.2) only requires `source_id(g, ie)` +on incoming edges — not `edge_value()`. This means: +- Algorithms that only need the graph structure (BFS, DFS, SCC) work with any + `in_edge_t`. +- Algorithms that need edge properties on incoming edges (rare) can add their own + `requires edge_value(g, in_edge_t{})` constraint. +- The undirected case (`in_edge_t == edge_t`) is the zero-cost happy path. + +Minimum incoming-edge contract for algorithms: + +- **Always required:** `in_edges(g, u)`, `source_id(g, ie)` +- **Conditionally required:** `edge_value(g, ie)` only for algorithms/views that + explicitly consume incoming edge properties +- **Not required:** `target_id(g, ie)` for incoming-only traversal algorithms + +## Appendix D: `out_` Alias Retention Decision + +**Date:** 2026-02-21 + +### Question + +Should the library provide `out_edges`, `out_degree`, `find_out_edge`, +`out_incidence`, `out_neighbors` (and their `basic_` variants) as aliases for the +existing outgoing CPOs/views, or should it omit them entirely? + +### Arguments for removing the aliases + +| # | Argument | +|---|---| +| R1 | `edges()` already means "outgoing" and always has — adding `out_edges()` is redundant and inflates the API surface. | +| R2 | Two spellings for the same operation create ambiguity: users must learn that `out_edges(g, u)` and `edges(g, u)` are identical. | +| R3 | Aliases clutter autocomplete, documentation tables, and error messages. | +| R4 | No existing user code ever spells `out_edges()` today, so removing the aliases breaks nobody. | +| R5 | If a future rename is ever desired (`edges` → `out_edges`), aliases make that rename harder because both names are already established. | + +### Arguments for keeping the aliases + +| # | Argument | +|---|---| +| K1 | **Symmetry with `in_edges`:** When a codebase uses `in_edges()` alongside `edges()`, the lack of an `out_` counterpart is visually jarring. `out_edges` / `in_edges` reads as a matched pair. | +| K2 | **Self-documenting code:** `out_edges(g, u)` makes directionality explicit at the call site; `edges(g, u)` requires the reader to know the convention. | +| K3 | **Familiar vocabulary:** Boost.Graph, NetworkX, LEDA, and the P1709 proposal all provide an `out_edges` name. Users migrating from those libraries expect it. | +| K4 | **Zero runtime cost:** The aliases are `inline constexpr` references to the existing CPO objects — no code duplication, no template bloat, no additional overload resolution. | +| K5 | **Grep-ability:** Searching a codebase for `out_edges` immediately reveals all outgoing-edge access; searching for `edges` produces false positives from `in_edges`, `num_edges`, `has_edge`, etc. | +| K6 | **Non-breaking:** Aliases are purely additive. Users who prefer `edges()` continue to use it unchanged. | + +### Resolution + +**Keep all `out_` aliases** (CPOs and view factory functions) as designed in §3.2, +§4.3, and §8.3. + +The symmetry (K1), self-documentation (K2), and familiarity (K3) benefits outweigh +the API-surface concern (R1–R3). The aliases are zero-cost (K4) and non-breaking (K6). + +To mitigate confusion (R2), documentation will: +- Note that `out_edges(g, u)` is an alias for `edges(g, u)` wherever it appears. +- Use `edges()` as the primary spelling in algorithm implementations. +- Use `out_edges()` in examples that also use `in_edges()`, to keep the pairing + visually clear. diff --git a/agents/incoming_edges_plan.md b/agents/incoming_edges_plan.md new file mode 100644 index 0000000..a3b4b1c --- /dev/null +++ b/agents/incoming_edges_plan.md @@ -0,0 +1,1094 @@ +# Incoming Edges — Phased Implementation Plan + +**Source:** `agents/incoming_edges_design.md` +**Branch:** `incoming` +**Build preset:** `linux-gcc-debug` (full test suite) + +--- + +## How to use this plan + +Each phase is a self-contained unit of work that can be completed by an agent in +one session. Phases are strictly ordered — each depends on the prior phase compiling +and passing all tests. Within each phase, steps are listed in execution order. + +**Conventions used throughout:** + +- "Mirror X" means copy the implementation pattern from X, replacing outgoing + names/semantics with incoming equivalents. +- "Full test suite passes" means `cmake --build build/linux-gcc-debug -j$(nproc)` + succeeds and `cd build/linux-gcc-debug && ctest --output-on-failure` reports + zero failures with no regressions. +- File paths are relative to the repository root. + +--- + +## Phase 1 — `in_edges` and `in_degree` CPOs + type aliases + +**Goal:** Add the two core incoming-edge CPOs, their public instances, the +outgoing aliases (`out_edges`, `out_degree`, `find_out_edge`), the six new type +aliases, and a CPO unit-test file proving all resolution tiers work. + +**Why first:** Every subsequent phase depends on `in_edges` and `in_edge_t` +existing. This phase touches exactly one production header and adds one test +file, so it is low-risk and easy to validate. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/adj_list/detail/graph_cpo.hpp` | Add CPOs, aliases, public instances | +| `tests/adj_list/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `tests/adj_list/cpo/test_in_edges_cpo.cpp` | Unit tests | + +### Steps + +#### 1.1 Add `in_edges` CPO (graph_cpo.hpp) + +Insert a new section **after** the `edges` public instance and type aliases +(after line ~835, before the `_target` namespace). Follow the exact structural +pattern of `namespace _edges`: + +``` +namespace _cpo_impls { + namespace _in_edges { + // --- (g, u) overload --- + enum class _St_u { _none, _vertex_member, _adl }; + + template + concept _has_vertex_member_u = is_vertex_descriptor_v<...> && + requires(G& g, const U& u) { + { u.inner_value(g).in_edges() } -> std::ranges::forward_range; + }; + + template + concept _has_adl_u = requires(G& g, const U& u) { + { in_edges(g, u) } -> std::ranges::forward_range; + }; + + // NOTE: No _edge_value_pattern tier — in_edges has no implicit default. + // A graph MUST explicitly provide in_edges() via member or ADL. + + template + [[nodiscard]] consteval _Choice_t<_St_u> _Choose_u() noexcept { ... } + + // --- (g, uid) overload --- + enum class _St_uid { _none, _adl, _default }; + // _has_adl_uid, _has_default_uid (find_vertex + in_edges(g, u)) + template + [[nodiscard]] consteval _Choice_t<_St_uid> _Choose_uid() noexcept { ... } + + class _fn { + // operator()(G&& g, const U& u) — vertex descriptor + // operator()(G&& g, const VId& uid) — vertex ID + // Both use _wrap_if_needed() to ensure edge_descriptor_view return + }; + } +} +``` + +Key differences from `edges`: +- Only 2 tiers for `(g, u)`: `_vertex_member` and `_adl`. No `_edge_value_pattern`. +- The `(g, uid)` default delegates to `in_edges(g, *find_vertex(g, uid))`. +- The `_has_default_uid` concept checks `_has_adl_u` (not `_has_edge_value_pattern`). + +#### 1.2 Add `in_edges` public instance and type aliases + +Immediately after the `_in_edges` namespace closing brace: + +```cpp +inline namespace _cpo_instances { + inline constexpr _cpo_impls::_in_edges::_fn in_edges{}; +} + +template +using in_vertex_edge_range_t = decltype(in_edges(std::declval(), std::declval>())); + +template +using in_vertex_edge_iterator_t = std::ranges::iterator_t>; + +template +using in_edge_t = std::ranges::range_value_t>; +``` + +#### 1.3 Add `in_degree` CPO + +Insert after the `in_edges` section, mirroring `namespace _degree`: + +``` +namespace _cpo_impls { + namespace _in_degree { + // (g, u): _member (g.in_degree(u)), _adl, _default (size(in_edges(g,u))) + // (g, uid): _member, _adl, _default (*find_vertex then in_degree(g,u)) + class _fn { ... }; + } +} + +inline namespace _cpo_instances { + inline constexpr _cpo_impls::_in_degree::_fn in_degree{}; +} +``` + +Key difference from `degree`: the `_default` tier calls `in_edges(g, u)` instead +of `edges(g, u)`. + +#### 1.4 Add outgoing aliases + +After the `in_degree` public instance: + +```cpp +inline namespace _cpo_instances { + inline constexpr auto& out_edges = edges; + inline constexpr auto& out_degree = degree; + inline constexpr auto& find_out_edge = find_vertex_edge; +} +``` + +#### 1.5 Add outgoing type aliases + +```cpp +template +using out_vertex_edge_range_t = vertex_edge_range_t; + +template +using out_vertex_edge_iterator_t = vertex_edge_iterator_t; + +template +using out_edge_t = edge_t; +``` + +#### 1.6 Create test file `tests/adj_list/cpo/test_in_edges_cpo.cpp` + +Test the following scenarios (mirror `test_edges_cpo.cpp` structure): + +1. **Stub graph with `in_edges()` member** — a minimal struct whose vertex + `inner_value(g).in_edges()` returns a `vector`. Verify the CPO + dispatches to `_vertex_member` tier and the return is an + `edge_descriptor_view`. + +2. **Stub graph with ADL `in_edges(g, u)` friend** — verify `_adl` tier. + +3. **`(g, uid)` overload with default** — verify `_default` tier delegates + through `find_vertex` + `in_edges(g, u)`. + +4. **Type alias verification** — `in_vertex_edge_range_t`, + `in_vertex_edge_iterator_t`, `in_edge_t` all compile and produce + expected types. + +5. **Mixed-type test** — a stub graph where `edge_t` is + `pair` but `in_edge_t` is just `int`. Verify both + aliases are independently deduced. + +6. **`out_edges` / `out_degree` / `find_out_edge` aliases** — verify they + are the exact same object as `edges` / `degree` / `find_vertex_edge` + (use `static_assert(&out_edges == &edges)`). + +7. **`in_degree` CPO** — test member, ADL, and default (`size(in_edges)`) + resolution tiers. + +8. **Outgoing type aliases** — verify `out_edge_t` is `edge_t` etc. + +#### 1.7 Register test in CMakeLists + +Add to `tests/adj_list/CMakeLists.txt` under the CPOs section: + +```cmake + cpo/test_in_edges_cpo.cpp +``` + +#### 1.8 Build and run full test suite + +```bash +cmake --build build/linux-gcc-debug -j$(nproc) +cd build/linux-gcc-debug && ctest --output-on-failure +``` + +### Merge gate + +- [ ] Full test suite passes with zero regressions. +- [ ] All 8 test scenarios pass. +- [ ] `in_edge_t != edge_t` mixed-type test passes. +- [ ] `out_edges` alias identity check passes. + +--- + +## Phase 2 — `find_in_edge` and `contains_in_edge` CPOs + traits + +**Goal:** Add the remaining incoming-edge CPOs (`find_in_edge`, +`contains_in_edge`), the new traits (`has_in_degree`, `has_find_in_edge`, +`has_contains_in_edge`), and their unit tests. + +**Why second:** These CPOs depend on `in_edges` (Phase 1). They complete the +full incoming-edge CPO surface before concepts are defined. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/adj_list/detail/graph_cpo.hpp` | Add `find_in_edge`, `contains_in_edge` CPOs | +| `include/graph/adj_list/adjacency_list_traits.hpp` | Add 3 new traits | +| `tests/adj_list/CMakeLists.txt` | Register new test files | + +### Files to create + +| File | Content | +|---|---| +| `tests/adj_list/cpo/test_find_in_edge_cpo.cpp` | Unit tests for `find_in_edge` | +| `tests/adj_list/cpo/test_contains_in_edge_cpo.cpp` | Unit tests for `contains_in_edge` | +| `tests/adj_list/traits/test_incoming_edge_traits.cpp` | Trait tests | + +### Steps + +#### 2.1 Add `find_in_edge` CPO (graph_cpo.hpp) + +Mirror `namespace _find_vertex_edge` with three overload groups: + +1. `find_in_edge(g, u, v)` — both descriptors. Default iterates + `in_edges(g, u)` and matches `source_id(g, ie)` against `vertex_id(g, v)`. +2. `find_in_edge(g, u, vid)` — descriptor + ID. Default iterates + `in_edges(g, u)` and matches `source_id(g, ie)` against `vid`. +3. `find_in_edge(g, uid, vid)` — both IDs. Default delegates via + `*find_vertex(g, uid)`. + +Key difference from `find_vertex_edge`: iterates `in_edges` and compares +`source_id` instead of `target_id`. + +```cpp +inline namespace _cpo_instances { + inline constexpr _cpo_impls::_find_in_edge::_fn find_in_edge{}; +} +``` + +#### 2.2 Add `contains_in_edge` CPO (graph_cpo.hpp) + +Mirror `namespace _contains_edge` with two overload groups: + +1. `contains_in_edge(g, u, v)` — both descriptors. +2. `contains_in_edge(g, uid, vid)` — both IDs. + +Default implementations iterate `in_edges(g, u)` and match `source_id`. + +```cpp +inline namespace _cpo_instances { + inline constexpr _cpo_impls::_contains_in_edge::_fn contains_in_edge{}; +} +``` + +#### 2.3 Add traits (adjacency_list_traits.hpp) + +Follow the existing `detail::has_X_impl` → `has_X` → `has_X_v` pattern: + +```cpp +// --- has_in_degree --- +namespace detail { + template + concept has_in_degree_impl = requires(G& g, vertex_t u, vertex_id_t uid) { + { in_degree(g, u) } -> std::integral; + { in_degree(g, uid) } -> std::integral; + }; +} +template concept has_in_degree = detail::has_in_degree_impl; +template inline constexpr bool has_in_degree_v = has_in_degree; + +// --- has_find_in_edge --- +namespace detail { + template + concept has_find_in_edge_impl = requires(G& g, vertex_t u, vertex_t v, + vertex_id_t uid, vertex_id_t vid) { + find_in_edge(g, u, v); + find_in_edge(g, u, vid); + find_in_edge(g, uid, vid); + }; +} +template concept has_find_in_edge = detail::has_find_in_edge_impl; +template inline constexpr bool has_find_in_edge_v = has_find_in_edge; + +// --- has_contains_in_edge --- +namespace detail { + template + concept has_contains_in_edge_impl = requires(G& g, vertex_t u, vertex_t v, + vertex_id_t uid, vertex_id_t vid) { + { contains_in_edge(g, u, v) } -> std::convertible_to; + { contains_in_edge(g, uid, vid) } -> std::convertible_to; + }; +} +template concept has_contains_in_edge = detail::has_contains_in_edge_impl; +template inline constexpr bool has_contains_in_edge_v = has_contains_in_edge; +``` + +#### 2.4 Create test files + +**`test_find_in_edge_cpo.cpp`** — test all 3 overloads: +- Stub graph with ADL `in_edges()` friend returning known edges. +- Verify `find_in_edge(g, u, v)` finds correct incoming edge by `source_id`. +- Verify `find_in_edge(g, u, vid)` works. +- Verify `find_in_edge(g, uid, vid)` delegates through `find_vertex`. +- Verify not-found case. + +**`test_contains_in_edge_cpo.cpp`** — test both overloads: +- Verify `contains_in_edge(g, u, v)` returns true/false correctly. +- Verify `contains_in_edge(g, uid, vid)` returns true/false correctly. + +**`test_incoming_edge_traits.cpp`** — test all 3 traits: +- Stub graph that models `in_edges`/`in_degree` → `has_in_degree_v` is true. +- Plain `vector>` → `has_in_degree_v` is false. +- Stub graph with `in_edges` + `find_in_edge` → `has_find_in_edge_v` is true. +- Stub graph with `in_edges` + `contains_in_edge` → `has_contains_in_edge_v` is true. + +#### 2.5 Register tests in CMakeLists + +Add to `tests/adj_list/CMakeLists.txt`: + +```cmake + cpo/test_find_in_edge_cpo.cpp + cpo/test_contains_in_edge_cpo.cpp + traits/test_incoming_edge_traits.cpp +``` + +#### 2.6 Build and run full test suite + +### Merge gate + +- [ ] Full test suite passes. +- [ ] All `find_in_edge` overloads work (member, ADL, default). +- [ ] All `contains_in_edge` overloads work. +- [ ] Traits correctly detect presence/absence of incoming-edge CPOs. + +--- + +## Phase 3 — Concepts + namespace re-exports + +**Goal:** Define `in_vertex_edge_range`, `bidirectional_adjacency_list`, +`index_bidirectional_adjacency_list` concepts; update `graph.hpp` with all +re-exports for Phases 1-3. + +**Why third:** Concepts depend on the CPOs and type aliases from Phases 1-2. +Re-exports should be done once after the complete CPO surface exists. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/adj_list/adjacency_list_concepts.hpp` | Add 3 concepts | +| `include/graph/graph.hpp` | Add re-exports for all new CPOs, aliases, traits, concepts | +| `tests/adj_list/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `tests/adj_list/concepts/test_bidirectional_concepts.cpp` | Concept tests | + +### Steps + +#### 3.1 Add concepts (adjacency_list_concepts.hpp) + +After the `index_adjacency_list` concept: + +```cpp +template +concept in_vertex_edge_range = std::ranges::forward_range; + +template +concept bidirectional_adjacency_list = + adjacency_list && + requires(G& g, vertex_t u, in_edge_t ie) { + { in_edges(g, u) } -> in_vertex_edge_range; + { source_id(g, ie) } -> std::convertible_to>; + }; + +template +concept index_bidirectional_adjacency_list = + bidirectional_adjacency_list && index_vertex_range; +``` + +#### 3.2 Update graph.hpp re-exports + +In the `namespace graph { ... }` using-declaration block, add: + +```cpp +// Incoming-edge CPOs +using adj_list::in_edges; +using adj_list::in_degree; +using adj_list::find_in_edge; +using adj_list::contains_in_edge; + +// Outgoing aliases +using adj_list::out_edges; +using adj_list::out_degree; +using adj_list::find_out_edge; + +// Incoming-edge type aliases +using adj_list::in_vertex_edge_range_t; +using adj_list::in_vertex_edge_iterator_t; +using adj_list::in_edge_t; + +// Outgoing type aliases +using adj_list::out_vertex_edge_range_t; +using adj_list::out_vertex_edge_iterator_t; +using adj_list::out_edge_t; + +// Incoming-edge concepts +using adj_list::in_vertex_edge_range; +using adj_list::bidirectional_adjacency_list; +using adj_list::index_bidirectional_adjacency_list; + +// Incoming-edge traits +using adj_list::has_in_degree; +using adj_list::has_in_degree_v; +using adj_list::has_find_in_edge; +using adj_list::has_find_in_edge_v; +using adj_list::has_contains_in_edge; +using adj_list::has_contains_in_edge_v; +``` + +#### 3.3 Create test file `tests/adj_list/concepts/test_bidirectional_concepts.cpp` + +1. **Stub bidirectional graph** — a struct with both `edges()` and `in_edges()` + ADL friends. `static_assert(bidirectional_adjacency_list)`. + +2. **Outgoing-only graph** — a `vector>`. + `static_assert(!bidirectional_adjacency_list)`. + +3. **Index variant** — stub graph with random-access vertices. + `static_assert(index_bidirectional_adjacency_list)`. + +4. **Mixed-type concept** — stub where `in_edge_t` is just `int` + (lightweight back-pointer) but `source_id(g, ie)` works. + `static_assert(bidirectional_adjacency_list)`. + +5. **Re-export test** — verify `graph::bidirectional_adjacency_list` is + accessible (compile-time only). + +#### 3.4 Register test and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] `bidirectional_adjacency_list` satisfied by stub bidirectional graph. +- [ ] `bidirectional_adjacency_list` not satisfied by `vector>`. +- [ ] Mixed-type (`in_edge_t != edge_t`) graph satisfies concept. +- [ ] All re-exports compile via `#include `. + +--- + +## Phase 4 — `undirected_adjacency_list` incoming-edge support + +**Goal:** Add `in_edges()` and `in_degree()` ADL friends to +`undirected_adjacency_list` that return the same ranges as `edges()` and +`degree()`, making undirected graphs model `bidirectional_adjacency_list` +at zero cost. + +**Why fourth:** This is the simplest container change (just forwarding to +existing functions) and provides a real container for integration testing. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/container/undirected_adjacency_list.hpp` | Add `in_edges`, `in_degree` friends | +| `tests/container/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `tests/container/test_undirected_bidirectional.cpp` | Integration tests | + +### Steps + +#### 4.1 Add ADL friends (undirected_adjacency_list.hpp) + +In `base_undirected_adjacency_list`, immediately after the existing `edges()` +friend functions (around line 1030), add: + +```cpp + // in_edges(g, u) — for undirected graphs, same as edges(g, u) + template + requires adj_list::vertex_descriptor_type + friend constexpr auto in_edges(graph_type& g, const U& u) noexcept { + auto uid = static_cast(u.vertex_id()); + return g.vertices_[uid].edges(g, uid); + } + template + requires adj_list::vertex_descriptor_type + friend constexpr auto in_edges(const graph_type& g, const U& u) noexcept { + auto uid = static_cast(u.vertex_id()); + return g.vertices_[uid].edges(g, uid); + } +``` + +Also add `in_degree` friends if the class has a `degree()` member/friend. +Otherwise, the `in_degree` CPO's default tier (`size(in_edges(g, u))`) will +handle it automatically. + +#### 4.2 Create test file + +Test using actual `undirected_adjacency_list` instances (e.g., `vov_graph`): + +1. **`in_edges` returns same edges as `edges`** — for each vertex, verify + `in_edges(g, u)` and `edges(g, u)` yield the same edge set. + +2. **`in_degree` equals `degree`** — for each vertex. + +3. **Concept satisfaction** — `static_assert(bidirectional_adjacency_list)`. + +4. **`find_in_edge` works** — since `in_edges` returns the same range. + +5. **`contains_in_edge` works** — mirrors `contains_edge`. + +6. Run against multiple trait types (at least `vov` and `vol`). + +#### 4.3 Register test and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] `undirected_adjacency_list` models `bidirectional_adjacency_list`. +- [ ] `in_edges(g, u)` and `edges(g, u)` produce identical results. + +--- + +## Phase 5 — `in_incidence` and `in_neighbors` views + +**Goal:** Create the incoming-edge view files, their factory functions, +and tests. + +**Why fifth:** Views consume the CPOs from Phases 1-3 and can now also be +tested against the undirected container from Phase 4. + +### Files to modify + +| File | Action | +|---|---| +| `tests/views/CMakeLists.txt` | Register new test files | + +### Files to create + +| File | Content | +|---|---| +| `include/graph/views/in_incidence.hpp` | `in_incidence_view`, `basic_in_incidence_view`, factory functions | +| `include/graph/views/in_neighbors.hpp` | `in_neighbors_view`, `basic_in_neighbors_view`, factory functions | +| `tests/views/test_in_incidence.cpp` | View tests | +| `tests/views/test_in_neighbors.cpp` | View tests | + +### Steps + +#### 5.1 Create `include/graph/views/in_incidence.hpp` + +Copy `incidence.hpp` and make these changes: + +- Rename all classes: `incidence_view` → `in_incidence_view`, + `basic_incidence_view` → `basic_in_incidence_view`. +- Change edge iteration from `edges(g, u)` to `in_edges(g, u)`. +- Change neighbor ID extraction from `target_id(g, e)` to `source_id(g, e)`. +- Change `edge_t` references to `in_edge_t`. +- Change concept constraint from `adjacency_list` to `bidirectional_adjacency_list`. +- Update all doxygen comments. +- Update the `edge_info` yield: `{sid, uv}` / `{sid, uv, val}` / `{sid}` / `{sid, val}`. + +#### 5.2 Create `include/graph/views/in_neighbors.hpp` + +Copy `neighbors.hpp` and apply the same transformation: + +- Rename: `neighbors_view` → `in_neighbors_view`. +- Iterate `in_edges` instead of `edges`. +- Extract `source_id` instead of `target_id`. +- Retrieve source vertex via `source(g, e)` instead of `target(g, e)`. +- Constrain with `bidirectional_adjacency_list`. + +#### 5.3 Add outgoing view aliases + +At the bottom of `in_incidence.hpp`: + +```cpp +namespace graph::views { + inline constexpr auto& out_incidence = incidence; + inline constexpr auto& basic_out_incidence = basic_incidence; +} +``` + +At the bottom of `in_neighbors.hpp`: + +```cpp +namespace graph::views { + inline constexpr auto& out_neighbors = neighbors; + inline constexpr auto& basic_out_neighbors = basic_neighbors; +} +``` + +#### 5.4 Update `graph.hpp` + +Add includes: + +```cpp +#include +#include +``` + +#### 5.5 Create test files + +**`test_in_incidence.cpp`** — mirror `test_incidence.cpp`: +- Use undirected_adjacency_list (from Phase 4) as the bidirectional graph. +- Verify `in_incidence(g, u)` yields `{source_id, edge}` tuples. +- Verify `in_incidence(g, u, evf)` yields `{source_id, edge, value}`. +- Verify `basic_in_incidence(g, uid)` yields `{source_id}`. +- Verify factory function overloads (descriptor and ID). +- Verify `out_incidence` alias is the same as `incidence`. + +**`test_in_neighbors.cpp`** — mirror `test_neighbors.cpp`: +- Verify `in_neighbors(g, u)` yields `{source_id, vertex}` tuples. +- Verify `basic_in_neighbors(g, uid)` yields `{source_id}`. +- Verify `out_neighbors` alias. + +#### 5.6 Register tests and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] `in_incidence_view` iterates incoming edges, yields `source_id`. +- [ ] `in_neighbors_view` iterates source vertices. +- [ ] All factory function overloads work. +- [ ] Outgoing aliases are identity references. + +--- + +## Phase 6 — Pipe-syntax adaptors + +**Goal:** Add pipe-syntax closures for `in_incidence`, `in_neighbors`, +`basic_in_incidence`, `basic_in_neighbors`, and their `out_` aliases +to `adaptors.hpp`. + +**Why sixth:** Adaptors depend on the view headers from Phase 5. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/views/adaptors.hpp` | Add adaptor closures and factory fns | +| `tests/views/test_adaptors.cpp` | Add pipe-syntax tests for new views | + +### Steps + +#### 6.1 Add adaptor closures (adaptors.hpp) + +For each new view, follow the existing pattern: + +1. `in_incidence_adaptor_closure` — holds `uid` and optional `evf`. + `operator|(G&& g, closure)` calls `graph::views::in_incidence(g, uid)` or + `graph::views::in_incidence(g, uid, evf)`. + +2. `in_incidence_adaptor_fn` — has `operator()(uid)`, `operator()(uid, evf)`, + `operator()(g, uid)`, `operator()(g, uid, evf)`. + +3. Same for `basic_in_incidence`, `in_neighbors`, `basic_in_neighbors`. + +4. Outgoing aliases: + ```cpp + inline constexpr auto& out_incidence = incidence; + inline constexpr auto& basic_out_incidence = basic_incidence; + inline constexpr auto& out_neighbors = neighbors; + inline constexpr auto& basic_out_neighbors = basic_neighbors; + ``` + +Add to the adaptor namespace block at the bottom: + +```cpp +inline constexpr in_incidence_adaptor_fn in_incidence{}; +inline constexpr basic_in_incidence_adaptor_fn basic_in_incidence{}; +inline constexpr in_neighbors_adaptor_fn in_neighbors{}; +inline constexpr basic_in_neighbors_adaptor_fn basic_in_neighbors{}; +``` + +#### 6.2 Add tests to `test_adaptors.cpp` + +Append new test cases: + +```cpp +TEST_CASE("pipe: g | in_incidence(uid)", "[adaptors][in_incidence]") { ... } +TEST_CASE("pipe: g | in_incidence(uid, evf)", "[adaptors][in_incidence]") { ... } +TEST_CASE("pipe: g | basic_in_incidence(uid)", "[adaptors][basic_in_incidence]") { ... } +TEST_CASE("pipe: g | in_neighbors(uid)", "[adaptors][in_neighbors]") { ... } +TEST_CASE("pipe: g | basic_in_neighbors(uid)", "[adaptors][in_neighbors]") { ... } +TEST_CASE("pipe: out_ aliases forward", "[adaptors][aliases]") { ... } +``` + +#### 6.3 Build and run full test suite + +### Merge gate + +- [ ] Full test suite passes. +- [ ] `g | in_incidence(uid)` produces same results as `in_incidence(g, uid)`. +- [ ] All 8 pipe expressions from design doc §8.4 work. + +--- + +## Phase 7 — BFS/DFS/topological-sort `EdgeAccessor` parameterization + +**Goal:** Add an `EdgeAccessor` template parameter to all 6 traversal view +classes and their factory functions; define `out_edges_accessor` and +`in_edges_accessor` functors. + +**Why seventh:** This is a cross-cutting change to 3 existing view headers. +It must not break any existing call sites (the default accessor preserves +current behavior). Having the undirected bidirectional container (Phase 4) +enables testing reverse traversal on a real graph. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/views/bfs.hpp` | Add `EdgeAccessor` param to `vertices_bfs_view`, `edges_bfs_view`; replace 5 hardcoded `adj_list::edges()` calls | +| `include/graph/views/dfs.hpp` | Same for `vertices_dfs_view`, `edges_dfs_view`; replace 5 calls | +| `include/graph/views/topological_sort.hpp` | Same for `vertices_topological_sort_view`, `edges_topological_sort_view`; replace 7 calls | +| `tests/views/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `include/graph/views/edge_accessor.hpp` | `out_edges_accessor`, `in_edges_accessor` definitions | +| `tests/views/test_reverse_traversal.cpp` | Reverse BFS/DFS tests | + +### Steps + +#### 7.1 Create `edge_accessor.hpp` + +```cpp +#pragma once +#include + +namespace graph::views { + +struct out_edges_accessor { + template + constexpr auto operator()(G& g, adj_list::vertex_t u) const { + return adj_list::edges(g, u); + } +}; + +struct in_edges_accessor { + template + constexpr auto operator()(G& g, adj_list::vertex_t u) const { + return adj_list::in_edges(g, u); + } +}; + +} // namespace graph::views +``` + +#### 7.2 Parameterize BFS views (bfs.hpp) + +For each of `vertices_bfs_view` and `edges_bfs_view`: + +1. Add `class EdgeAccessor = out_edges_accessor` as the last template parameter + (before `Alloc` if present, or after the value function type). + +2. Store `[[no_unique_address]] EdgeAccessor edge_accessor_;` member. + +3. Replace every `adj_list::edges(g, v)` or `adj_list::edges(*g_, v)` with + `edge_accessor_(g, v)` or `edge_accessor_(*g_, v)`. + +4. Add factory function overloads accepting `EdgeAccessor`: + ```cpp + vertices_bfs(g, seed, vvf, accessor) + edges_bfs(g, seed, evf, accessor) + ``` + +5. Existing signatures remain unchanged (default `EdgeAccessor`). + +#### 7.3 Parameterize DFS views (dfs.hpp) + +Same transformation: 5 `adj_list::edges()` calls → `edge_accessor_()`. + +#### 7.4 Parameterize topological sort views (topological_sort.hpp) + +Same transformation: 7 `adj_list::edges()` calls → `edge_accessor_()`. + +#### 7.5 Create `test_reverse_traversal.cpp` + +Using an undirected_adjacency_list (where `in_edges == edges`): + +1. **Forward BFS** — `vertices_bfs(g, seed)` produces expected order. +2. **Reverse BFS** — `vertices_bfs(g, seed, void_fn, in_edges_accessor{})` + produces expected order (same as forward for undirected). +3. **Forward DFS** vs **Reverse DFS** — same pattern. +4. **Source-compatibility** — existing call sites `vertices_bfs(g, seed)` and + `vertices_bfs(g, seed, vvf)` still compile unchanged. + +#### 7.6 Build and run full test suite + +### Merge gate + +- [ ] Full test suite passes (zero regressions in existing BFS/DFS/topo tests). +- [ ] Reverse BFS/DFS with `in_edges_accessor` produces correct traversal. +- [ ] Existing factory signatures compile without changes. + +--- + +## Phase 8 — `dynamic_graph` bidirectional support + +**Goal:** Add `bool Bidirectional = false` template parameter to +`dynamic_graph_base`, maintain reverse adjacency lists when enabled, +provide `in_edges()` ADL friend. + +**Why eighth:** This is the most complex container change. All prerequisite +infrastructure (CPOs, concepts, views) is already proven by earlier phases. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/container/dynamic_graph.hpp` | Add `Bidirectional` param, reverse storage, in_edges friend, mutator updates | +| `tests/container/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `tests/container/test_dynamic_graph_bidirectional.cpp` | Comprehensive tests | + +### Steps + +#### 8.1 Add `Bidirectional` template parameter + +Change `dynamic_graph_base` signature: + +```cpp +template +class dynamic_graph_base; +``` + +Propagate through `dynamic_graph` and all using-aliases. + +#### 8.2 Add reverse edge storage (conditional) + +When `Bidirectional = true`, each vertex needs a second edge container. +Use `if constexpr` or a conditional base class: + +```cpp +// In the vertex type: +if constexpr (Bidirectional) { + edges_type in_edges_; // reverse adjacency container +} +``` + +Add `in_edges()` accessor to the vertex type (conditional on `Bidirectional`). + +#### 8.3 Update mutators + +Update `create_edge(u, v, ...)`: +```cpp +if constexpr (Bidirectional) { + // Insert reverse entry in v.in_edges_ with source = u +} +``` + +Similarly update `erase_edge`, `erase_vertex`, `clear_vertex`, `clear`, +copy constructor, move constructor, and swap. Each must maintain +forward↔reverse consistency. + +#### 8.4 Add `in_edges()` ADL friend + +```cpp +template +requires adj_list::vertex_descriptor_type +friend constexpr auto in_edges(graph_type& g, const U& u) noexcept + requires (Bidirectional) +{ + auto uid = static_cast(u.vertex_id()); + return g.vertices_[uid].in_edges(g, uid); +} +``` + +#### 8.5 Create comprehensive test file + +1. **Basic bidirectional graph construction** — create edges, verify `in_edges` + returns expected source vertices. + +2. **`in_degree` matches expected** — for each vertex. + +3. **Concept satisfaction** — `static_assert(bidirectional_adjacency_list)`. + +4. **Mutator invariants** — after `create_edge(u, v)`: + - `contains_edge(g, u, v) == true` + - `contains_in_edge(g, v, u) == true` + + After `erase_edge(...)`: + - Both forward and reverse entries removed. + +5. **`erase_vertex`** — removes all incident edges from both directions. + +6. **`clear_vertex`** — removes all edges for vertex in both directions. + +7. **Copy/move** — verify reverse adjacency is preserved. + +8. **Non-bidirectional unchanged** — `Bidirectional=false` graph compiles and + works identically to before. `static_assert(!bidirectional_adjacency_list)`. + +9. **Views integration** — `in_incidence(g, u)` and `in_neighbors(g, u)` work + on bidirectional dynamic_graph. + +10. Test with at least 2 trait types (e.g., `vov` and `vol`). + +#### 8.6 Register test and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] All 6 mutator invariants from design doc §10.5 are tested and pass. +- [ ] Non-bidirectional mode has identical behavior (no regressions). +- [ ] `bidirectional_adjacency_list` concept satisfied. + +--- + +## Phase 9 — Algorithm updates (Kosaraju + transpose_view) + +**Goal:** Optimize Kosaraju's SCC to use `in_edges()` when available; +add `transpose_view` as a zero-cost adaptor. + +**Why ninth:** Algorithms depend on container support (Phase 8) and the +edge accessor infrastructure (Phase 7). + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/algorithm/connected_components.hpp` | Add `if constexpr (bidirectional_adjacency_list)` branch using `in_edges_accessor` | +| `tests/algorithms/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `include/graph/views/transpose.hpp` | `transpose_view` wrapper | +| `tests/algorithms/test_scc_bidirectional.cpp` | SCC tests with bidirectional graph | +| `tests/views/test_transpose.cpp` | Transpose view tests | + +### Steps + +#### 9.1 Add `transpose_view` + +A lightweight wrapper that swaps `edges()` ↔ `in_edges()`: + +```cpp +template +class transpose_view { + G* g_; +public: + // ADL friends: edges(tv, u) → in_edges(*g_, u) + // in_edges(tv, u) → edges(*g_, u) + // Forward all other CPOs to underlying graph +}; +``` + +#### 9.2 Optimize Kosaraju's SCC + +Add a compile-time branch: +```cpp +if constexpr (bidirectional_adjacency_list) { + // Use in_edges_accessor for second DFS pass +} else { + // Keep existing edge-rebuild approach +} +``` + +#### 9.3 Create tests and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] SCC produces correct results on bidirectional graph. +- [ ] `transpose_view` swaps edge directions correctly. + +--- + +## Phase 10 — Documentation + +**Goal:** Update all documentation per design doc §13. + +### Files to modify + +Per the table in design doc §13.1 (11 files), plus: + +| File | Action | +|---|---| +| `docs/index.md` | Add "Bidirectional edge access" feature | +| `docs/getting-started.md` | Add incoming edges section | +| `docs/user-guide/views.md` | Add `in_incidence`, `in_neighbors` | +| `docs/user-guide/algorithms.md` | Note bidirectional benefits | +| `docs/user-guide/containers.md` | Document `Bidirectional` parameter | +| `docs/reference/concepts.md` | Add `bidirectional_adjacency_list` | +| `docs/reference/cpo-reference.md` | Add all incoming CPOs | +| `docs/reference/type-aliases.md` | Add `in_edge_t` etc. | +| `docs/reference/adjacency-list-interface.md` | Add incoming edge section | +| `docs/contributing/cpo-implementation.md` | Add `in_edges` example | +| `README.md` | Update feature highlights | +| `CHANGELOG.md` | Add entry | + +### Files to create + +| File | Content | +|---|---| +| `docs/user-guide/bidirectional-access.md` | Tutorial guide | + +### Steps + +1. Update each file per the table. +2. Create the tutorial guide with examples using `in_edges`, `in_incidence`, + reverse BFS, and bidirectional `dynamic_graph`. +3. Add CHANGELOG entry. +4. Build docs and verify no broken links. + +### Merge gate + +- [ ] All docs updated. +- [ ] Tutorial compiles (code examples are tested). +- [ ] No broken internal links. + +--- + +## Phase summary + +| Phase | Title | New files | Modified files | Key deliverable | +|---|---|---|---|---| +| 1 | `in_edges`/`in_degree` CPOs + aliases | 1 test | 2 | Core CPOs + type aliases | +| 2 | `find_in_edge`/`contains_in_edge` + traits | 3 tests | 3 | Complete CPO surface | +| 3 | Concepts + re-exports | 1 test | 2 | `bidirectional_adjacency_list` concept | +| 4 | Undirected container support | 1 test | 1 | First real bidirectional container | +| 5 | `in_incidence`/`in_neighbors` views | 2 headers + 2 tests | 1 | Incoming-edge views | +| 6 | Pipe-syntax adaptors | — | 2 | `g \| in_incidence(uid)` | +| 7 | BFS/DFS/topo EdgeAccessor | 1 header + 1 test | 3 | Reverse traversal | +| 8 | `dynamic_graph` bidirectional | 1 test | 1 | Directed bidirectional container | +| 9 | Algorithms | 2 headers + 2 tests | 1 | Kosaraju + transpose | +| 10 | Documentation | 1 guide | 12 | Complete docs | + +**Total estimated effort:** 11-15 days (same as design doc estimate) + +--- + +## Safety principles + +1. **Each phase adds only; nothing is removed or renamed.** + Existing tests pass between every phase. + +2. **Default template arguments preserve source compatibility.** + `EdgeAccessor = out_edges_accessor` means no existing call site changes. + +3. **Stub graphs in early phases isolate CPO testing from container code.** + Phases 1-3 don't touch any container — they use lightweight test stubs. + +4. **The simplest container change comes first (Phase 4).** + Undirected just forwards to the existing `edges()` function. + +5. **The most complex change (Phase 8) comes last in the implementation + sequence**, after all the infrastructure it depends on is proven. + +6. **Each phase has an explicit merge gate** — a checklist of conditions + that must pass before proceeding. diff --git a/docs/archive/edge_map_analysis.md b/docs/archive/edge_map_analysis.md index cb036b0..b2165e0 100644 --- a/docs/archive/edge_map_analysis.md +++ b/docs/archive/edge_map_analysis.md @@ -288,7 +288,7 @@ if constexpr (is_map_based_edge_container) { ### 4.1 Minimal Viable Changes -**Required for Phase 4.3.2 (voem_graph_traits):** +**Required for Phase 4.3.2 (vom_graph_traits):** 1. **Add `is_map_based_edge_container` concept** (container_utility.hpp) ```cpp @@ -331,7 +331,7 @@ These can be added later in Phase 4.3: ### 4.3 Testing Strategy -For `voem_graph_traits` (vector vertices + map edges): +For `vom_graph_traits` (vector vertices + map edges): 1. **Basic functionality tests:** - Edge insertion (automatic target_id keying) @@ -346,7 +346,7 @@ For `voem_graph_traits` (vector vertices + map edges): - `contains_edge(g, uid, vid)` works correctly 3. **Comparison with existing traits:** - - voem should behave like vov except: + - vom should behave like vov except: - No parallel edges (map uniqueness) - Edges sorted by target_id - O(log n) lookup instead of O(n) @@ -405,7 +405,7 @@ For `voem_graph_traits` (vector vertices + map edges): **Problem:** Map uniqueness means only one edge per target, even if multiple edges should exist. **Mitigation:** -- Document this limitation clearly in voem/moem trait headers +- Document this limitation clearly in vom/mom trait headers - For multigraphs, users should use vector/list/set edge containers - This is a feature, not a bug: map-based edges are for graphs with unique edges @@ -444,21 +444,21 @@ For typical edge (1-2 VId members): ~8-16 bytes overhead per edge - [x] Design edge_descriptor changes for map-based edges - [x] Document findings in edge_map_analysis.md -### Phase 4.3.2 (voem_graph_traits) - ⏳ PENDING +### Phase 4.3.2 (vom_graph_traits) - ⏳ PENDING - [ ] Add `is_map_based_edge_container` concept to container_utility.hpp - [ ] Add `emplace_edge` helper function to container_utility.hpp - [ ] Update `edge_descriptor::target_id()` to handle map pair unwrapping - [ ] Update `load_edges` functions to use map-aware edge construction -- [ ] Create voem_graph_traits.hpp -- [ ] Create test_dynamic_graph_voem.cpp with basic tests -- [ ] Create test_dynamic_graph_cpo_voem.cpp with CPO tests +- [ ] Create vom_graph_traits.hpp +- [ ] Create test_dynamic_graph_vom.cpp with basic tests +- [ ] Create test_dynamic_graph_cpo_vom.cpp with CPO tests - [ ] Verify all tests pass -### Phase 4.3.3 (moem_graph_traits) - ⏳ PENDING +### Phase 4.3.3 (mom_graph_traits) - ⏳ PENDING -- [ ] Create moem_graph_traits.hpp (reuse voem infrastructure) -- [ ] Create test files for moem +- [ ] Create mom_graph_traits.hpp (reuse vom infrastructure) +- [ ] Create test files for mom - [ ] Verify all tests pass ## 9. Conclusion @@ -468,13 +468,13 @@ For typical edge (1-2 VId members): ~8-16 bytes overhead per edge 1. **Container Utility:** Add concept detection and helper for map pair construction 2. **Edge Descriptor:** Update target_id extraction to unwrap map pairs 3. **Load Functions:** Add conditional logic for map-based edge insertion -4. **Trait Headers:** Define new voem/moem traits with `std::map` +4. **Trait Headers:** Define new vom/mom traits with `std::map` **Estimated Effort:** - Container utility changes: ~50 lines - Edge descriptor updates: ~20 lines - Load function updates: ~30 lines per load variant -- Trait headers: ~40 lines each (voem, moem) +- Trait headers: ~40 lines each (vom, mom) - Test files: ~2000 lines per trait (existing template) **Total:** ~200 lines of core changes + ~4000 lines of tests for two new traits. diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 0000000..6eb18af --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GRAPH v3 + \ No newline at end of file diff --git a/docs/contributing/architecture.md b/docs/contributing/architecture.md index 704fe67..0ac4f4f 100644 --- a/docs/contributing/architecture.md +++ b/docs/contributing/architecture.md @@ -41,7 +41,7 @@ graph-v3/ │ │ ├── jaccard.hpp │ │ └── traversal_common.hpp │ ├── container/ # Library-provided containers -│ │ ├── dynamic_graph.hpp # 26 trait combinations +│ │ ├── dynamic_graph.hpp # 27 trait combinations │ │ ├── compressed_graph.hpp # CSR format │ │ ├── undirected_adjacency_list.hpp │ │ ├── container_utility.hpp @@ -206,7 +206,7 @@ Tests mirror the source structure: |----------------|---------------| | `tests/adj_list/` | Descriptor construction, CPO dispatch, type aliases | | `tests/algorithms/` | One test file per algorithm (Dijkstra, BFS, DFS, …) | -| `tests/container/` | Container conformance — all 26 trait combinations | +| `tests/container/` | Container conformance — all 27 trait combinations | | `tests/edge_list/` | Edge list model tests | | `tests/views/` | View iteration and composition | | `tests/common/` | Shared test utilities and helpers | diff --git a/docs/contributing/cpo-implementation.md b/docs/contributing/cpo-implementation.md index 5b4c00f..8c107fd 100644 --- a/docs/contributing/cpo-implementation.md +++ b/docs/contributing/cpo-implementation.md @@ -829,6 +829,7 @@ strategy: 8. **Mark `[[nodiscard]]`** on CPOs that return values. 9. **Mark `constexpr`** on all CPOs — enable compile-time evaluation where possible. 10. **Document compiler workarounds** if any are needed, with `#if defined(_MSC_VER)` guards. +11. **Use `decltype(auto)` for value CPOs** — the three value CPOs (`vertex_value`, `edge_value`, `graph_value`) must return `decltype(auto)` so that the exact return type of the resolved member/ADL/default function is preserved. Return-by-value (`T`), return-by-reference (`T&`), return-by-const-reference (`const T&`), and return-by-rvalue-reference (`T&&`) must all be forwarded faithfully without decay or copy. ### `_Fake_copy_init` Pattern (Advanced) @@ -938,6 +939,7 @@ For each new CPO: - [ ] `_fn::operator()` uses `if constexpr` chain (no separate overloads) - [ ] `noexcept` specification references `_Choice._No_throw` - [ ] `[[nodiscard]]` on operator() +- [ ] Value CPOs use `decltype(auto)` return type to preserve by-value/by-ref/by-const-ref/by-rvalue-ref semantics - [ ] `static_assert` with helpful message in the `_none` branch - [ ] `std::remove_cvref_t` used consistently for `_Choice` lookups - [ ] Type alias defined after the CPO (if applicable) diff --git a/docs/getting-started.md b/docs/getting-started.md index 54d470f..306170a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -112,7 +112,7 @@ Vertices: 4 ### Using `dynamic_graph` For richer features — edge values, flexible container selection, partitioning — -use `dynamic_graph` with one of the 26 trait combinations. +use `dynamic_graph` with one of the 27 trait combinations. ```cpp #include diff --git a/docs/index.md b/docs/index.md index 897c9bd..64b926a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,14 @@ + + + +
graph-v3 logo + # graph-v3 Documentation > A modern C++20 graph library — 13 algorithms, 7 lazy views, 3 containers, 4261+ tests. +
+ --- ## For Users @@ -9,7 +16,7 @@ - [Getting Started](getting-started.md) — installation, first graph, first algorithm - [Adjacency Lists](user-guide/adjacency-lists.md) — range-of-ranges model, concepts, CPOs - [Edge Lists](user-guide/edge-lists.md) — flat sourced-edge model, concepts, patterns -- [Containers](user-guide/containers.md) — `dynamic_graph`, `compressed_graph`, `undirected_adjacency_list`, 26 trait combinations +- [Containers](user-guide/containers.md) — `dynamic_graph`, `compressed_graph`, `undirected_adjacency_list`, 27 trait combinations - [Views](user-guide/views.md) — lazy traversal views (BFS, DFS, topological sort, etc.) - [Algorithms](user-guide/algorithms.md) — Dijkstra, Bellman-Ford, MST, connected components, and more @@ -39,3 +46,4 @@ - [FAQ](FAQ.md) — common questions and answers - [Migration from v2](migration-from-v2.md) — what changed from graph-v2 and how to migrate - [Status & Metrics](status/metrics.md) — canonical counts and implementation matrix +- [Code Coverage](status/coverage.md) — line and function coverage report (95.8% lines, 92.0% functions) diff --git a/docs/migration-from-v2.md b/docs/migration-from-v2.md index 20bd9fe..141c1e6 100644 --- a/docs/migration-from-v2.md +++ b/docs/migration-from-v2.md @@ -50,7 +50,7 @@ edges without holding references to the underlying container. - Vertex storage in `map` and `unordered_map` (for sparse vertex IDs). - Edge storage in `map`, `set`, `unordered_set` (for sorted or deduplicated edges). - Non-integral vertex IDs. - - 26 vertex×edge container combinations via traits (see + - 27 vertex×edge container combinations via traits (see [Containers](user-guide/containers.md)). ### Graph Container Interface diff --git a/docs/reference/cpo-reference.md b/docs/reference/cpo-reference.md index e06767e..2996cd6 100644 --- a/docs/reference/cpo-reference.md +++ b/docs/reference/cpo-reference.md @@ -35,9 +35,9 @@ All CPOs listed below are available in `namespace graph` after | `find_vertex_edge` | `(g, u, v)` / `(g, uid, vid)` | `vertex_edge_iterator_t` | O(deg) | Yes | | `contains_edge` | `(g, uid, vid)` | `bool` | O(deg) | Yes | | `has_edge` | `(g)` | `bool` | O(V) | Yes | -| `vertex_value` | `(g, u)` | deduced | O(1) | No | -| `edge_value` | `(g, uv)` | deduced | O(1) | Yes | -| `graph_value` | `(g)` | deduced | O(1) | No | +| `vertex_value` | `(g, u)` | `decltype(auto)` | O(1) | No | +| `edge_value` | `(g, uv)` | `decltype(auto)` | O(1) | Yes | +| `graph_value` | `(g)` | `decltype(auto)` | O(1) | No | | `partition_id` | `(g, u)` | `partition_id_t` | O(1) | No | | `num_partitions` | `(g)` | integral | O(1) | Yes (1) | @@ -253,19 +253,31 @@ Tests whether the graph has at least one edge. ## Value CPOs +All three value CPOs (`vertex_value`, `edge_value`, `graph_value`) return +`decltype(auto)`, which **preserves the exact return type** of the resolved +member, ADL, or default function. Return-by-value (`T`), +return-by-reference (`T&`), return-by-const-reference (`const T&`), and +return-by-rvalue-reference (`T&&`) are all faithfully forwarded without +decay or copy. + ### `vertex_value(g, u)` ```cpp -auto vertex_value(G& g, vertex_t& u) -> /* deduced */; +auto vertex_value(G& g, vertex_t& u) -> /* decltype(auto) */; ``` Returns the user-defined value associated with vertex `u`. **No default** — the graph must provide this via member or ADL. +| Property | Value | +|----------|-------| +| **Return type** | `decltype(auto)` — preserves by-value, by-ref, by-const-ref, and by-rvalue-ref | +| **Complexity** | O(1) | + ### `edge_value(g, uv)` ```cpp -auto edge_value(G& g, edge_t& uv) -> /* deduced */; +auto edge_value(G& g, edge_t& uv) -> /* decltype(auto) */; ``` Returns the user-defined value associated with edge `uv`. @@ -273,17 +285,23 @@ Returns the user-defined value associated with edge `uv`. | Property | Value | |----------|-------| | **Default** | Resolution chain: `uv.edge_value(g)` → ADL → descriptor → `uv.value` member → tuple `get<1>(uv)` (adj_list) / `get<2>(uv)` (edge list) | +| **Return type** | `decltype(auto)` — preserves by-value, by-ref, by-const-ref, and by-rvalue-ref | | **Complexity** | O(1) | ### `graph_value(g)` ```cpp -auto graph_value(G& g) -> /* deduced */; +auto graph_value(G& g) -> /* decltype(auto) */; ``` Returns the user-defined value associated with the graph itself. **No default** — the graph must provide this. +| Property | Value | +|----------|-------| +| **Return type** | `decltype(auto)` — preserves by-value, by-ref, by-const-ref, and by-rvalue-ref | +| **Complexity** | O(1) | + --- ## Partition CPOs diff --git a/docs/status/coverage.md b/docs/status/coverage.md new file mode 100644 index 0000000..9872b8d --- /dev/null +++ b/docs/status/coverage.md @@ -0,0 +1,126 @@ +# Code Coverage Report + +> **Generated:** 2026-02-21 | **Compiler:** GCC 15.1.0 | **Preset:** `linux-gcc-coverage` +> **Tests:** 4261 passed, 0 failed (100% pass rate) +> **Overall line coverage:** 95.8% (3300 / 3446 lines) +> **Overall function coverage:** 92.0% (27193 / 29567 functions) + +--- + +## Summary by Category + +| Category | Lines Hit | Lines Total | Line Coverage | +|----------|-----------|-------------|---------------| +| Adjacency list infrastructure | 321 | 321 | **100.0%** | +| Algorithms | 739 | 788 | **93.8%** | +| Containers | 902 | 962 | **93.8%** | +| Detail / CPOs | 21 | 21 | **100.0%** | +| Edge list | 15 | 16 | **93.8%** | +| Views | 1302 | 1336 | **97.5%** | + +--- + +## Detailed File Coverage + +### Adjacency List Infrastructure (`adj_list/`) + +All adjacency list descriptor and CPO support headers reach **100% line coverage**. + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `descriptor_traits.hpp` | 10 / 10 | 100.0% | 8 / 8 | 100.0% | +| `detail/graph_cpo.hpp` | 135 / 135 | 100.0% | 4250 / 4253 | 99.9% | +| `edge_descriptor.hpp` | 74 / 74 | 100.0% | 1205 / 1205 | 100.0% | +| `edge_descriptor_view.hpp` | 37 / 37 | 100.0% | 2291 / 2292 | 100.0% | +| `vertex_descriptor.hpp` | 35 / 35 | 100.0% | 1305 / 1306 | 99.9% | +| `vertex_descriptor_view.hpp` | 30 / 30 | 100.0% | 2321 / 2390 | 97.1% | + +### Algorithms (`algorithm/`) + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `articulation_points.hpp` | 53 / 53 | 100.0% | 2 / 2 | 100.0% | +| `bellman_ford_shortest_paths.hpp` | 54 / 63 | 85.7% | 31 / 31 | 100.0% | +| `biconnected_components.hpp` | 68 / 68 | 100.0% | 4 / 4 | 100.0% | +| `breadth_first_search.hpp` | 29 / 29 | 100.0% | 12 / 12 | 100.0% | +| `connected_components.hpp` | 123 / 137 | 89.8% | 10 / 10 | 100.0% | +| `depth_first_search.hpp` | 38 / 38 | 100.0% | 5 / 5 | 100.0% | +| `dijkstra_shortest_paths.hpp` | 54 / 65 | 83.1% | 32 / 36 | 88.9% | +| `jaccard.hpp` | 24 / 24 | 100.0% | 5 / 5 | 100.0% | +| `label_propagation.hpp` | 66 / 69 | 95.7% | 3 / 3 | 100.0% | +| `mis.hpp` | 25 / 28 | 89.3% | 1 / 1 | 100.0% | +| `mst.hpp` | 117 / 126 | 92.9% | 24 / 24 | 100.0% | +| `tc.hpp` | 27 / 27 | 100.0% | 2 / 2 | 100.0% | +| `topological_sort.hpp` | 52 / 52 | 100.0% | 7 / 7 | 100.0% | +| `traversal_common.hpp` | 9 / 9 | 100.0% | 3 / 3 | 100.0% | + +### Containers (`container/`) + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `compressed_graph.hpp` | 198 / 223 | 88.8% | 580 / 606 | 95.7% | +| `container_utility.hpp` | 6 / 6 | 100.0% | 382 / 382 | 100.0% | +| `detail/undirected_adjacency_list_impl.hpp` | 312 / 321 | 97.2% | 158 / 163 | 96.9% | +| `dynamic_graph.hpp` | 286 / 311 | 92.0% | 11429 / 13626 | 83.9% | +| `undirected_adjacency_list.hpp` | 100 / 101 | 99.0% | 126 / 128 | 98.4% | + +### Views (`views/`) + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `adaptors.hpp` | 109 / 109 | 100.0% | 85 / 85 | 100.0% | +| `bfs.hpp` | 211 / 227 | 93.0% | 146 / 157 | 93.0% | +| `dfs.hpp` | 225 / 239 | 94.1% | 257 / 268 | 95.9% | +| `edgelist.hpp` | 219 / 221 | 99.1% | 556 / 578 | 96.2% | +| `incidence.hpp` | 89 / 89 | 100.0% | 463 / 469 | 98.7% | +| `neighbors.hpp` | 102 / 102 | 100.0% | 282 / 286 | 98.6% | +| `search_base.hpp` | 5 / 5 | 100.0% | 11 / 11 | 100.0% | +| `topological_sort.hpp` | 230 / 232 | 99.1% | 248 / 256 | 96.9% | +| `vertexlist.hpp` | 112 / 112 | 100.0% | 467 / 469 | 99.6% | + +### Other + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `detail/edge_cpo.hpp` | 21 / 21 | 100.0% | 461 / 461 | 100.0% | +| `edge_list/edge_list_descriptor.hpp` | 15 / 16 | 93.8% | 21 / 21 | 100.0% | +| `graph_info.hpp` | 0 / 2 | 0.0% | 0 / 2 | 0.0% | + +--- + +## Coverage Gaps + +Files below 90% line coverage, ranked by gap size: + +| File | Line % | Lines Missed | Notes | +|------|--------|--------------|-------| +| `graph_info.hpp` | 0.0% | 2 | Metadata-only header; no executable paths exercised | +| `dijkstra_shortest_paths.hpp` | 83.1% | 11 | Some overload / error-path branches not reached | +| `bellman_ford_shortest_paths.hpp` | 85.7% | 9 | Negative-cycle detection path not fully exercised | +| `compressed_graph.hpp` | 88.8% | 25 | Some construction / accessor overloads untested | +| `connected_components.hpp` | 89.8% | 14 | Sparse-graph and edge-case branches | +| `mis.hpp` | 89.3% | 3 | Minor branch not reached | + +--- + +## How to Reproduce + +```bash +# Configure with coverage preset +cmake --preset linux-gcc-coverage + +# Build +cmake --build --preset linux-gcc-coverage + +# Run tests +ctest --preset linux-gcc-coverage + +# Generate HTML report (output in build/linux-gcc-coverage/coverage_html/) +cd build/linux-gcc-coverage +lcov --directory . --capture --output-file coverage.info --ignore-errors mismatch +lcov --remove coverage.info '/usr/*' '/opt/*' '*/tests/*' '*/build/*' '*/_deps/*' \ + --output-file coverage.info --ignore-errors mismatch +genhtml coverage.info --output-directory coverage_html --ignore-errors mismatch +``` + +Open `build/linux-gcc-coverage/coverage_html/index.html` in a browser for the full interactive report. diff --git a/docs/status/implementation_matrix.md b/docs/status/implementation_matrix.md index eb9d8ec..6e6d985 100644 --- a/docs/status/implementation_matrix.md +++ b/docs/status/implementation_matrix.md @@ -82,9 +82,10 @@ Utility header: `container_utility.hpp`. | `std::list` | Bidirectional | O(1) insertion/removal anywhere | `l` | | `std::set` | Bidirectional | Sorted, deduplicated | `s` | | `std::unordered_set` | Forward | Hash-based, O(1) avg lookup | `us` | -| `std::map` | Bidirectional | Sorted by target_id key | `em` | +| `std::map` | Bidirectional | Sorted by target_id key | `m` | +| `std::unordered_map` | Forward | Hash-based, O(1) avg lookup by target_id | `um` | -### All 26 trait files +### All 27 trait files Naming convention: `{vertex}o{edge}_graph_traits.hpp` @@ -96,26 +97,27 @@ Naming convention: `{vertex}o{edge}_graph_traits.hpp` | 4 | `vol_graph_traits.hpp` | vector | list | | 5 | `vos_graph_traits.hpp` | vector | set | | 6 | `vous_graph_traits.hpp` | vector | unordered_set | -| 7 | `voem_graph_traits.hpp` | vector | map | -| 8 | `dov_graph_traits.hpp` | deque | vector | -| 9 | `dod_graph_traits.hpp` | deque | deque | -| 10 | `dofl_graph_traits.hpp` | deque | forward_list | -| 11 | `dol_graph_traits.hpp` | deque | list | -| 12 | `dos_graph_traits.hpp` | deque | set | -| 13 | `dous_graph_traits.hpp` | deque | unordered_set | -| 14 | `mov_graph_traits.hpp` | map | vector | -| 15 | `mod_graph_traits.hpp` | map | deque | -| 16 | `mofl_graph_traits.hpp` | map | forward_list | -| 17 | `mol_graph_traits.hpp` | map | list | -| 18 | `mos_graph_traits.hpp` | map | set | -| 19 | `mous_graph_traits.hpp` | map | unordered_set | -| 20 | `moem_graph_traits.hpp` | map | map | -| 21 | `uov_graph_traits.hpp` | unordered_map | vector | -| 22 | `uod_graph_traits.hpp` | unordered_map | deque | -| 23 | `uofl_graph_traits.hpp` | unordered_map | forward_list | -| 24 | `uol_graph_traits.hpp` | unordered_map | list | -| 25 | `uos_graph_traits.hpp` | unordered_map | set | -| 26 | `uous_graph_traits.hpp` | unordered_map | unordered_set | +| 7 | `vom_graph_traits.hpp` | vector | map | +| 8 | `voum_graph_traits.hpp` | vector | unordered_map | +| 9 | `dov_graph_traits.hpp` | deque | vector | +| 10 | `dod_graph_traits.hpp` | deque | deque | +| 11 | `dofl_graph_traits.hpp` | deque | forward_list | +| 12 | `dol_graph_traits.hpp` | deque | list | +| 13 | `dos_graph_traits.hpp` | deque | set | +| 14 | `dous_graph_traits.hpp` | deque | unordered_set | +| 15 | `mov_graph_traits.hpp` | map | vector | +| 16 | `mod_graph_traits.hpp` | map | deque | +| 17 | `mofl_graph_traits.hpp` | map | forward_list | +| 18 | `mol_graph_traits.hpp` | map | list | +| 19 | `mos_graph_traits.hpp` | map | set | +| 20 | `mous_graph_traits.hpp` | map | unordered_set | +| 21 | `mom_graph_traits.hpp` | map | map | +| 22 | `uov_graph_traits.hpp` | unordered_map | vector | +| 23 | `uod_graph_traits.hpp` | unordered_map | deque | +| 24 | `uofl_graph_traits.hpp` | unordered_map | forward_list | +| 25 | `uol_graph_traits.hpp` | unordered_map | list | +| 26 | `uos_graph_traits.hpp` | unordered_map | set | +| 27 | `uous_graph_traits.hpp` | unordered_map | unordered_set | --- diff --git a/docs/status/metrics.md b/docs/status/metrics.md index 53191ab..25551b5 100644 --- a/docs/status/metrics.md +++ b/docs/status/metrics.md @@ -13,6 +13,8 @@ | Trait combinations | 26 | `include/graph/container/traits/` | | Test count | 4261 | `ctest --preset linux-gcc-debug` (100% pass, 2026-02-19) | | C++ standard | C++20 | `CMakeLists.txt` line 26 | +| Line coverage | 95.8% (3300 / 3446) | `docs/status/coverage.md` (2026-02-21) | +| Function coverage | 92.0% (27193 / 29567) | `docs/status/coverage.md` (2026-02-21) | | License | BSL-1.0 | `LICENSE` | | CMake minimum | 3.20 | `CMakeLists.txt` line 1 | | Consumer target | `graph::graph3` | `cmake/InstallConfig.cmake` (`find_package(graph3)`) | diff --git a/docs/user-guide/containers.md b/docs/user-guide/containers.md index 4d67993..9d086e2 100644 --- a/docs/user-guide/containers.md +++ b/docs/user-guide/containers.md @@ -137,9 +137,10 @@ container abbreviation, the letter `o`, edge container abbreviation. | `std::list` | Bidirectional | O(1) insertion/removal anywhere. Edges added to the back. | `l` | | `std::set` | Bidirectional | Sorted, deduplicated target id | `s` | | `std::unordered_set` | Forward | Hash-based, O(1) avg lookup, deduplicated target id| `us` | -| `std::map` | Bidirectional | Sorted by target_id key, deduplicated target id | `em` | +| `std::map` | Bidirectional | Sorted by target_id key, deduplicated target id | `m` | +| `std::unordered_map` | Forward | Hash-based, O(1) avg lookup, deduplicated target id | `um` | -#### Full 26-combination matrix +#### Full 27-combination matrix Each trait struct is in `graph::container` and has its own header in `include/graph/container/traits/`. @@ -152,7 +153,8 @@ Each trait struct is in `graph::container` and has its own header in | `vol_graph_traits` | `vector` | `list` | `traits/vol_graph_traits.hpp` | | `vos_graph_traits` | `vector` | `set` | `traits/vos_graph_traits.hpp` | | `vous_graph_traits` | `vector` | `unordered_set` | `traits/vous_graph_traits.hpp` | -| `voem_graph_traits` | `vector` | `map` | `traits/voem_graph_traits.hpp` | +| `vom_graph_traits` | `vector` | `map` | `traits/vom_graph_traits.hpp` | +| `voum_graph_traits` | `vector` | `unordered_map` | `traits/voum_graph_traits.hpp` | | `dov_graph_traits` | `deque` | `vector` | `traits/dov_graph_traits.hpp` | | `dod_graph_traits` | `deque` | `deque` | `traits/dod_graph_traits.hpp` | | `dofl_graph_traits` | `deque` | `forward_list` | `traits/dofl_graph_traits.hpp` | @@ -165,7 +167,7 @@ Each trait struct is in `graph::container` and has its own header in | `mol_graph_traits` | `map` | `list` | `traits/mol_graph_traits.hpp` | | `mos_graph_traits` | `map` | `set` | `traits/mos_graph_traits.hpp` | | `mous_graph_traits` | `map` | `unordered_set` | `traits/mous_graph_traits.hpp` | -| `moem_graph_traits` | `map` | `map` | `traits/moem_graph_traits.hpp` | +| `mom_graph_traits` | `map` | `map` | `traits/mom_graph_traits.hpp` | | `uov_graph_traits` | `unordered_map` | `vector` | `traits/uov_graph_traits.hpp` | | `uod_graph_traits` | `unordered_map` | `deque` | `traits/uod_graph_traits.hpp` | | `uofl_graph_traits` | `unordered_map` | `forward_list` | `traits/uofl_graph_traits.hpp` | @@ -268,7 +270,7 @@ G g; // All CPOs, views, and algorithms work as normal ``` -> **Tip:** Model your traits struct on one of the 26 built-in traits headers in +> **Tip:** Model your traits struct on one of the 27 built-in traits headers in > `include/graph/container/traits/`. The simplest starting point is > `vov_graph_traits.hpp`. @@ -362,8 +364,8 @@ for (auto&& u : graph::vertices(g)) { #include namespace graph::container { -template class VContainer = std::vector, @@ -405,16 +407,13 @@ endpoint). Use `edges_size() / 2` for the unique edge count. | Parameter | Default | Description | |-----------|---------|-------------| -| `VV` | `void` | Vertex value type | | `EV` | `void` | Edge value type | +| `VV` | `void` | Vertex value type | | `GV` | `void` | Graph value type | | `VId` | `uint32_t` | Vertex ID type (integral) | | `VContainer` | `std::vector` | Vertex storage container template | | `Alloc` | `std::allocator` | Allocator | -> **Note:** The template parameter order is `VV, EV, GV` (vertex-first), which -> differs from `dynamic_graph` and `compressed_graph` (`EV, VV, GV`). - --- ## 4. Range-of-Ranges Graphs (No Library Graph Container Required) @@ -699,7 +698,7 @@ struct), if they are mutable. | Memory efficiency | Medium | Best (CSR) | Highest overhead | Zero overhead (existing data) | | Cache locality | Depends on trait | Excellent | Poor (linked-list) | Depends on containers used | | Multi-partite | No | Yes | No | No | -| Container flexibility | 26 trait combos | Fixed (CSR) | Configurable random access vertex container | Any forward_range of forward_ranges | +| Container flexibility | 27 trait combos | Fixed (CSR) | Configurable random access vertex container | Any forward_range of forward_ranges | **Custom graphs.** See [Section 5 (Custom Graphs)](#5-custom-graphs) for how to use your own graph data structure with all library views and algorithms by overriding graph CPOs. diff --git a/include/graph/adj_list/detail/graph_cpo.hpp b/include/graph/adj_list/detail/graph_cpo.hpp index 96f601d..f0237a1 100644 --- a/include/graph/adj_list/detail/graph_cpo.hpp +++ b/include/graph/adj_list/detail/graph_cpo.hpp @@ -2198,11 +2198,19 @@ namespace _cpo_impls { * * This provides access to user-defined vertex properties/data stored in the graph. * + * **Return-type preservation guarantee:** The `operator()` returns `decltype(auto)`, + * which faithfully preserves the exact return type of the resolved function: + * return-by-value (`T`), return-by-reference (`T&`), return-by-const-reference + * (`const T&`), and return-by-rvalue-reference (`T&&`) are all forwarded without + * decay or copy. + * * @tparam G Graph type * @tparam U Vertex descriptor type (constrained to be a vertex_descriptor_type) * @param g Graph container * @param u Vertex descriptor - * @return Reference to the vertex value/data + * @return Exactly the type returned by the resolved dispatch path (`decltype(auto)`). + * By-value, by-reference, by-const-reference, and by-rvalue-reference + * are all preserved. */ template [[nodiscard]] constexpr decltype(auto) operator()(G&& g, const U& u) const @@ -2292,9 +2300,17 @@ namespace _cpo_impls { * This provides access to user-defined graph-level properties/metadata * stored in the graph container (e.g., name, creation date, statistics). * + * **Return-type preservation guarantee:** The `operator()` returns `decltype(auto)`, + * which faithfully preserves the exact return type of the resolved function: + * return-by-value (`T`), return-by-reference (`T&`), return-by-const-reference + * (`const T&`), and return-by-rvalue-reference (`T&&`) are all forwarded without + * decay or copy. + * * @tparam G Graph type * @param g Graph container - * @return Reference to the graph value/properties (or by-value if custom implementation returns by-value) + * @return Exactly the type returned by the resolved dispatch path (`decltype(auto)`). + * By-value, by-reference, by-const-reference, and by-rvalue-reference + * are all preserved. */ template [[nodiscard]] constexpr decltype(auto) operator()(G&& g) const noexcept(_Choice>._No_throw) diff --git a/include/graph/container/detail/undirected_adjacency_list_api.hpp b/include/graph/container/detail/undirected_adjacency_list_api.hpp index 822d9bf..0508748 100644 --- a/include/graph/container/detail/undirected_adjacency_list_api.hpp +++ b/include/graph/container/detail/undirected_adjacency_list_api.hpp @@ -71,54 +71,54 @@ using const_neighbor_range_t = typename G::const_neighbor_range; // // Uniform API: Common functions (accepts graph, vertex and edge) // -template class VContainer, typename Alloc> -constexpr auto graph_value(const undirected_adjacency_list& g) - -> const graph_value_t>& { +constexpr auto graph_value(const undirected_adjacency_list& g) + -> const graph_value_t>& { return user_value(g); } // // API vertex functions // -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_vertex_iterator_t> u) - -> vertex_id_t> { - return static_cast>>(u - +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_vertex_iterator_t> u) + -> vertex_id_t> { + return static_cast>>(u - g.vertices().begin()); } -template class VContainer, typename Alloc> -constexpr auto vertex_value(undirected_adjacency_list& g, - vertex_iterator_t> u) - -> vertex_value_t>& { +constexpr auto vertex_value(undirected_adjacency_list& g, + vertex_iterator_t> u) + -> vertex_value_t>& { return user_value(*u); } -template class VContainer, typename Alloc> -constexpr auto vertex_value(const undirected_adjacency_list& g, - const_vertex_iterator_t> u) - -> const vertex_value_t>& { +constexpr auto vertex_value(const undirected_adjacency_list& g, + const_vertex_iterator_t> u) + -> const vertex_value_t>& { return user_value(*u); } @@ -128,128 +128,128 @@ constexpr auto vertex_value(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto edge_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> edge_id_t> { +constexpr auto edge_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> edge_id_t> { return uv.edge_id(g); } -template class VContainer, typename Alloc> -constexpr auto edge_id(const undirected_adjacency_list& g, - const_vertex_iterator_t> u, - const_vertex_iterator_t> v) - -> edge_id_t> { - return edge_id_t>(vertex_id(g, u), vertex_id(g, v)); +constexpr auto edge_id(const undirected_adjacency_list& g, + const_vertex_iterator_t> u, + const_vertex_iterator_t> v) + -> edge_id_t> { + return edge_id_t>(vertex_id(g, u), vertex_id(g, v)); } -template class VContainer, typename Alloc> -constexpr auto edge_value(undirected_adjacency_list& g, - edge_iterator_t> uv) - -> edge_value_t>& { +constexpr auto edge_value(undirected_adjacency_list& g, + edge_iterator_t> uv) + -> edge_value_t>& { return user_value(*uv); } -template class VContainer, typename Alloc> -constexpr auto edge_value(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> const edge_value_t>& { +constexpr auto edge_value(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> const edge_value_t>& { return user_value(*uv); } -template class VContainer, typename Alloc> -constexpr auto vertex(undirected_adjacency_list& g, - edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> vertex_iterator_t> { +constexpr auto vertex(undirected_adjacency_list& g, + edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> vertex_iterator_t> { return uv->other_vertex(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex(const undirected_adjacency_list& g, - const_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> const_vertex_iterator_t> { +constexpr auto vertex(const undirected_adjacency_list& g, + const_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> const_vertex_iterator_t> { return uv->other_vertex(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex(undirected_adjacency_list& g, - edge_iterator_t> uv, - vertex_id_t> source_id) - -> vertex_iterator_t> { +constexpr auto vertex(undirected_adjacency_list& g, + edge_iterator_t> uv, + vertex_id_t> source_id) + -> vertex_iterator_t> { return uv->other_vertex(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto vertex(const undirected_adjacency_list& g, - const_edge_iterator_t> uv, - vertex_id_t> source_id) - -> const_vertex_iterator_t> { +constexpr auto vertex(const undirected_adjacency_list& g, + const_edge_iterator_t> uv, + vertex_id_t> source_id) + -> const_vertex_iterator_t> { return uv->other_vertex(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> vertex_id_t> { return uv->other_vertex_id(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv, - vertex_id_t> source_id) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv, + vertex_id_t> source_id) + -> vertex_id_t> { return uv->other_vertex_id(g, source_id); } @@ -259,83 +259,83 @@ constexpr auto vertex_id(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto vertices(undirected_adjacency_list& g) - -> vertex_range_t> { +constexpr auto vertices(undirected_adjacency_list& g) + -> vertex_range_t> { return g.vertices(); } -template class VContainer, typename Alloc> -constexpr auto vertices(const undirected_adjacency_list& g) - -> const_vertex_range_t> { +constexpr auto vertices(const undirected_adjacency_list& g) + -> const_vertex_range_t> { return g.vertices(); } -template class VContainer, typename Alloc> -constexpr auto try_find_vertex(undirected_adjacency_list& g, - vertex_id_t> id) - -> vertex_iterator_t> { +constexpr auto try_find_vertex(undirected_adjacency_list& g, + vertex_id_t> id) + -> vertex_iterator_t> { return g.try_find_vertex(id); } -template class VContainer, typename Alloc> -constexpr auto try_find_vertex(const undirected_adjacency_list& g, - vertex_id_t> id) - -> const_vertex_iterator_t> { +constexpr auto try_find_vertex(const undirected_adjacency_list& g, + vertex_id_t> id) + -> const_vertex_iterator_t> { return g.try_find_vertex(id); } -template class VContainer, typename Alloc> -void reserve_vertices(undirected_adjacency_list& g, - vertex_size_t> n) { +void reserve_vertices(undirected_adjacency_list& g, + vertex_size_t> n) { g.reserve_vertices(n); } -template class VContainer, typename Alloc> -void resize_vertices(undirected_adjacency_list& g, - vertex_size_t> n) { +void resize_vertices(undirected_adjacency_list& g, + vertex_size_t> n) { g.resize(n); } -template class VContainer, typename Alloc> -void resize_vertices(undirected_adjacency_list& g, - vertex_size_t> n, - const vertex_value_t>& val) { +void resize_vertices(undirected_adjacency_list& g, + vertex_size_t> n, + const vertex_value_t>& val) { g.resize(n, val); } @@ -344,38 +344,38 @@ void resize_vertices(undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto edges(undirected_adjacency_list& g) - -> edge_range_t> { +constexpr auto edges(undirected_adjacency_list& g) + -> edge_range_t> { return g.edges(); } -template class VContainer, typename Alloc> -constexpr auto edges(const undirected_adjacency_list& g) - -> const_edge_range_t> { +constexpr auto edges(const undirected_adjacency_list& g) + -> const_edge_range_t> { return g.edges(); } -template class VContainer, typename Alloc> -constexpr auto find_edge(undirected_adjacency_list& g, - vertex_iterator_t> u, - vertex_iterator_t> v) - -> edge_iterator_t> { +constexpr auto find_edge(undirected_adjacency_list& g, + vertex_iterator_t> u, + vertex_iterator_t> v) + -> edge_iterator_t> { auto e = edges(g); for (auto uv = e.begin(); uv != e.end(); ++uv) if (vertex(g, uv, u) == v) @@ -383,16 +383,16 @@ constexpr auto find_edge(undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto find_edge(const undirected_adjacency_list& g, - const_vertex_iterator_t> u, - const_vertex_iterator_t> v) - -> const_edge_iterator_t> { +constexpr auto find_edge(const undirected_adjacency_list& g, + const_vertex_iterator_t> u, + const_vertex_iterator_t> v) + -> const_edge_iterator_t> { auto e = edges(g); for (auto uv = e.begin(); uv != e.end(); ++uv) if (vertex(g, uv, u) == v) @@ -400,29 +400,29 @@ constexpr auto find_edge(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto find_edge(undirected_adjacency_list& g, - vertex_id_t> uid, - vertex_id_t> vid) - -> edge_iterator_t> { +constexpr auto find_edge(undirected_adjacency_list& g, + vertex_id_t> uid, + vertex_id_t> vid) + -> edge_iterator_t> { return find_edge(g, try_find_vertex(g, uid), try_find_vertex(g, vid)); } -template class VContainer, typename Alloc> -constexpr auto find_edge(const undirected_adjacency_list& g, - vertex_id_t> uid, - vertex_id_t> vid) - -> const_edge_iterator_t> { +constexpr auto find_edge(const undirected_adjacency_list& g, + vertex_id_t> uid, + vertex_id_t> vid) + -> const_edge_iterator_t> { return find_edge(g, try_find_vertex(g, uid), try_find_vertex(g, vid)); } @@ -431,141 +431,141 @@ constexpr auto find_edge(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto vertex(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> vertex_iterator_t> { +constexpr auto vertex(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> vertex_iterator_t> { return uv->other_vertex(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> const_vertex_iterator_t> { +constexpr auto vertex(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> const_vertex_iterator_t> { return uv->other_vertex(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv, - vertex_id_t> source_id) - -> vertex_iterator_t> { +constexpr auto vertex(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv, + vertex_id_t> source_id) + -> vertex_iterator_t> { return uv->other_vertex(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto vertex(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv, - vertex_id_t> source_id) - -> const_vertex_iterator_t> { +constexpr auto vertex(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv, + vertex_id_t> source_id) + -> const_vertex_iterator_t> { return uv->other_vertex(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> vertex_id_t> { return uv->other_vertex_id(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv, - vertex_id_t> source_id) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv, + vertex_id_t> source_id) + -> vertex_id_t> { return uv->other_vertex_id(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto edge_value(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv) - -> edge_value_t>& { +constexpr auto edge_value(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv) + -> edge_value_t>& { return user_value(*uv); } -template class VContainer, typename Alloc> constexpr auto -edge_value(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> const edge_value_t>& { +edge_value(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> const edge_value_t>& { return user_value(*uv); } -template class VContainer, typename Alloc> -constexpr auto edges(undirected_adjacency_list& g, - vertex_iterator_t> u) - -> vertex_edge_range_t> { +constexpr auto edges(undirected_adjacency_list& g, + vertex_iterator_t> u) + -> vertex_edge_range_t> { return u->edges(g, vertex_id(g, u)); } -template class VContainer, typename Alloc> -constexpr auto edges(const undirected_adjacency_list& g, - const_vertex_iterator_t> u) - -> const_vertex_edge_range_t> { +constexpr auto edges(const undirected_adjacency_list& g, + const_vertex_iterator_t> u) + -> const_vertex_edge_range_t> { return u->edges(g, vertex_id(g, u)); } -template class VContainer, typename Alloc> -constexpr auto try_find_vertex_edge(undirected_adjacency_list& g, - vertex_iterator_t> u, - vertex_iterator_t> v) - -> vertex_edge_iterator_t> { +constexpr auto try_find_vertex_edge(undirected_adjacency_list& g, + vertex_iterator_t> u, + vertex_iterator_t> v) + -> vertex_edge_iterator_t> { auto e = edges(g, u); for (auto uv = begin(e); uv != end(e); ++uv) if (vertex(g, uv, u) == v) @@ -573,17 +573,17 @@ constexpr auto try_find_vertex_edge(undirected_adjacency_list class VContainer, typename Alloc> constexpr auto -try_find_vertex_edge(const undirected_adjacency_list& g, - const vertex_iterator_t>& u, - const vertex_iterator_t>& v) - -> const_vertex_edge_iterator_t> { +try_find_vertex_edge(const undirected_adjacency_list& g, + const vertex_iterator_t>& u, + const vertex_iterator_t>& v) + -> const_vertex_edge_iterator_t> { auto e = edges(g, u); for (auto uv = begin(e); uv != end(e); ++uv) if (vertex(g, uv, u) == v) @@ -591,72 +591,72 @@ try_find_vertex_edge(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto try_find_vertex_edge(undirected_adjacency_list& g, - vertex_id_t> uid, - vertex_id_t> vid) - -> vertex_edge_iterator_t> { +constexpr auto try_find_vertex_edge(undirected_adjacency_list& g, + vertex_id_t> uid, + vertex_id_t> vid) + -> vertex_edge_iterator_t> { return try_find_vertex_edge(g, try_find_vertex(g, uid), try_find_vertex(g, vid)); } -template class VContainer, typename Alloc> -constexpr auto try_find_vertex_edge(const undirected_adjacency_list& g, - vertex_id_t> uid, - vertex_id_t> vid) - -> const_vertex_edge_iterator_t> { +constexpr auto try_find_vertex_edge(const undirected_adjacency_list& g, + vertex_id_t> uid, + vertex_id_t> vid) + -> const_vertex_edge_iterator_t> { return try_find_vertex_edge(g, try_find_vertex(g, uid), try_find_vertex(g, vid)); } -template class VContainer, typename Alloc> -constexpr auto erase_edge(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv) - -> vertex_edge_iterator_t> { - edge_t>* uv_ptr = &*uv; +constexpr auto erase_edge(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv) + -> vertex_edge_iterator_t> { + edge_t>* uv_ptr = &*uv; ++uv; delete uv_ptr; return uv; } -template class VContainer, typename Alloc> -constexpr auto erase_edges(undirected_adjacency_list& g, - vertex_edge_range_t> uv_rng) - -> vertex_edge_iterator_t> { +constexpr auto erase_edges(undirected_adjacency_list& g, + vertex_edge_range_t> uv_rng) + -> vertex_edge_iterator_t> { - vertex_edge_iterator_t> uv = ranges::begin(uv_rng); + vertex_edge_iterator_t> uv = ranges::begin(uv_rng); while (uv != ranges::end(uv_rng)) uv = erase_edge(g, uv); return uv; } -template class VContainer, typename Alloc> -constexpr void clear_edges(undirected_adjacency_list& g, - vertex_iterator_t> u) { +constexpr void clear_edges(undirected_adjacency_list& g, + vertex_iterator_t> u) { u->clear_edges(g); } @@ -664,39 +664,39 @@ constexpr void clear_edges(undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_neighbor_iterator_t> u) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_neighbor_iterator_t> u) + -> vertex_id_t> { return u.other_vertex_id(); } -template class VContainer, typename Alloc> -constexpr auto vertices(undirected_adjacency_list& g, - vertex_iterator_t> u) - -> neighbor_range_t> { +constexpr auto vertices(undirected_adjacency_list& g, + vertex_iterator_t> u) + -> neighbor_range_t> { return u->vertices(g, vertex_id(g, u)); } -template class VContainer, typename Alloc> -constexpr auto vertices(const undirected_adjacency_list& g, - const_vertex_iterator_t> u) - -> const_neighbor_range_t> { +constexpr auto vertices(const undirected_adjacency_list& g, + const_vertex_iterator_t> u) + -> const_neighbor_range_t> { return u->vertices(g, vertex_id(g, u)); } @@ -709,152 +709,152 @@ constexpr auto vertices(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto target_vertex(undirected_adjacency_list& g, - edge_iterator_t> uv) - -> vertex_iterator_t> { +constexpr auto target_vertex(undirected_adjacency_list& g, + edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->target_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto target_vertex(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> const_vertex_iterator_t> { +constexpr auto target_vertex(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> const_vertex_iterator_t> { return uv->target_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto target_vertex(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv) - -> vertex_iterator_t> { +constexpr auto target_vertex(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->target_vertex(g); } -template class VContainer, typename Alloc> constexpr auto -target_vertex(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> const_vertex_iterator_t> { +target_vertex(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> const_vertex_iterator_t> { return uv->target_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto target_vertex_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> vertex_id_t> { +constexpr auto target_vertex_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> vertex_id_t> { return uv->target_vertex_id(g); } -template class VContainer, typename Alloc> constexpr auto -target_vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> vertex_id_t> { +target_vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> vertex_id_t> { return uv->target_vertex_id(g); } -template class VContainer, typename Alloc> -constexpr auto source_vertex(undirected_adjacency_list& g, - edge_iterator_t> uv) - -> vertex_iterator_t> { +constexpr auto source_vertex(undirected_adjacency_list& g, + edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->source_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto source_vertex(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> const_vertex_iterator_t> { +constexpr auto source_vertex(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> const_vertex_iterator_t> { return uv->source_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto source_vertex(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv) - -> vertex_iterator_t> { +constexpr auto source_vertex(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->source_vertex(g); } -template class VContainer, typename Alloc> constexpr auto -source_vertex(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> const_vertex_iterator_t> { +source_vertex(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> const_vertex_iterator_t> { return uv->source_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto source_vertex_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> vertex_id_t> { +constexpr auto source_vertex_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> vertex_id_t> { return uv->list_owner_id(); } -template class VContainer, typename Alloc> constexpr auto -source_vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> vertex_id_t> { +source_vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> vertex_id_t> { return uv->list_owner_id(); } @@ -867,23 +867,23 @@ source_vertex_id(const undirected_adjacency_list -constexpr auto vertex(undirected_adjacency_list& g, vertex_edge_iterator_t> uv) - -> vertex_iterator_t> { +template +constexpr auto vertex(undirected_adjacency_list& g, vertex_edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->target_vertex(g); } -template -constexpr auto vertex(const undirected_adjacency_list& g, - const_vertex_edge_t> uv) - -> const_vertex_iterator_t> { +template +constexpr auto vertex(const undirected_adjacency_list& g, + const_vertex_edge_t> uv) + -> const_vertex_iterator_t> { return uv->target_vertex(g); } -template -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_t> uv) - -> vertex_id_t> { +template +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_t> uv) + -> vertex_id_t> { return uv->target_vertex_id(g); } # endif @@ -893,24 +893,24 @@ constexpr auto vertex_id(const undirected_adjacency_list& // API graph functions // -template class VContainer, typename Alloc> -bool contains_vertex(undirected_adjacency_list& g, - vertex_id_t> uid) { +bool contains_vertex(undirected_adjacency_list& g, + vertex_id_t> uid) { return uid >= 0 && uid < g.vertices().size(); } -template class VContainer, typename Alloc> -void clear(undirected_adjacency_list& g) { +void clear(undirected_adjacency_list& g) { g.clear(); } diff --git a/include/graph/container/detail/undirected_adjacency_list_impl.hpp b/include/graph/container/detail/undirected_adjacency_list_impl.hpp index 0055136..12c2ae0 100644 --- a/include/graph/container/detail/undirected_adjacency_list_impl.hpp +++ b/include/graph/container/detail/undirected_adjacency_list_impl.hpp @@ -14,85 +14,85 @@ namespace graph::container { /// // ual_vertex_edge_list::const_iterator -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator::reference -ual_vertex_edge_list::const_iterator::operator*() const { +typename ual_vertex_edge_list::const_iterator::reference +ual_vertex_edge_list::const_iterator::operator*() const { return *edge_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator::pointer -ual_vertex_edge_list::const_iterator::operator->() const { +typename ual_vertex_edge_list::const_iterator::pointer +ual_vertex_edge_list::const_iterator::operator->() const { return edge_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator& -ual_vertex_edge_list::const_iterator::operator++() { +typename ual_vertex_edge_list::const_iterator& +ual_vertex_edge_list::const_iterator::operator++() { advance(); return *this; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::const_iterator::operator++(int) { +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::const_iterator::operator++(int) { const_iterator tmp(*this); ++*this; return tmp; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator& -ual_vertex_edge_list::const_iterator::operator--() { +typename ual_vertex_edge_list::const_iterator& +ual_vertex_edge_list::const_iterator::operator--() { retreat(); return *this; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::const_iterator::operator--(int) { +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::const_iterator::operator--(int) { const_iterator tmp(*this); --*this; return tmp; } -template class VContainer, typename Alloc> -void ual_vertex_edge_list::const_iterator::advance() { +void ual_vertex_edge_list::const_iterator::advance() { edge_type* start_edge = edge_; // Remember where we started for cycle detection vertex_edge_list_inward_link_type& inward_link = *edge_; // in.vertex_id_ == this->vertex_id_; @@ -110,13 +110,13 @@ void ual_vertex_edge_list::const_iterator::a } } -template class VContainer, typename Alloc> -void ual_vertex_edge_list::const_iterator::retreat() { +void ual_vertex_edge_list::const_iterator::retreat() { if (edge_) { vertex_edge_list_inward_link_type& inward_link = *edge_; // in.vertex_id_ == this->vertex_id_; vertex_edge_list_outward_link_type& outward_link = *edge_; @@ -132,174 +132,174 @@ void ual_vertex_edge_list::const_iterator::r } } -template class VContainer, typename Alloc> -bool ual_vertex_edge_list::const_iterator::operator==( +bool ual_vertex_edge_list::const_iterator::operator==( const const_iterator& rhs) const noexcept { return edge_ == rhs.edge_; } -template class VContainer, typename Alloc> -bool ual_vertex_edge_list::const_iterator::operator!=( +bool ual_vertex_edge_list::const_iterator::operator!=( const const_iterator& rhs) const noexcept { return !(*this == rhs); } // ual_vertex_edge_list::iterator -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::reference -ual_vertex_edge_list::iterator::operator*() const { +typename ual_vertex_edge_list::reference +ual_vertex_edge_list::iterator::operator*() const { return *this->edge_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::pointer -ual_vertex_edge_list::iterator::operator->() const { +typename ual_vertex_edge_list::pointer +ual_vertex_edge_list::iterator::operator->() const { return this->edge_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator& -ual_vertex_edge_list::iterator::operator++() { +typename ual_vertex_edge_list::iterator& +ual_vertex_edge_list::iterator::operator++() { this->advance(); return *this; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator -ual_vertex_edge_list::iterator::operator++(int) { +typename ual_vertex_edge_list::iterator +ual_vertex_edge_list::iterator::operator++(int) { iterator tmp(*this); ++*this; return tmp; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator& -ual_vertex_edge_list::iterator::operator--() { +typename ual_vertex_edge_list::iterator& +ual_vertex_edge_list::iterator::operator--() { this->retreat(); return *this; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator -ual_vertex_edge_list::iterator::operator--(int) { +typename ual_vertex_edge_list::iterator +ual_vertex_edge_list::iterator::operator--(int) { iterator tmp(*this); --*this; return tmp; } // ual_vertex_edge_list -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::size_type -ual_vertex_edge_list::size() const noexcept { +typename ual_vertex_edge_list::size_type +ual_vertex_edge_list::size() const noexcept { return size_; } -template class VContainer, typename Alloc> -bool ual_vertex_edge_list::empty() const noexcept { +bool ual_vertex_edge_list::empty() const noexcept { return size_ == 0; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::edge_type& -ual_vertex_edge_list::front() { +typename ual_vertex_edge_list::edge_type& +ual_vertex_edge_list::front() { return *head_; } -template class VContainer, typename Alloc> -const typename ual_vertex_edge_list::edge_type& -ual_vertex_edge_list::front() const { +const typename ual_vertex_edge_list::edge_type& +ual_vertex_edge_list::front() const { return *head_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::edge_type& -ual_vertex_edge_list::back() { +typename ual_vertex_edge_list::edge_type& +ual_vertex_edge_list::back() { return *tail_; } -template class VContainer, typename Alloc> -const typename ual_vertex_edge_list::edge_type& -ual_vertex_edge_list::back() const { +const typename ual_vertex_edge_list::edge_type& +ual_vertex_edge_list::back() const { return *tail_; } -template class VContainer, typename Alloc> template -void ual_vertex_edge_list::link_front( - edge_type& uv, ual_vertex_edge_list_link& uv_link) { - using link_t = ual_vertex_edge_list_link; +void ual_vertex_edge_list::link_front( + edge_type& uv, ual_vertex_edge_list_link& uv_link) { + using link_t = ual_vertex_edge_list_link; if (head_) { link_t& head_link = *static_cast(head_); uv_link.next_ = head_; @@ -311,15 +311,15 @@ void ual_vertex_edge_list::link_front( ++size_; } -template class VContainer, typename Alloc> template -void ual_vertex_edge_list::link_back( - edge_type& uv, ual_vertex_edge_list_link& uv_link) { +void ual_vertex_edge_list::link_back( + edge_type& uv, ual_vertex_edge_list_link& uv_link) { if (tail_) { vertex_edge_list_inward_link_type& tail_inward_link = static_cast(*tail_); vertex_edge_list_outward_link_type& tail_outward_link = static_cast(*tail_); @@ -339,15 +339,15 @@ void ual_vertex_edge_list::link_back( ++size_; } -template class VContainer, typename Alloc> template -void ual_vertex_edge_list::unlink( - edge_type& uv, ual_vertex_edge_list_link& uv_link) { +void ual_vertex_edge_list::unlink( + edge_type& uv, ual_vertex_edge_list_link& uv_link) { if (uv_link.prev_) { vertex_edge_list_inward_link_type& prev_inward_link = @@ -392,89 +392,89 @@ void ual_vertex_edge_list::unlink( // don't hold during partial unlink. The invariant is restored after both unlinks. } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator -ual_vertex_edge_list::begin(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex_edge_list::iterator +ual_vertex_edge_list::begin(graph_type& g, vertex_id_type uid) noexcept { return iterator(g, uid, head_); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::begin(const graph_type& g, +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::begin(const graph_type& g, vertex_id_type uid) const noexcept { return const_iterator(g, uid, head_); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::cbegin(const graph_type& g, +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::cbegin(const graph_type& g, vertex_id_type uid) const noexcept { return const_iterator(g, uid, head_); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator -ual_vertex_edge_list::end(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex_edge_list::iterator +ual_vertex_edge_list::end(graph_type& g, vertex_id_type uid) noexcept { return iterator(g, uid, nullptr); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::end(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::end(const graph_type& g, vertex_id_type uid) const noexcept { return const_iterator(g, uid, nullptr); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::cend(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::cend(const graph_type& g, vertex_id_type uid) const noexcept { return const_iterator(g, uid, nullptr); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::edge_range -ual_vertex_edge_list::edges(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex_edge_list::edge_range +ual_vertex_edge_list::edges(graph_type& g, vertex_id_type uid) noexcept { return {iterator(g, uid, head_), iterator(g, uid, nullptr), size_}; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_edge_range -ual_vertex_edge_list::edges(const graph_type& g, +typename ual_vertex_edge_list::const_edge_range +ual_vertex_edge_list::edges(const graph_type& g, vertex_id_type uid) const noexcept { return {const_iterator(g, uid, head_), const_iterator(g, uid, nullptr), size_}; } @@ -482,24 +482,24 @@ ual_vertex_edge_list::edges(const graph_type ///------------------------------------------------------------------------------------- /// ual_edge /// -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, vertex_id_type uid, vertex_id_type vid) noexcept +ual_edge::ual_edge(graph_type& g, vertex_id_type uid, vertex_id_type vid) noexcept : base_value_type(), vertex_edge_list_inward_link_type(uid), vertex_edge_list_outward_link_type(vid) { link_back(g.vertices()[uid], g.vertices()[vid]); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, +ual_edge::ual_edge(graph_type& g, vertex_id_type uid, vertex_id_type vid, const edge_value_type& val) noexcept @@ -507,13 +507,13 @@ ual_edge::ual_edge(graph_type& g, link_back(g.vertices()[uid], g.vertices()[vid]); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, +ual_edge::ual_edge(graph_type& g, vertex_id_type uid, vertex_id_type vid, edge_value_type&& val) noexcept @@ -521,26 +521,26 @@ ual_edge::ual_edge(graph_type& g, link_back(g.vertices()[uid], g.vertices()[vid]); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, vertex_iterator ui, vertex_iterator vi) noexcept +ual_edge::ual_edge(graph_type& g, vertex_iterator ui, vertex_iterator vi) noexcept : base_value_type() , vertex_edge_list_inward_link_type(static_cast(ui - g.vertices().begin())) , vertex_edge_list_outward_link_type(static_cast(vi - g.vertices().begin())) { link_back(*ui, *vi); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, +ual_edge::ual_edge(graph_type& g, vertex_iterator ui, vertex_iterator vi, const edge_value_type& val) noexcept @@ -550,13 +550,13 @@ ual_edge::ual_edge(graph_type& g, link_back(*ui, *vi); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, +ual_edge::ual_edge(graph_type& g, vertex_iterator ui, vertex_iterator vi, edge_value_type&& val) noexcept @@ -566,13 +566,13 @@ ual_edge::ual_edge(graph_type& g, link_back(*ui, *vi); } -template class VContainer, typename Alloc> -ual_edge::~ual_edge() noexcept { +ual_edge::~ual_edge() noexcept { vertex_edge_list_outward_link_type& outward_link = *static_cast(this); assert(outward_link.prev() == nullptr && outward_link.next() == nullptr); // has edge been unlinked? @@ -580,184 +580,184 @@ ual_edge::~ual_edge() noexcept { assert(inward_link.prev() == nullptr && inward_link.next() == nullptr); // has edge been unlinked? } -template class VContainer, typename Alloc> -void ual_edge::link_front(vertex_type& u, vertex_type& v) noexcept { +void ual_edge::link_front(vertex_type& u, vertex_type& v) noexcept { u.edges_.link_front(*this, *static_cast(this)); v.edges_.link_front(*this, *static_cast(this)); } -template class VContainer, typename Alloc> -void ual_edge::link_back(vertex_type& u, vertex_type& v) noexcept { +void ual_edge::link_back(vertex_type& u, vertex_type& v) noexcept { u.edges_.link_back(*this, *static_cast(this)); v.edges_.link_back(*this, *static_cast(this)); } -template class VContainer, typename Alloc> -void ual_edge::unlink(vertex_type& u, vertex_type& v) noexcept { +void ual_edge::unlink(vertex_type& u, vertex_type& v) noexcept { u.edges_.unlink(*this, *static_cast(this)); v.edges_.unlink(*this, *static_cast(this)); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_iterator -ual_edge::source(graph_type& g) noexcept { +typename ual_edge::vertex_iterator +ual_edge::source(graph_type& g) noexcept { return g.vertices().begin() + list_owner_id(); } -template class VContainer, typename Alloc> -typename ual_edge::const_vertex_iterator -ual_edge::source(const graph_type& g) const noexcept { +typename ual_edge::const_vertex_iterator +ual_edge::source(const graph_type& g) const noexcept { return static_cast(this)->vertex(g); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_id_type -ual_edge::list_owner_id() const noexcept { +typename ual_edge::vertex_id_type +ual_edge::list_owner_id() const noexcept { return static_cast(this)->vertex_id(); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_iterator -ual_edge::target(graph_type& g) noexcept { +typename ual_edge::vertex_iterator +ual_edge::target(graph_type& g) noexcept { return g.vertices().begin() + list_target_id(); } -template class VContainer, typename Alloc> -typename ual_edge::const_vertex_iterator -ual_edge::target(const graph_type& g) const noexcept { +typename ual_edge::const_vertex_iterator +ual_edge::target(const graph_type& g) const noexcept { return static_cast(this)->vertex(g); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_id_type -ual_edge::list_target_id() const noexcept { +typename ual_edge::vertex_id_type +ual_edge::list_target_id() const noexcept { return static_cast(this)->vertex_id(); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_iterator -ual_edge::other_vertex(graph_type& g, const_vertex_iterator other) noexcept { +typename ual_edge::vertex_iterator +ual_edge::other_vertex(graph_type& g, const_vertex_iterator other) noexcept { return other != source(g) ? source(g) : target(g); } -template class VContainer, typename Alloc> -typename ual_edge::const_vertex_iterator -ual_edge::other_vertex(const graph_type& g, +typename ual_edge::const_vertex_iterator +ual_edge::other_vertex(const graph_type& g, const_vertex_iterator other) const noexcept { return other != source(g) ? source(g) : target(g); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_iterator -ual_edge::other_vertex(graph_type& g, vertex_id_type other_id) noexcept { +typename ual_edge::vertex_iterator +ual_edge::other_vertex(graph_type& g, vertex_id_type other_id) noexcept { return other_id != list_owner_id() ? source(g) : target(g); } -template class VContainer, typename Alloc> -typename ual_edge::const_vertex_iterator -ual_edge::other_vertex(const graph_type& g, +typename ual_edge::const_vertex_iterator +ual_edge::other_vertex(const graph_type& g, vertex_id_type other_id) const noexcept { return other_id != list_owner_id() ? source(g) : target(g); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_id_type -ual_edge::other_vertex_id(const graph_type& g, +typename ual_edge::vertex_id_type +ual_edge::other_vertex_id(const graph_type& g, const_vertex_iterator other) const noexcept { return other != source(g) ? list_owner_id() : list_target_id(); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_id_type -ual_edge::other_vertex_id([[maybe_unused]] const graph_type& g, +typename ual_edge::vertex_id_type +ual_edge::other_vertex_id([[maybe_unused]] const graph_type& g, vertex_id_type other_id) const noexcept { return other_id != list_owner_id() ? list_owner_id() : list_target_id(); } -template class VContainer, typename Alloc> -typename ual_edge::edge_id_type -ual_edge::edge_id(const graph_type& g) const noexcept { +typename ual_edge::edge_id_type +ual_edge::edge_id(const graph_type& g) const noexcept { return unordered_pair(list_owner_id(), list_target_id()); } @@ -765,201 +765,201 @@ ual_edge::edge_id(const graph_type& g) const ///------------------------------------------------------------------------------------- /// ual_vertex /// -template class VContainer, typename Alloc> -ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, +ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, [[maybe_unused]] vertex_index index) {} -template class VContainer, typename Alloc> -ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, +ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, [[maybe_unused]] vertex_index index, const vertex_value_type& val) : base_value_type(val) {} -template class VContainer, typename Alloc> -ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, +ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, [[maybe_unused]] vertex_index index, vertex_value_type&& val) noexcept : base_value_type(move(val)) {} -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::edges_begin(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex::vertex_edge_iterator +ual_vertex::edges_begin(graph_type& g, vertex_id_type uid) noexcept { return edges_.begin(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_iterator -ual_vertex::edges_begin(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex::const_vertex_edge_iterator +ual_vertex::edges_begin(const graph_type& g, vertex_id_type uid) const noexcept { return edges_.begin(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_iterator -ual_vertex::edges_cbegin(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex::const_vertex_edge_iterator +ual_vertex::edges_cbegin(const graph_type& g, vertex_id_type uid) const noexcept { return edges_.cbegin(g, uid); } // Removed: e_begin implementation - legacy method using const_cast, replaced by edges_begin -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::edges_end(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex::vertex_edge_iterator +ual_vertex::edges_end(graph_type& g, vertex_id_type uid) noexcept { return edges_.end(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_iterator -ual_vertex::edges_end(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex::const_vertex_edge_iterator +ual_vertex::edges_end(const graph_type& g, vertex_id_type uid) const noexcept { return edges_.end(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_iterator -ual_vertex::edges_cend(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex::const_vertex_edge_iterator +ual_vertex::edges_cend(const graph_type& g, vertex_id_type uid) const noexcept { return edges_.cend(g, uid); } // Removed: e_end implementation - legacy method using const_cast, replaced by edges_end -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_range -ual_vertex::edges(graph_type& g, vertex_id_type uid) { +typename ual_vertex::vertex_edge_range +ual_vertex::edges(graph_type& g, vertex_id_type uid) { return edges_.edges(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_range -ual_vertex::edges(const graph_type& g, vertex_id_type uid) const { +typename ual_vertex::const_vertex_edge_range +ual_vertex::edges(const graph_type& g, vertex_id_type uid) const { return edges_.edges(g, uid); } #if 0 -template class VContainer, typename Alloc> -typename ual_vertex::vertex_id_type -ual_vertex::vertex_id(const graph_type& g) const noexcept { +template class VContainer, typename Alloc> +typename ual_vertex::vertex_id_type +ual_vertex::vertex_id(const graph_type& g) const noexcept { return static_cast(this - g.vertices().data()); } #endif -template class VContainer, typename Alloc> -typename ual_vertex::edge_type& -ual_vertex::edge_front(graph_type& g) noexcept { +typename ual_vertex::edge_type& +ual_vertex::edge_front(graph_type& g) noexcept { return edges_.front(g, *this); } -template class VContainer, typename Alloc> -const typename ual_vertex::edge_type& -ual_vertex::edge_front(const graph_type& g) const noexcept { +const typename ual_vertex::edge_type& +ual_vertex::edge_front(const graph_type& g) const noexcept { return edges_.front(g, *this); } -template class VContainer, typename Alloc> -typename ual_vertex::edge_type& -ual_vertex::edge_back(graph_type& g) noexcept { +typename ual_vertex::edge_type& +ual_vertex::edge_back(graph_type& g) noexcept { return edges_.back(); } -template class VContainer, typename Alloc> -const typename ual_vertex::edge_type& -ual_vertex::edge_back(const graph_type& g) const noexcept { +const typename ual_vertex::edge_type& +ual_vertex::edge_back(const graph_type& g) const noexcept { return edges_.back(); } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_size_type -ual_vertex::num_edges() const { +typename ual_vertex::vertex_edge_size_type +ual_vertex::num_edges() const { return edges_.size(); } -template class VContainer, typename Alloc> -void ual_vertex::erase_edge(graph_type& g, edge_type* uv) { +void ual_vertex::erase_edge(graph_type& g, edge_type* uv) { vertex_type& u = g.vertices()[uv->list_owner_id()]; vertex_type& v = g.vertices()[uv->list_target_id()]; uv->unlink(u, v); @@ -969,40 +969,40 @@ void ual_vertex::erase_edge(graph_type& g, e --g.edges_size_; } -template class VContainer, typename Alloc> -void ual_vertex::clear_edges(graph_type& g) { +void ual_vertex::clear_edges(graph_type& g) { while (!edges_.empty()) { erase_edge(g, &edges_.front()); } } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::erase_edge(graph_type& g, vertex_edge_iterator uvi) { +typename ual_vertex::vertex_edge_iterator +ual_vertex::erase_edge(graph_type& g, vertex_edge_iterator uvi) { edge_type* uv = &*uvi; ++uvi; erase_edge(g, uv); return uvi; } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::erase_edge(graph_type& g, +typename ual_vertex::vertex_edge_iterator +ual_vertex::erase_edge(graph_type& g, vertex_edge_iterator first, vertex_edge_iterator last) { while (first != last) @@ -1011,27 +1011,27 @@ ual_vertex::erase_edge(graph_type& } #if 0 -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v) { +template class VContainer, typename Alloc> +typename ual_vertex::vertex_edge_iterator +ual_vertex::create_edge(graph_type& g, vertex_type& v) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v); ++g.edges_size_; return vertex_edge_iterator(g, *this, uv); } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v, edge_value_type&& val) { +template class VContainer, typename Alloc> +typename ual_vertex::vertex_edge_iterator +ual_vertex::create_edge(graph_type& g, vertex_type& v, edge_value_type&& val) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v, move(val)); ++g.edges_size_; return vertex_edge_iterator(g, *this, uv); } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v, const edge_value_type& val) { +template class VContainer, typename Alloc> +typename ual_vertex::vertex_edge_iterator +ual_vertex::create_edge(graph_type& g, vertex_type& v, const edge_value_type& val) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v, val); ++g.edges_size_; @@ -1039,35 +1039,35 @@ ual_vertex::create_edge(graph_type& g, verte } #endif -template class VContainer, typename Alloc> -typename ual_vertex::neighbor_size_type -ual_vertex::neighbors_size(const graph_type& g) const { +typename ual_vertex::neighbor_size_type +ual_vertex::neighbors_size(const graph_type& g) const { return size(edges(g)); } -template class VContainer, typename Alloc> -typename ual_vertex::neighbor_range -ual_vertex::neighbors(graph_type& g, vertex_id_type uid) { +typename ual_vertex::neighbor_range +ual_vertex::neighbors(graph_type& g, vertex_id_type uid) { return {neighbor_iterator(edges_.begin(g, uid)), neighbor_iterator(edges_.end(g, uid)), edges_.size()}; } -template class VContainer, typename Alloc> -typename ual_vertex::const_neighbor_range -ual_vertex::neighbors(const graph_type& g, vertex_id_type uid) const { +typename ual_vertex::const_neighbor_range +ual_vertex::neighbors(const graph_type& g, vertex_id_type uid) const { return {const_neighbor_iterator(edges_.begin(g, uid)), const_neighbor_iterator(edges_.end(g, uid)), edges_.size()}; } @@ -1077,13 +1077,13 @@ ual_vertex::neighbors(const graph_type& g, v /// // Copy constructor - copies vertices and edges -template class VContainer, typename Alloc> -base_undirected_adjacency_list::base_undirected_adjacency_list( +base_undirected_adjacency_list::base_undirected_adjacency_list( const base_undirected_adjacency_list& other) { // Copy base class members edge_alloc_ = other.edge_alloc_; @@ -1123,8 +1123,8 @@ base_undirected_adjacency_list::base_undirec } // Range constructor -template class VContainer, @@ -1133,7 +1133,7 @@ template requires ranges::forward_range && ranges::input_range && std::regular_invocable> && std::regular_invocable> -base_undirected_adjacency_list::base_undirected_adjacency_list( +base_undirected_adjacency_list::base_undirected_adjacency_list( const ERng& erng, const VRng& vrng, const EProj& eproj, const VProj& vproj, const allocator_type& alloc) : vertices_(alloc), edge_alloc_(alloc) { // Handle empty case - no vertices or edges to create @@ -1181,13 +1181,13 @@ base_undirected_adjacency_list::base_undirec } // Initializer list constructor with edge values -template class VContainer, typename Alloc> -base_undirected_adjacency_list::base_undirected_adjacency_list( +base_undirected_adjacency_list::base_undirected_adjacency_list( const initializer_list>& ilist, const allocator_type& alloc) : vertices_(alloc), edge_alloc_(alloc) { @@ -1210,13 +1210,13 @@ base_undirected_adjacency_list::base_undirec } // Initializer list constructor without edge values -template class VContainer, typename Alloc> -base_undirected_adjacency_list::base_undirected_adjacency_list( +base_undirected_adjacency_list::base_undirected_adjacency_list( const initializer_list>& ilist, const allocator_type& alloc) : vertices_(alloc), edge_alloc_(alloc) { // Evaluate max vertex id needed @@ -1239,27 +1239,27 @@ base_undirected_adjacency_list::base_undirec // Destructor -template class VContainer, typename Alloc> -base_undirected_adjacency_list::~base_undirected_adjacency_list() { +base_undirected_adjacency_list::~base_undirected_adjacency_list() { // Downcast to graph_type to access clear() method auto& g = static_cast(*this); g.clear(); // assure edges are deleted using edge_alloc_ } // Copy assignment operator -template class VContainer, typename Alloc> -base_undirected_adjacency_list& -base_undirected_adjacency_list::operator=( +base_undirected_adjacency_list& +base_undirected_adjacency_list::operator=( const base_undirected_adjacency_list& other) { if (this != &other) { // Use copy-and-swap idiom @@ -1276,13 +1276,13 @@ base_undirected_adjacency_list::operator=( } // Helper method for validation -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::throw_unordered_edges() const { +void base_undirected_adjacency_list::throw_unordered_edges() const { assert(false); // container must be sorted by edge_id.first throw std::invalid_argument("edges not ordered"); } @@ -1291,127 +1291,127 @@ void base_undirected_adjacency_list::throw_u // Accessor methods //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::edge_allocator_type -base_undirected_adjacency_list::edge_allocator() const noexcept { +constexpr typename base_undirected_adjacency_list::edge_allocator_type +base_undirected_adjacency_list::edge_allocator() const noexcept { return this->edge_alloc_; } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::vertex_set& -base_undirected_adjacency_list::vertices() { +constexpr typename base_undirected_adjacency_list::vertex_set& +base_undirected_adjacency_list::vertices() { return this->vertices_; } -template class VContainer, typename Alloc> -constexpr const typename base_undirected_adjacency_list::vertex_set& -base_undirected_adjacency_list::vertices() const { +constexpr const typename base_undirected_adjacency_list::vertex_set& +base_undirected_adjacency_list::vertices() const { return this->vertices_; } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::vertex_iterator -base_undirected_adjacency_list::begin() { +constexpr typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::begin() { return this->vertices_.begin(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::begin() const { +constexpr typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::begin() const { return this->vertices_.begin(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::cbegin() const { +constexpr typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::cbegin() const { return this->vertices_.cbegin(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::vertex_iterator -base_undirected_adjacency_list::end() { +constexpr typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::end() { return this->vertices_.end(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::end() const { +constexpr typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::end() const { return this->vertices_.end(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::cend() const { +constexpr typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::cend() const { return this->vertices_.cend(); } -template class VContainer, typename Alloc> -typename base_undirected_adjacency_list::vertex_iterator -base_undirected_adjacency_list::try_find_vertex(vertex_id_type id) { +typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::try_find_vertex(vertex_id_type id) { if (id < this->vertices_.size()) return this->vertices_.begin() + id; else return this->vertices_.end(); } -template class VContainer, typename Alloc> -typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::try_find_vertex(vertex_id_type id) const { +typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::try_find_vertex(vertex_id_type id) const { if (id < this->vertices_.size()) return this->vertices_.begin() + id; else @@ -1427,14 +1427,14 @@ base_undirected_adjacency_list::try_find_ver // Edge removal methods //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -typename base_undirected_adjacency_list::edge_iterator -base_undirected_adjacency_list::erase_edge(edge_iterator pos) { +typename base_undirected_adjacency_list::edge_iterator +base_undirected_adjacency_list::erase_edge(edge_iterator pos) { edge_type* uv = &*pos; ++pos; uv->~edge_type(); // unlinks from vertices @@ -1447,13 +1447,13 @@ base_undirected_adjacency_list::erase_edge(e // Graph modification methods //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::clear() { +void base_undirected_adjacency_list::clear() { // make sure edges are deallocated from this->edge_alloc_ // Use downcast to call derived class clear_edges method auto& derived = static_cast(*this); @@ -1462,13 +1462,13 @@ void base_undirected_adjacency_list::clear() this->vertices_.clear(); // now we can clear the vertices } -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::swap(base_undirected_adjacency_list& other) { +void base_undirected_adjacency_list::swap(base_undirected_adjacency_list& other) { using std::swap; this->vertices_.swap(other.vertices_); swap(this->edges_size_, other.edges_size_); @@ -1481,33 +1481,33 @@ void base_undirected_adjacency_list::swap(ba // Utility methods //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::reserve_vertices(vertex_size_type n) { +void base_undirected_adjacency_list::reserve_vertices(vertex_size_type n) { this->vertices_.reserve(n); } -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::resize_vertices(vertex_size_type n) { +void base_undirected_adjacency_list::resize_vertices(vertex_size_type n) { this->vertices_.resize(n); } -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::resize_vertices(vertex_size_type n, +void base_undirected_adjacency_list::resize_vertices(vertex_size_type n, const vertex_value_type& val) { this->vertices_.resize(n, val); } @@ -1517,47 +1517,47 @@ void base_undirected_adjacency_list::resize_ /// undirected_adjacency_list /// -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list(const allocator_type& alloc) +undirected_adjacency_list::undirected_adjacency_list(const allocator_type& alloc) : base_type(alloc) {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list(const graph_value_type& val, +undirected_adjacency_list::undirected_adjacency_list(const graph_value_type& val, const allocator_type& alloc) : base_type(alloc), graph_value_(val) {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list(graph_value_type&& val, +undirected_adjacency_list::undirected_adjacency_list(graph_value_type&& val, const allocator_type& alloc) : base_type(alloc), graph_value_(std::forward(val)) {} // clang-format off -template class VContainer, typename Alloc> +template class VContainer, typename Alloc> template requires ranges::forward_range && ranges::input_range && std::regular_invocable> && std::regular_invocable> && (!std::is_void_v) -undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, +undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, const VRng& vrng, const EProj& eproj, const VProj& vproj, @@ -1569,12 +1569,12 @@ undirected_adjacency_list::undirected_adjace {} // clang-format off -template class VContainer, typename Alloc> +template class VContainer, typename Alloc> template requires ranges::forward_range && std::regular_invocable> && (!std::is_void_v) -undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, +undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, const EProj& eproj, const GV_& gv, const Alloc& alloc) @@ -1585,14 +1585,14 @@ undirected_adjacency_list::undirected_adjace // Overload for GV=void: edge+vertex range constructor without graph value // clang-format off -template class VContainer, typename Alloc> +template class VContainer, typename Alloc> template requires ranges::forward_range && ranges::input_range && std::regular_invocable> && std::regular_invocable> && std::is_void_v -undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, +undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, const VRng& vrng, const EProj& eproj, const VProj& vproj, @@ -1644,12 +1644,12 @@ undirected_adjacency_list::undirected_adjace // Overload for GV=void: edge-only range constructor without graph value // clang-format off -template class VContainer, typename Alloc> +template class VContainer, typename Alloc> template requires ranges::forward_range && std::regular_invocable> && std::is_void_v -undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, +undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, const EProj& eproj, const Alloc& alloc) : undirected_adjacency_list(erng, vector(), eproj, [](auto) @@ -1657,46 +1657,46 @@ undirected_adjacency_list::undirected_adjace // clang-format on {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +undirected_adjacency_list::undirected_adjacency_list( const initializer_list>& ilist, const Alloc& alloc) : base_type(ilist, alloc) {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +undirected_adjacency_list::undirected_adjacency_list( const initializer_list>& ilist, const Alloc& alloc) : base_type(ilist, alloc) {} // Copy constructor - deep copies all vertices and edges -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +undirected_adjacency_list::undirected_adjacency_list( const undirected_adjacency_list& other) : base_type(other), graph_value_(other.graph_value_) {} // Copy assignment operator - uses copy-and-swap idiom -template class VContainer, typename Alloc> -undirected_adjacency_list& -undirected_adjacency_list::operator=(const undirected_adjacency_list& other) { +undirected_adjacency_list& +undirected_adjacency_list::operator=(const undirected_adjacency_list& other) { if (this != &other) { undirected_adjacency_list tmp(other); swap(tmp); @@ -1704,25 +1704,25 @@ undirected_adjacency_list::operator=(const u return *this; } -template class VContainer, typename Alloc> -undirected_adjacency_list::~undirected_adjacency_list() = default; +undirected_adjacency_list::~undirected_adjacency_list() = default; //------------------------------------------------------------------------------------- // Vertex/edge modification methods (non-accessor) //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -void undirected_adjacency_list::swap(undirected_adjacency_list& rhs) { +void undirected_adjacency_list::swap(undirected_adjacency_list& rhs) { using std::swap; swap(graph_value_, rhs.graph_value_); base_type::swap(rhs); // Call base class swap for vertices, edges_size, allocators @@ -1731,145 +1731,145 @@ void undirected_adjacency_list::swap(undirec ///------------------------------------------------------------------------------------- /// ual_const_neighbor_iterator /// -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator::ual_const_neighbor_iterator( +constexpr ual_const_neighbor_iterator::ual_const_neighbor_iterator( vertex_edge_iterator const& uv) : uv_(uv) {} -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::graph_type& -ual_const_neighbor_iterator::graph() noexcept { +constexpr typename ual_const_neighbor_iterator::graph_type& +ual_const_neighbor_iterator::graph() noexcept { return uv_.graph(); } -template class VContainer, typename Alloc> -constexpr const typename ual_const_neighbor_iterator::graph_type& -ual_const_neighbor_iterator::graph() const noexcept { +constexpr const typename ual_const_neighbor_iterator::graph_type& +ual_const_neighbor_iterator::graph() const noexcept { return uv_.graph(); } -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::const_vertex_iterator -ual_const_neighbor_iterator::other_vertex() const { +constexpr typename ual_const_neighbor_iterator::const_vertex_iterator +ual_const_neighbor_iterator::other_vertex() const { return uv_->other_vertex(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::vertex_id_type -ual_const_neighbor_iterator::other_vertex_id() const { +constexpr typename ual_const_neighbor_iterator::vertex_id_type +ual_const_neighbor_iterator::other_vertex_id() const { return uv_->other_vertex_id(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::reference -ual_const_neighbor_iterator::operator*() const noexcept { +constexpr typename ual_const_neighbor_iterator::reference +ual_const_neighbor_iterator::operator*() const noexcept { return *uv_->other_vertex(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::pointer -ual_const_neighbor_iterator::operator->() const noexcept { +constexpr typename ual_const_neighbor_iterator::pointer +ual_const_neighbor_iterator::operator->() const noexcept { return &**this; } -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator& -ual_const_neighbor_iterator::operator++() noexcept { +constexpr ual_const_neighbor_iterator& +ual_const_neighbor_iterator::operator++() noexcept { ++uv_; return *this; } -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator -ual_const_neighbor_iterator::operator++(int) noexcept { +constexpr ual_const_neighbor_iterator +ual_const_neighbor_iterator::operator++(int) noexcept { ual_const_neighbor_iterator tmp(*this); ++*this; return tmp; } -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator& -ual_const_neighbor_iterator::operator--() noexcept { +constexpr ual_const_neighbor_iterator& +ual_const_neighbor_iterator::operator--() noexcept { --uv_; return *this; } -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator -ual_const_neighbor_iterator::operator--(int) noexcept { +constexpr ual_const_neighbor_iterator +ual_const_neighbor_iterator::operator--(int) noexcept { ual_const_neighbor_iterator tmp(*this); --*this; return tmp; } -template class VContainer, typename Alloc> -constexpr bool ual_const_neighbor_iterator::operator==( +constexpr bool ual_const_neighbor_iterator::operator==( const ual_const_neighbor_iterator& rhs) const noexcept { return uv_ == rhs.uv_; } -template class VContainer, typename Alloc> -constexpr bool ual_const_neighbor_iterator::operator!=( +constexpr bool ual_const_neighbor_iterator::operator!=( const ual_const_neighbor_iterator& rhs) const noexcept { return !operator==(rhs); } @@ -1878,160 +1878,160 @@ constexpr bool ual_const_neighbor_iterator:: ///------------------------------------------------------------------------------------- /// ual_neighbor_iterator /// -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator::ual_neighbor_iterator( +constexpr ual_neighbor_iterator::ual_neighbor_iterator( vertex_edge_iterator const& uv) : base_t(uv) {} -template class VContainer, typename Alloc> -constexpr typename ual_neighbor_iterator::vertex_iterator -ual_neighbor_iterator::other_vertex() { +constexpr typename ual_neighbor_iterator::vertex_iterator +ual_neighbor_iterator::other_vertex() { return uv_->other_vertex(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_neighbor_iterator::const_reference -ual_neighbor_iterator::operator*() const { +constexpr typename ual_neighbor_iterator::const_reference +ual_neighbor_iterator::operator*() const { return *uv_->other_vertex(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_neighbor_iterator::const_pointer -ual_neighbor_iterator::operator->() const { +constexpr typename ual_neighbor_iterator::const_pointer +ual_neighbor_iterator::operator->() const { return &**this; } -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator& -ual_neighbor_iterator::operator++() { +constexpr ual_neighbor_iterator& +ual_neighbor_iterator::operator++() { ++uv_; return *this; } -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator -ual_neighbor_iterator::operator++(int) { +constexpr ual_neighbor_iterator +ual_neighbor_iterator::operator++(int) { ual_neighbor_iterator tmp(*this); ++*this; return tmp; } -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator& -ual_neighbor_iterator::operator--() noexcept { +constexpr ual_neighbor_iterator& +ual_neighbor_iterator::operator--() noexcept { --uv_; return *this; } -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator -ual_neighbor_iterator::operator--(int) noexcept { +constexpr ual_neighbor_iterator +ual_neighbor_iterator::operator--(int) noexcept { ual_neighbor_iterator tmp(*this); --*this; return tmp; } -template class VContainer, typename Alloc> constexpr bool -ual_neighbor_iterator::operator==(const ual_neighbor_iterator& rhs) const noexcept { +ual_neighbor_iterator::operator==(const ual_neighbor_iterator& rhs) const noexcept { return base_t::operator==(rhs); } -template class VContainer, typename Alloc> constexpr bool -ual_neighbor_iterator::operator!=(const ual_neighbor_iterator& rhs) const noexcept { +ual_neighbor_iterator::operator!=(const ual_neighbor_iterator& rhs) const noexcept { return base_t::operator!=(rhs); } ///------------------------------------------------------------------------------------- -/// Implementations for undirected_adjacency_list specialization +/// Implementations for undirected_adjacency_list specialization /// (when GV=void, no graph_value_ member) /// // Default allocator constructor -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list(const allocator_type& alloc) +template class VContainer, typename Alloc> +undirected_adjacency_list::undirected_adjacency_list(const allocator_type& alloc) : base_type(alloc) {} // Copy constructor -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +template class VContainer, typename Alloc> +undirected_adjacency_list::undirected_adjacency_list( const undirected_adjacency_list& other) : base_type(other) {} // Initializer list constructors -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +template class VContainer, typename Alloc> +undirected_adjacency_list::undirected_adjacency_list( const initializer_list>& ilist, const Alloc& alloc) : base_type(ilist, alloc) {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +template class VContainer, typename Alloc> +undirected_adjacency_list::undirected_adjacency_list( const initializer_list>& ilist, const Alloc& alloc) : base_type(ilist, alloc) {} // Destructor -template class VContainer, typename Alloc> -undirected_adjacency_list::~undirected_adjacency_list() = default; +template class VContainer, typename Alloc> +undirected_adjacency_list::~undirected_adjacency_list() = default; //------------------------------------------------------------------------------------- // Vertex/edge modification methods (non-accessor) - GV=void specialization //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -void undirected_adjacency_list::swap(undirected_adjacency_list& rhs) { +template class VContainer, typename Alloc> +void undirected_adjacency_list::swap(undirected_adjacency_list& rhs) { base_type::swap(rhs); // Call base class swap (no graph_value_ to swap) } -template class VContainer, typename Alloc> -undirected_adjacency_list& -undirected_adjacency_list::operator=(const undirected_adjacency_list& other) { +template class VContainer, typename Alloc> +undirected_adjacency_list& +undirected_adjacency_list::operator=(const undirected_adjacency_list& other) { if (this != &other) { undirected_adjacency_list tmp(other); swap(tmp); diff --git a/include/graph/container/traits/moem_graph_traits.hpp b/include/graph/container/traits/mom_graph_traits.hpp similarity index 90% rename from include/graph/container/traits/moem_graph_traits.hpp rename to include/graph/container/traits/mom_graph_traits.hpp index f6aa338..26b24ca 100644 --- a/include/graph/container/traits/moem_graph_traits.hpp +++ b/include/graph/container/traits/mom_graph_traits.hpp @@ -14,7 +14,7 @@ class dynamic_vertex; template class dynamic_graph; -// moem_graph_traits +// mom_graph_traits // Vertices: std::map (ordered map keyed by vertex ID) // Edges: std::map (ordered map keyed by target vertex ID) // @@ -34,18 +34,18 @@ class dynamic_graph; // - Efficient vertex insertion/deletion without affecting other IDs // - Memory overhead: ~24-32 bytes per vertex + ~8-16 bytes per edge // -// Parameter semantics mirror voem_graph_traits. +// Parameter semantics mirror vom_graph_traits. template -struct moem_graph_traits { +struct mom_graph_traits { using edge_value_type = EV; using vertex_value_type = VV; using graph_value_type = GV; using vertex_id_type = VId; static constexpr bool sourced = Sourced; - using edge_type = dynamic_edge; - using vertex_type = dynamic_vertex; - using graph_type = dynamic_graph; + using edge_type = dynamic_edge; + using vertex_type = dynamic_vertex; + using graph_type = dynamic_graph; using vertices_type = std::map; // Map keyed by vertex ID using edges_type = std::map; // Map keyed by target vertex ID diff --git a/include/graph/container/traits/voem_graph_traits.hpp b/include/graph/container/traits/vom_graph_traits.hpp similarity index 92% rename from include/graph/container/traits/voem_graph_traits.hpp rename to include/graph/container/traits/vom_graph_traits.hpp index 28e3fc3..a362be4 100644 --- a/include/graph/container/traits/voem_graph_traits.hpp +++ b/include/graph/container/traits/vom_graph_traits.hpp @@ -15,7 +15,7 @@ class dynamic_vertex; template class dynamic_graph; -// voem_graph_traits +// vom_graph_traits // Vertices: std::vector (contiguous; random access by vertex ID) // Edges: std::map (ordered map keyed by target vertex ID) // @@ -34,16 +34,16 @@ class dynamic_graph; // // Parameter semantics mirror vofl_graph_traits. template -struct voem_graph_traits { +struct vom_graph_traits { using edge_value_type = EV; using vertex_value_type = VV; using graph_value_type = GV; using vertex_id_type = VId; static constexpr bool sourced = Sourced; - using edge_type = dynamic_edge; - using vertex_type = dynamic_vertex; - using graph_type = dynamic_graph; + using edge_type = dynamic_edge; + using vertex_type = dynamic_vertex; + using graph_type = dynamic_graph; using vertices_type = std::vector; using edges_type = std::map; // Map keyed by target vertex ID diff --git a/include/graph/container/traits/voum_graph_traits.hpp b/include/graph/container/traits/voum_graph_traits.hpp new file mode 100644 index 0000000..7c24f3e --- /dev/null +++ b/include/graph/container/traits/voum_graph_traits.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +namespace graph::container { + +// Forward declarations +template +class dynamic_edge; + +template +class dynamic_vertex; + +template +class dynamic_graph; + +// voum_graph_traits +// Vertices: std::vector (contiguous; random access by vertex ID) +// Edges: std::unordered_map (hash map keyed by target vertex ID) +// +// Key characteristics: +// - O(1) average edge lookup by target vertex ID using unordered_map::find(vid) +// - Edges automatically deduplicated (only one edge per target vertex) +// - Edges stored in unordered fashion (hash-bucket order, not sorted) +// - Forward iterators only (no bidirectional or random access) +// - Edge insertion/deletion O(1) average, O(n) worst case +// - Requires std::hash and operator== on VId (for hash map) +// +// Compared to vom_graph_traits (std::map edges): +// - vom: O(log n) operations, sorted order, bidirectional iterators +// - voum: O(1) average operations, unordered, forward iterators only +// +// Use cases: +// - Graphs with unique edges (no parallel edges to same target) +// - Fast edge lookup by target: find_vertex_edge(g, u, v) in O(1) average +// - Memory overhead: ~8-16 bytes per edge (pair) +// +// Parameter semantics mirror vom_graph_traits. +template +struct voum_graph_traits { + using edge_value_type = EV; + using vertex_value_type = VV; + using graph_value_type = GV; + using vertex_id_type = VId; + static constexpr bool sourced = Sourced; + + using edge_type = dynamic_edge; + using vertex_type = dynamic_vertex; + using graph_type = dynamic_graph; + + using vertices_type = std::vector; + using edges_type = std::unordered_map; // Hash map keyed by target vertex ID +}; + +} // namespace graph::container diff --git a/include/graph/container/undirected_adjacency_list.hpp b/include/graph/container/undirected_adjacency_list.hpp index 646a06c..2c64533 100644 --- a/include/graph/container/undirected_adjacency_list.hpp +++ b/include/graph/container/undirected_adjacency_list.hpp @@ -120,7 +120,7 @@ namespace ranges = std::ranges; /// EXAMPLE USAGE: /// -------------- /// @code -/// using Graph = undirected_adjacency_list; // vertex_value=string, edge_value=int +/// using Graph = undirected_adjacency_list; // edge_value=int, vertex_value=string /// /// Graph g; /// auto u = g.create_vertex("Alice"); @@ -133,56 +133,56 @@ namespace ranges = std::ranges; /// } /// @endcode /// -/// @tparam VV Vertex Value type (default: void for no value) /// @tparam EV Edge Value type (default: void for no value) +/// @tparam VV Vertex Value type (default: void for no value) /// @tparam GV Graph Value type (default: void for no value) /// @tparam VId Vertex id/index type (default: uint32_t) /// @tparam VContainer Vertex storage container template (default: std::vector) /// @tparam Alloc Allocator type (default: std::allocator) ///------------------------------------------------------------------------------------- -template class VContainer = vector, typename Alloc = allocator> class undirected_adjacency_list; -template class VContainer, typename Alloc> class ual_vertex; -template class VContainer, typename Alloc> class base_undirected_adjacency_list; -template class VContainer, typename Alloc> class ual_edge; -template class VContainer, typename Alloc> class ual_const_neighbor_iterator; -template class VContainer, @@ -196,16 +196,16 @@ class ual_neighbor_iterator; struct inward_list; struct outward_list; -template class VContainer, typename Alloc> class ual_vertex_edge_list; -template class VContainer, @@ -236,9 +236,9 @@ template class ual_edge_value { public: - using edge_type = ual_edge; + using edge_type = ual_edge; using value_type = EV; - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; public: constexpr ual_edge_value(const value_type& val) : value_(val) {} @@ -280,24 +280,24 @@ class ual_edge_value {}; /// This class stores the user-defined vertex value. A specialization for VV=void provides /// zero memory overhead when no vertex value is needed. /// -/// @tparam VV Vertex value type /// @tparam EV Edge value type +/// @tparam VV Vertex value type /// @tparam GV Graph value type /// @tparam VId Vertex ID type /// @tparam VContainer Container template for vertices /// @tparam Alloc Allocator type /// -template class VContainer, typename Alloc> class ual_vertex_value { public: - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using value_type = VV; - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; public: constexpr ual_vertex_value(const value_type& val) : value_(val) {} @@ -339,21 +339,21 @@ class ual_vertex_value { /// @brief Specialization for VV=void that provides zero memory overhead. /// template class VContainer, typename Alloc> -class ual_vertex_value {}; +class ual_vertex_value {}; ///------------------------------------------------------------------------------------- /// ual_vertex_edge_list /// -/// @tparam VV Vertex Value type. default = void. /// @tparam EV Edge Value type. default = void. +/// @tparam VV Vertex Value type. default = void. /// @tparam GV Graph Value type. default = void. /// @tparam IntexT The type used for vertex & edge index into the internal vectors. /// @tparam A Allocator. default = std::allocator /// @tparam ListT inward_list|outward_list. Which edge list this is for. /// -template class VContainer, @@ -363,9 +363,9 @@ class ual_vertex_edge_list { class iterator; class const_iterator; - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; using vertex_set = VContainer; using vertex_iterator = typename vertex_set::iterator; @@ -374,12 +374,12 @@ class ual_vertex_edge_list { using vertex_index = VId; using edge_value_type = EV; - using edge_type = ual_edge; + using edge_type = ual_edge; - using edge_list_type = ual_vertex_edge_list; - using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; + using edge_list_type = ual_vertex_edge_list; + using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; using vertex_edge_list_outward_link_type = - ual_vertex_edge_list_link; + ual_vertex_edge_list_link; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using value_type = edge_type; @@ -518,13 +518,13 @@ class ual_vertex_edge_list { public: template - void link_front(edge_type& uv, ual_vertex_edge_list_link& uv_link); + void link_front(edge_type& uv, ual_vertex_edge_list_link& uv_link); template - void link_back(edge_type& uv, ual_vertex_edge_list_link& uv_link); + void link_back(edge_type& uv, ual_vertex_edge_list_link& uv_link); template - void unlink(edge_type& uv, ual_vertex_edge_list_link& uv_link); + void unlink(edge_type& uv, ual_vertex_edge_list_link& uv_link); iterator begin(graph_type& g, vertex_id_type uid) noexcept; const_iterator begin(const graph_type& g, vertex_id_type uid) const noexcept; @@ -546,15 +546,15 @@ class ual_vertex_edge_list { ///------------------------------------------------------------------------------------- /// ual_vertex_edge_list_link /// -/// @tparam VV Vertex Value type. default = void. /// @tparam EV Edge Value type. default = void. +/// @tparam VV Vertex Value type. default = void. /// @tparam GV Graph Value type. default = void. /// @tparam IntexT The type used for vertex & edge index into the internal vectors. /// @tparam A Allocator. default = std::allocator /// @tparam ListT inward_list|outward_list. Which edge list this is for. /// -template class VContainer, @@ -562,9 +562,9 @@ template class ual_vertex_edge_list_link { public: - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; using vertex_set = VContainer; using vertex_iterator = typename vertex_set::iterator; @@ -572,10 +572,10 @@ class ual_vertex_edge_list_link { using vertex_id_type = VId; using vertex_index = VId; - using edge_type = ual_edge; + using edge_type = ual_edge; - using edge_list_type = ual_vertex_edge_list; - using edge_list_link_type = ual_vertex_edge_list_link; + using edge_list_type = ual_vertex_edge_list; + using edge_list_link_type = ual_vertex_edge_list_link; public: ual_vertex_edge_list_link(vertex_id_type uid) noexcept : vertex_id_(uid) {} @@ -610,28 +610,28 @@ class ual_vertex_edge_list_link { ///------------------------------------------------------------------------------------- /// ual_edge /// -/// @tparam VV Vertex Value type. default = void. /// @tparam EV Edge Value type. default = void. +/// @tparam VV Vertex Value type. default = void. /// @tparam GV Graph Value type. default = void. /// @tparam IntexT The type used for vertex & edge index into the internal vectors. /// @tparam A Allocator. default = std::allocator /// -template class VContainer, typename Alloc> class ual_edge : public ual_edge_value - , public ual_vertex_edge_list_link - , public ual_vertex_edge_list_link { + , public ual_vertex_edge_list_link + , public ual_vertex_edge_list_link { public: - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = GV; using base_value_type = ual_edge_value; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; using vertex_set = VContainer; using vertex_iterator = typename vertex_set::iterator; @@ -642,16 +642,16 @@ class ual_edge using edge_id_type = pair; using edge_value_type = EV; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_index = VId; using edge_size_type = size_t; using edge_difference_type = ptrdiff_t; - using edge_list_type = ual_vertex_edge_list; - using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; + using edge_list_type = ual_vertex_edge_list; + using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; using vertex_edge_list_outward_link_type = - ual_vertex_edge_list_link; + ual_vertex_edge_list_link; protected: // noexcept is only defined for move ctor & assignment b/c the user-defined value type could @@ -724,7 +724,7 @@ class ual_edge edge_id_type edge_id(const graph_type& g) const noexcept; friend graph_type; // the graph is the one to create & destroy edges because it owns the allocator - friend base_undirected_adjacency_list; // base class also creates edges + friend base_undirected_adjacency_list; // base class also creates edges friend vertex_type; // vertex can also destroy its own edges friend edge_list_type; // for delete, when clearing the list }; @@ -749,26 +749,26 @@ class ual_edge /// - graph_value() accessors (general template only) /// - graph_value() CPO friend function (general template only) /// -/// @tparam VV Vertex Value type /// @tparam EV Edge Value type +/// @tparam VV Vertex Value type /// @tparam GV Graph Value type (may be void) /// @tparam VId Vertex id/index type /// @tparam VContainer Vertex storage container template /// @tparam Alloc Allocator type ///------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> class base_undirected_adjacency_list { public: // Type Aliases - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = GV; using allocator_type = Alloc; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_value_type = VV; using vertex_id_type = VId; using vertex_index_type = VId; @@ -782,7 +782,7 @@ class base_undirected_adjacency_list { using vertex_range = vertex_set&; using const_vertex_range = const vertex_set&; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_value_type = EV; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_id_type = pair; // @@ -812,7 +812,7 @@ class base_undirected_adjacency_list { public: using iterator_category = forward_iterator_tag; using iterator_concept = std::forward_iterator_tag; - using value_type = ual_edge; + using value_type = ual_edge; using size_type = size_t; using difference_type = ptrdiff_t; using pointer = value_type const*; @@ -1198,14 +1198,14 @@ class base_undirected_adjacency_list { /// find_vertex(g, id) - returns view iterator yielding vertex_descriptor /// REQUIRED: Provides bounds checking - returns end() if id >= size() /// The default CPO implementation lacks this bounds checking. -template class VContainer, typename Alloc> -constexpr auto find_vertex(undirected_adjacency_list& g, VId id) noexcept { - using graph_type = undirected_adjacency_list; +constexpr auto find_vertex(undirected_adjacency_list& g, VId id) noexcept { + using graph_type = undirected_adjacency_list; using vertex_set = typename graph_type::vertex_set; using container_iter = typename vertex_set::iterator; using view_type = vertex_descriptor_view; @@ -1218,14 +1218,14 @@ constexpr auto find_vertex(undirected_adjacency_list(id)}; } -template class VContainer, typename Alloc> -constexpr auto find_vertex(const undirected_adjacency_list& g, VId id) noexcept { - using graph_type = undirected_adjacency_list; +constexpr auto find_vertex(const undirected_adjacency_list& g, VId id) noexcept { + using graph_type = undirected_adjacency_list; using vertex_set = typename graph_type::vertex_set; using container_iter = typename vertex_set::const_iterator; using view_type = vertex_descriptor_view; @@ -1242,15 +1242,15 @@ constexpr auto find_vertex(const undirected_adjacency_list class VContainer, typename Alloc, typename E> requires edge_descriptor_type -constexpr VId target_id(const undirected_adjacency_list& g, const E& e) noexcept { +constexpr VId target_id(const undirected_adjacency_list& g, const E& e) noexcept { return e.value()->other_vertex_id(g, static_cast(e.source_id())); } @@ -1261,15 +1261,15 @@ constexpr VId target_id(const undirected_adjacency_list class VContainer, typename Alloc, typename E> requires edge_descriptor_type -constexpr VId source_id([[maybe_unused]] const undirected_adjacency_list& g, +constexpr VId source_id([[maybe_unused]] const undirected_adjacency_list& g, const E& e) noexcept { return static_cast(e.source_id()); } @@ -1277,8 +1277,8 @@ constexpr VId source_id([[maybe_unused]] const undirected_adjacency_list class VContainer, @@ -1286,12 +1286,12 @@ template requires edge_descriptor_type && (!std::is_void_v) constexpr decltype(auto) - edge_value(undirected_adjacency_list&, const E& e) noexcept { + edge_value(undirected_adjacency_list&, const E& e) noexcept { return e.value()->value(); } -template class VContainer, @@ -1299,32 +1299,32 @@ template requires edge_descriptor_type && (!std::is_void_v) constexpr decltype(auto) - edge_value(const undirected_adjacency_list&, const E& e) noexcept { + edge_value(const undirected_adjacency_list&, const E& e) noexcept { return e.value()->value(); } ///------------------------------------------------------------------------------------- /// ual_vertex /// -/// @tparam VV Vertex Value type. default = void. /// @tparam EV Edge Value type. default = void. +/// @tparam VV Vertex Value type. default = void. /// @tparam GV Graph Value type. default = void. /// @tparam IntexT The type used for vertex & edge index into the internal vectors. /// @tparam A Allocator. default = std::allocator /// -template class VContainer, typename Alloc> -class ual_vertex : public ual_vertex_value { +class ual_vertex : public ual_vertex_value { public: - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = GV; - using base_value_type = ual_vertex_value; + using base_value_type = ual_vertex_value; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; using vertex_set = VContainer; using vertex_iterator = typename vertex_set::iterator; @@ -1334,23 +1334,23 @@ class ual_vertex : public ual_vertex_value { using vertex_value_type = VV; using edge_value_type = EV; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_size_type = typename edge_type::edge_size_type; using edge_difference_type = typename edge_type::edge_difference_type; - using vertex_edge_list_type = ual_vertex_edge_list; - using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; + using vertex_edge_list_type = ual_vertex_edge_list; + using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; using vertex_edge_list_outward_link_type = - ual_vertex_edge_list_link; + ual_vertex_edge_list_link; using vertex_edge_size_type = typename vertex_edge_list_type::size_type; using vertex_edge_iterator = typename vertex_edge_list_type::iterator; using const_vertex_edge_iterator = typename vertex_edge_list_type::const_iterator; using vertex_edge_range = typename vertex_edge_list_type::edge_range; using const_vertex_edge_range = typename vertex_edge_list_type::const_edge_range; - using neighbor_iterator = ual_neighbor_iterator; - using const_neighbor_iterator = ual_const_neighbor_iterator; + using neighbor_iterator = ual_neighbor_iterator; + using const_neighbor_iterator = ual_const_neighbor_iterator; using neighbor_range = ranges::subrange; using const_neighbor_range = ranges::subrange; @@ -1415,19 +1415,19 @@ class ual_vertex : public ual_vertex_value { friend vertex_edge_list_outward_link_type; }; -template class VContainer, typename Alloc> class ual_const_neighbor_iterator { public: - using this_t = ual_const_neighbor_iterator; + using this_t = ual_const_neighbor_iterator; - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_value_type = VV; using vertex_id_type = VId; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; @@ -1436,12 +1436,12 @@ class ual_const_neighbor_iterator { using vertex_iterator = typename vertex_set::iterator; using const_vertex_iterator = typename vertex_set::const_iterator; - using edge_list_type = ual_vertex_edge_list; + using edge_list_type = ual_vertex_edge_list; using vertex_edge_size_type = typename edge_list_type::size_type; using vertex_edge_iterator = typename edge_list_type::iterator; using const_vertex_edge_iterator = typename edge_list_type::const_iterator; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_value_type = typename edge_type::edge_value_type; using edge_id_type = typename edge_type::edge_id_type; // @@ -1491,16 +1491,16 @@ class ual_const_neighbor_iterator { vertex_edge_iterator uv_; }; -template class VContainer, typename Alloc> -class ual_neighbor_iterator : public ual_const_neighbor_iterator { +class ual_neighbor_iterator : public ual_const_neighbor_iterator { public: - using this_t = ual_neighbor_iterator; - using base_t = ual_const_neighbor_iterator; + using this_t = ual_neighbor_iterator; + using base_t = ual_const_neighbor_iterator; using graph_type = typename base_t::graph_type; using vertex_type = typename base_t::vertex_type; @@ -1583,37 +1583,37 @@ class ual_neighbor_iterator : public ual_const_neighbor_iterator Random-access container type used to store vertices (V) with allocator (A). /// @tparam Alloc Allocator. default = std::allocator /// // clang-format off -template class VContainer, typename Alloc> class undirected_adjacency_list - : public base_undirected_adjacency_list + : public base_undirected_adjacency_list // clang-format on { public: - using base_type = base_undirected_adjacency_list; + using base_type = base_undirected_adjacency_list; // Note: Access base class members using this-> or base_type:: // (using declarations don't work in constructor initializer lists) - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = GV; using allocator_type = Alloc; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_value_type = VV; - using vertex_value_wrapper_type = ual_vertex_value; + using vertex_value_wrapper_type = ual_vertex_value; using vertex_id_type = VId; using vertex_index_type = VId; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; @@ -1626,7 +1626,7 @@ class undirected_adjacency_list using vertex_range = vertex_set&; using const_vertex_range = const vertex_set&; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_value_type = EV; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_id_type = pair; // @@ -1884,7 +1884,7 @@ class undirected_adjacency_list }; ///------------------------------------------------------------------------------------- -/// undirected_adjacency_list - Specialization for GV=void +/// undirected_adjacency_list - Specialization for GV=void /// /// @brief Specialization of undirected_adjacency_list when no graph value is needed. /// @@ -1893,20 +1893,20 @@ class undirected_adjacency_list /// /// All other functionality remains identical to the primary template. ///------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -class undirected_adjacency_list - : public base_undirected_adjacency_list { +template class VContainer, typename Alloc> +class undirected_adjacency_list + : public base_undirected_adjacency_list { public: - using base_type = base_undirected_adjacency_list; + using base_type = base_undirected_adjacency_list; // Note: Access base class members using this-> or base_type:: // (using declarations don't work in constructor initializer lists) - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = void; using allocator_type = Alloc; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_value_type = VV; using vertex_id_type = VId; using vertex_index_type = VId; @@ -1920,7 +1920,7 @@ class undirected_adjacency_list using vertex_range = vertex_set&; using const_vertex_range = const vertex_set&; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_value_type = EV; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_id_type = pair; diff --git a/include/graph/detail/edge_cpo.hpp b/include/graph/detail/edge_cpo.hpp index 2319693..3e28137 100644 --- a/include/graph/detail/edge_cpo.hpp +++ b/include/graph/detail/edge_cpo.hpp @@ -486,11 +486,19 @@ namespace _cpo_impls { * * This provides access to user-defined edge properties/weights stored in the graph. * + * **Return-type preservation guarantee:** The `operator()` returns `decltype(auto)`, + * which faithfully preserves the exact return type of the resolved function: + * return-by-value (`T`), return-by-reference (`T&`), return-by-const-reference + * (`const T&`), and return-by-rvalue-reference (`T&&`) are all forwarded without + * decay or copy. + * * @tparam G Graph type * @tparam E Edge descriptor or edge type * @param g Graph container * @param uv Edge descriptor or edge - * @return Reference to the edge value/properties (or by-value if custom implementation returns by-value) + * @return Exactly the type returned by the resolved dispatch path (`decltype(auto)`). + * By-value, by-reference, by-const-reference, and by-rvalue-reference + * are all preserved. */ template [[nodiscard]] constexpr decltype(auto) operator()(G&& g, E&& uv) const diff --git a/tests/common/graph_test_types.hpp b/tests/common/graph_test_types.hpp index 3ab77e6..c26665c 100644 --- a/tests/common/graph_test_types.hpp +++ b/tests/common/graph_test_types.hpp @@ -50,8 +50,10 @@ #include #include // Edge multiset containers (sorted edges with duplicates allowed) -#include -#include +#include +#include +// Edge unordered_map containers (hash-based, deduplicated by target_id) +#include #include namespace graph::test { @@ -349,22 +351,33 @@ struct uofl_tag { * @brief Tag for vector + map container type * @note Edges are sorted by target_id (map key), deduplicated (only one edge per target) */ -struct voem_tag { - static constexpr const char* name = "voem"; +struct vom_tag { + static constexpr const char* name = "vom"; template - using traits = graph::container::voem_graph_traits; + using traits = graph::container::vom_graph_traits; }; /** * @brief Tag for map + map container type * @note Vertices are sparse, edges are sorted by target_id (map key), deduplicated */ -struct moem_tag { - static constexpr const char* name = "moem"; +struct mom_tag { + static constexpr const char* name = "mom"; template - using traits = graph::container::moem_graph_traits; + using traits = graph::container::mom_graph_traits; +}; + +/** + * @brief Tag for vector + unordered_map container type + * @note Edges are hash-based, deduplicated (only one edge per target), unordered + */ +struct voum_tag { + static constexpr const char* name = "voum"; + + template + using traits = graph::container::voum_graph_traits; }; // ============================================================================= diff --git a/tests/container/CMakeLists.txt b/tests/container/CMakeLists.txt index d59f006..ab3993d 100644 --- a/tests/container/CMakeLists.txt +++ b/tests/container/CMakeLists.txt @@ -22,8 +22,9 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_uov.cpp dynamic_graph/test_dynamic_graph_uod.cpp dynamic_graph/test_dynamic_graph_vos.cpp - dynamic_graph/test_dynamic_graph_voem.cpp - dynamic_graph/test_dynamic_graph_moem.cpp + dynamic_graph/test_dynamic_graph_vom.cpp + dynamic_graph/test_dynamic_graph_voum.cpp + dynamic_graph/test_dynamic_graph_mom.cpp dynamic_graph/test_dynamic_graph_dos.cpp dynamic_graph/test_dynamic_graph_mos.cpp dynamic_graph/test_dynamic_graph_uos.cpp @@ -53,7 +54,7 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_cpo_map_vertices.cpp dynamic_graph/test_dynamic_graph_cpo_unordered_map_vertices.cpp - # dynamic_graph - CPO tests (edge map containers - voem, moem) + # dynamic_graph - CPO tests (edge map containers - vom, mom) dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp # undirected_adjacency_list diff --git a/tests/container/dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp b/tests/container/dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp index aeba686..7265a48 100644 --- a/tests/container/dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp +++ b/tests/container/dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp @@ -1,12 +1,12 @@ /** * @file test_dynamic_graph_cpo_edge_map.cpp - * @brief Consolidated CPO tests for edge map containers (voem, moem) + * @brief Consolidated CPO tests for edge map containers (vom, mom, voum) * - * Edge map containers use std::map for edge storage (keyed by target_id): - * - Edges are sorted by target_id + * Edge map containers use std::map or std::unordered_map for edge storage (keyed by target_id): * - Edges are DEDUPLICATED (only one edge per target vertex - no parallel edges) - * - voem: vector vertices (resize_vertices), map edges - * - moem: map vertices (sparse, on-demand), map edges + * - vom: vector vertices (resize_vertices), map edges (sorted by target_id) + * - mom: map vertices (sparse, on-demand), map edges (sorted by target_id) + * - voum: vector vertices (resize_vertices), unordered_map edges (hash-based, unordered) * * Tests are adapted to handle both vertex container semantics. */ @@ -26,7 +26,11 @@ using namespace graph::test; // Helper to check if a tag uses map-based vertices (sparse) template -constexpr bool is_map_based_v = std::is_same_v; +constexpr bool is_map_based_v = std::is_same_v; + +// Helper to check if a tag uses unordered edge containers (no sorted order guarantee) +template +constexpr bool is_unordered_edges_v = std::is_same_v; // Helper type for edges using edge_void = copyable_edge_t; @@ -36,7 +40,7 @@ using edge_int = copyable_edge_t; // 1. vertices(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO vertices(g)", "[dynamic_graph][cpo][vertices][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO vertices(g)", "[dynamic_graph][cpo][vertices][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -94,7 +98,7 @@ TEMPLATE_TEST_CASE("edge_map CPO vertices(g)", "[dynamic_graph][cpo][vertices][e // 2. num_vertices(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g)", "[dynamic_graph][cpo][num_vertices][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g)", "[dynamic_graph][cpo][num_vertices][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -164,8 +168,9 @@ TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g)", "[dynamic_graph][cpo][num_ver TEMPLATE_TEST_CASE("edge_map CPO find_vertex(g, uid)", "[dynamic_graph][cpo][find_vertex][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -243,7 +248,7 @@ TEMPLATE_TEST_CASE("edge_map CPO find_vertex(g, uid)", // 4. vertex_id(g, u) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO vertex_id(g, u)", "[dynamic_graph][cpo][vertex_id][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO vertex_id(g, u)", "[dynamic_graph][cpo][vertex_id][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -321,7 +326,7 @@ TEMPLATE_TEST_CASE("edge_map CPO vertex_id(g, u)", "[dynamic_graph][cpo][vertex_ // 5. num_edges(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO num_edges(g)", "[dynamic_graph][cpo][num_edges][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO num_edges(g)", "[dynamic_graph][cpo][num_edges][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -395,7 +400,7 @@ TEMPLATE_TEST_CASE("edge_map CPO num_edges(g)", "[dynamic_graph][cpo][num_edges] // 6. has_edge(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO has_edge(g)", "[dynamic_graph][cpo][has_edge][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO has_edge(g)", "[dynamic_graph][cpo][has_edge][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -450,7 +455,7 @@ TEMPLATE_TEST_CASE("edge_map CPO has_edge(g)", "[dynamic_graph][cpo][has_edge][e // 7. edges(g, u) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO edges(g, u)", "[dynamic_graph][cpo][edges][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO edges(g, u)", "[dynamic_graph][cpo][edges][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -473,8 +478,10 @@ TEMPLATE_TEST_CASE("edge_map CPO edges(g, u)", "[dynamic_graph][cpo][edges][edge targets.push_back(target_id(g, uv)); } - // Edges are sorted by target_id in multiset REQUIRE(targets.size() == 2); + if constexpr (is_unordered_edges_v) { + std::ranges::sort(targets); + } REQUIRE(targets[0] == 1); REQUIRE(targets[1] == 2); } @@ -565,7 +572,7 @@ TEMPLATE_TEST_CASE("edge_map CPO edges(g, u)", "[dynamic_graph][cpo][edges][edge // 8. degree(g, u) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO degree(g, u)", "[dynamic_graph][cpo][degree][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO degree(g, u)", "[dynamic_graph][cpo][degree][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -647,7 +654,7 @@ TEMPLATE_TEST_CASE("edge_map CPO degree(g, u)", "[dynamic_graph][cpo][degree][ed // 9. target_id(g, uv) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO target_id(g, uv)", "[dynamic_graph][cpo][target_id][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO target_id(g, uv)", "[dynamic_graph][cpo][target_id][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -669,8 +676,10 @@ TEMPLATE_TEST_CASE("edge_map CPO target_id(g, uv)", "[dynamic_graph][cpo][target targets.push_back(target_id(g, uv)); } - // Map keeps edges sorted by target REQUIRE(targets.size() == 2); + if constexpr (is_unordered_edges_v) { + std::ranges::sort(targets); + } REQUIRE(targets[0] == 1); REQUIRE(targets[1] == 2); } @@ -734,7 +743,7 @@ TEMPLATE_TEST_CASE("edge_map CPO target_id(g, uv)", "[dynamic_graph][cpo][target // 10. target(g, uv) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO target(g, uv)", "[dynamic_graph][cpo][target][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO target(g, uv)", "[dynamic_graph][cpo][target][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -816,8 +825,9 @@ TEMPLATE_TEST_CASE("edge_map CPO target(g, uv)", "[dynamic_graph][cpo][target][e TEMPLATE_TEST_CASE("edge_map CPO find_vertex_edge(g, uid, vid)", "[dynamic_graph][cpo][find_vertex_edge][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -886,8 +896,9 @@ TEMPLATE_TEST_CASE("edge_map CPO find_vertex_edge(g, uid, vid)", TEMPLATE_TEST_CASE("edge_map CPO contains_edge(g, uid, vid)", "[dynamic_graph][cpo][contains_edge][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -972,8 +983,9 @@ TEMPLATE_TEST_CASE("edge_map CPO contains_edge(g, uid, vid)", TEMPLATE_TEST_CASE("edge_map CPO vertex_value(g, u)", "[dynamic_graph][cpo][vertex_value][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_int_vv = typename Types::int_vv; @@ -1029,7 +1041,7 @@ TEMPLATE_TEST_CASE("edge_map CPO vertex_value(g, u)", // 14. edge_value(g, uv) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO edge_value(g, uv)", "[dynamic_graph][cpo][edge_value][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO edge_value(g, uv)", "[dynamic_graph][cpo][edge_value][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_int_ev = typename Types::int_ev; @@ -1099,7 +1111,7 @@ TEMPLATE_TEST_CASE("edge_map CPO edge_value(g, uv)", "[dynamic_graph][cpo][edge_ // 15. graph_value(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO graph_value(g)", "[dynamic_graph][cpo][graph_value][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO graph_value(g)", "[dynamic_graph][cpo][graph_value][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_all_int = typename Types::all_int; @@ -1151,7 +1163,7 @@ TEMPLATE_TEST_CASE("edge_map CPO graph_value(g)", "[dynamic_graph][cpo][graph_va // 16. source_id(g, uv) CPO Tests (Sourced=true) //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO source_id(g, uv)", "[dynamic_graph][cpo][source_id][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO source_id(g, uv)", "[dynamic_graph][cpo][source_id][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_sourced = typename Types::sourced_void; using Graph_sourced_int = typename Types::sourced_int; @@ -1228,7 +1240,7 @@ TEMPLATE_TEST_CASE("edge_map CPO source_id(g, uv)", "[dynamic_graph][cpo][source // 17. source(g, uv) CPO Tests (Sourced=true) //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO source(g, uv)", "[dynamic_graph][cpo][source][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO source(g, uv)", "[dynamic_graph][cpo][source][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_sourced = typename Types::sourced_void; using Graph_sourced_int = typename Types::sourced_int; @@ -1310,8 +1322,9 @@ TEMPLATE_TEST_CASE("edge_map CPO source(g, uv)", "[dynamic_graph][cpo][source][e TEMPLATE_TEST_CASE("edge_map CPO partition_id(g, u)", "[dynamic_graph][cpo][partition_id][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1353,8 +1366,9 @@ TEMPLATE_TEST_CASE("edge_map CPO partition_id(g, u)", TEMPLATE_TEST_CASE("edge_map CPO num_partitions(g)", "[dynamic_graph][cpo][num_partitions][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1397,8 +1411,9 @@ TEMPLATE_TEST_CASE("edge_map CPO num_partitions(g)", TEMPLATE_TEST_CASE("edge_map CPO vertices(g, pid)", "[dynamic_graph][cpo][vertices][partition][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1454,8 +1469,9 @@ TEMPLATE_TEST_CASE("edge_map CPO vertices(g, pid)", TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g, pid)", "[dynamic_graph][cpo][num_vertices][partition][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1511,8 +1527,9 @@ TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g, pid)", TEMPLATE_TEST_CASE("edge_map CPO integration: duplicate edges", "[dynamic_graph][cpo][integration][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_int_ev = typename Types::int_ev; @@ -1569,8 +1586,9 @@ TEMPLATE_TEST_CASE("edge_map CPO integration: duplicate edges", TEMPLATE_TEST_CASE("edge_map CPO integration: values", "[dynamic_graph][cpo][integration][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_all_int = typename Types::all_int; @@ -1612,8 +1630,9 @@ TEMPLATE_TEST_CASE("edge_map CPO integration: values", TEMPLATE_TEST_CASE("edge_map CPO integration: traversal", "[dynamic_graph][cpo][integration][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1654,8 +1673,11 @@ TEMPLATE_TEST_CASE("edge_map CPO integration: traversal", targets.push_back(target_id(g, uv)); } - // Map sorts by target key + // Map sorts by target key; unordered_map does not, so sort for comparison REQUIRE(targets.size() == 3); + if constexpr (is_unordered_edges_v) { + std::ranges::sort(targets); + } REQUIRE(targets[0] == 1); REQUIRE(targets[1] == 2); REQUIRE(targets[2] == 3); diff --git a/tests/container/dynamic_graph/test_dynamic_graph_moem.cpp b/tests/container/dynamic_graph/test_dynamic_graph_mom.cpp similarity index 88% rename from tests/container/dynamic_graph/test_dynamic_graph_moem.cpp rename to tests/container/dynamic_graph/test_dynamic_graph_mom.cpp index 4c84a5d..13d2dc3 100644 --- a/tests/container/dynamic_graph/test_dynamic_graph_moem.cpp +++ b/tests/container/dynamic_graph/test_dynamic_graph_mom.cpp @@ -3,7 +3,7 @@ * @brief Tests for dynamic_graph with map vertices + set edges * * Phase 4.1.4: Map Vertex + Set Edge Containers - * Tests moem_graph_traits (map vertices + set edges) + * Tests mom_graph_traits (map vertices + set edges) * * Key characteristics: * - Vertices: std::map (associative; key-based lookup; bidirectional iteration) @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include #include @@ -31,35 +31,35 @@ using namespace graph::container; // Type aliases for common test configurations with uint32_t vertex IDs using mos_void_void_void = - dynamic_graph>; + dynamic_graph>; using mos_int_void_void = - dynamic_graph>; + dynamic_graph>; using mos_void_int_void = - dynamic_graph>; + dynamic_graph>; using mos_int_int_void = - dynamic_graph>; + dynamic_graph>; using mos_void_void_int = - dynamic_graph>; + dynamic_graph>; using mos_int_int_int = - dynamic_graph>; + dynamic_graph>; // Type aliases with string vertex IDs (the primary use case for map containers) using mos_str_void_void_void = - dynamic_graph>; + dynamic_graph>; using mos_str_int_void_void = - dynamic_graph>; + dynamic_graph>; using mos_str_void_int_void = - dynamic_graph>; + dynamic_graph>; using mos_str_int_int_int = - dynamic_graph>; + dynamic_graph>; using mos_sourced = - dynamic_graph>; + dynamic_graph>; using mos_int_sourced = - dynamic_graph>; + dynamic_graph>; using mos_str_sourced = - dynamic_graph>; + dynamic_graph>; // Edge and vertex data types for loading using edge_void = copyable_edge_t; @@ -80,9 +80,9 @@ size_t count_all_edges(G& g) { // 1. Traits Verification Tests //================================================================================================== -TEST_CASE("moem traits verification", "[dynamic_graph][moem][traits]") { +TEST_CASE("mom traits verification", "[dynamic_graph][mom][traits]") { SECTION("vertices_type is std::map") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; using vertices_t = typename traits::vertices_type; // Verify it's a map by checking for map-specific members static_assert(requires { typename vertices_t::key_type; }); @@ -91,7 +91,7 @@ TEST_CASE("moem traits verification", "[dynamic_graph][moem][traits]") { } SECTION("edges_type is std::set") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; using edges_t = typename traits::edges_type; // set has key_type and doesn't have mapped_type (unlike map) static_assert(requires { typename edges_t::key_type; }); @@ -99,27 +99,27 @@ TEST_CASE("moem traits verification", "[dynamic_graph][moem][traits]") { } SECTION("vertex_id_type can be string") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; static_assert(std::same_as); REQUIRE(true); } SECTION("sourced flag is preserved") { - using traits_unsourced = moem_graph_traits; - using traits_sourced = moem_graph_traits; + using traits_unsourced = mom_graph_traits; + using traits_sourced = mom_graph_traits; static_assert(traits_unsourced::sourced == false); static_assert(traits_sourced::sourced == true); REQUIRE(true); } SECTION("vertex_id_type for uint32_t") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; static_assert(std::same_as); REQUIRE(true); } SECTION("custom vertex_id_type") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; static_assert(std::same_as); REQUIRE(true); } @@ -129,7 +129,7 @@ TEST_CASE("moem traits verification", "[dynamic_graph][moem][traits]") { // 2. Iterator Category Tests //================================================================================================== -TEST_CASE("moem iterator categories", "[dynamic_graph][moem][iterators]") { +TEST_CASE("mom iterator categories", "[dynamic_graph][mom][iterators]") { SECTION("underlying map iterators are bidirectional") { using G = mos_void_void_void; using iter_t = typename G::vertices_type::iterator; @@ -139,7 +139,7 @@ TEST_CASE("moem iterator categories", "[dynamic_graph][moem][iterators]") { } SECTION("set edge iterators are bidirectional") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; using edges_t = typename traits::edges_type; using edge_iter_t = typename edges_t::iterator; // set iterators are bidirectional @@ -161,7 +161,7 @@ TEST_CASE("moem iterator categories", "[dynamic_graph][moem][iterators]") { // 3. Construction Tests //================================================================================================== -TEST_CASE("moem construction", "[dynamic_graph][moem][construction]") { +TEST_CASE("mom construction", "[dynamic_graph][mom][construction]") { SECTION("default constructor creates empty graph") { mos_void_void_void g; REQUIRE(g.size() == 0); @@ -223,7 +223,7 @@ TEST_CASE("moem construction", "[dynamic_graph][moem][construction]") { } } -TEST_CASE("moem construction with string vertex IDs", "[dynamic_graph][moem][construction][string]") { +TEST_CASE("mom construction with string vertex IDs", "[dynamic_graph][mom][construction][string]") { SECTION("default constructor creates empty graph") { mos_str_void_void_void g; REQUIRE(g.size() == 0); @@ -245,7 +245,7 @@ TEST_CASE("moem construction with string vertex IDs", "[dynamic_graph][moem][con } } -TEST_CASE("moem construction sourced", "[dynamic_graph][moem][construction][sourced]") { +TEST_CASE("mom construction sourced", "[dynamic_graph][mom][construction][sourced]") { SECTION("sourced edge construction with uint32_t IDs") { mos_sourced g; REQUIRE(g.size() == 0); @@ -266,7 +266,7 @@ TEST_CASE("moem construction sourced", "[dynamic_graph][moem][construction][sour // 4. Basic Properties Tests //================================================================================================== -TEST_CASE("moem properties", "[dynamic_graph][moem][properties]") { +TEST_CASE("mom properties", "[dynamic_graph][mom][properties]") { SECTION("size() on empty graph") { mos_void_void_void g; REQUIRE(g.size() == 0); @@ -293,7 +293,7 @@ TEST_CASE("moem properties", "[dynamic_graph][moem][properties]") { } } -TEST_CASE("moem properties with string IDs", "[dynamic_graph][moem][properties][string]") { +TEST_CASE("mom properties with string IDs", "[dynamic_graph][mom][properties][string]") { SECTION("size() on empty graph") { mos_str_void_void_void g; REQUIRE(g.size() == 0); @@ -309,7 +309,7 @@ TEST_CASE("moem properties with string IDs", "[dynamic_graph][moem][properties][ // 5. Initializer List Construction Tests (uint32_t vertex IDs) //================================================================================================== -TEST_CASE("moem initializer_list construction", "[dynamic_graph][moem][initializer_list]") { +TEST_CASE("mom initializer_list construction", "[dynamic_graph][mom][initializer_list]") { SECTION("empty initializer list") { using G = mos_void_void_void; G g({}); @@ -371,7 +371,7 @@ TEST_CASE("moem initializer_list construction", "[dynamic_graph][moem][initializ // 6. Set-Specific Behavior: Deduplication Tests //================================================================================================== -TEST_CASE("moem edge deduplication", "[dynamic_graph][moem][set][deduplication]") { +TEST_CASE("mom edge deduplication", "[dynamic_graph][mom][set][deduplication]") { SECTION("duplicate edges are ignored - unsourced") { mos_void_void_void g; // Load edges with duplicates @@ -429,7 +429,7 @@ TEST_CASE("moem edge deduplication", "[dynamic_graph][moem][set][deduplication]" // 7. Set-Specific Behavior: Sorted Order Tests //================================================================================================== -TEST_CASE("moem edges are sorted by target_id", "[dynamic_graph][moem][set][sorted]") { +TEST_CASE("mom edges are sorted by target_id", "[dynamic_graph][mom][set][sorted]") { SECTION("unsourced edges sorted by target_id") { // Insert edges in unsorted order mos_void_void_void g; @@ -469,7 +469,7 @@ TEST_CASE("moem edges are sorted by target_id", "[dynamic_graph][moem][set][sort // 8. Initializer List Construction Tests (string vertex IDs) //================================================================================================== -TEST_CASE("moem initializer_list construction string IDs", "[dynamic_graph][moem][initializer_list][string]") { +TEST_CASE("mom initializer_list construction string IDs", "[dynamic_graph][mom][initializer_list][string]") { SECTION("single edge with string IDs") { using G = mos_str_void_void_void; G g({{"alice", "bob"}}); @@ -510,7 +510,7 @@ TEST_CASE("moem initializer_list construction string IDs", "[dynamic_graph][moem // 9. Graph Value Tests //================================================================================================== -TEST_CASE("moem graph value access", "[dynamic_graph][moem][graph_value]") { +TEST_CASE("mom graph value access", "[dynamic_graph][mom][graph_value]") { SECTION("graph_value() returns reference") { mos_void_void_int g(100); REQUIRE(g.graph_value() == 100); @@ -542,7 +542,7 @@ TEST_CASE("moem graph value access", "[dynamic_graph][moem][graph_value]") { // 10. Graph Iteration Tests //================================================================================================== -TEST_CASE("moem graph iteration", "[dynamic_graph][moem][iteration]") { +TEST_CASE("mom graph iteration", "[dynamic_graph][mom][iteration]") { SECTION("iterate over empty graph") { mos_void_void_void g; size_t count = 0; @@ -604,7 +604,7 @@ TEST_CASE("moem graph iteration", "[dynamic_graph][moem][iteration]") { // 11. Vertex Accessor Methods Tests //================================================================================================== -TEST_CASE("moem contains_vertex", "[dynamic_graph][moem][accessor][contains_vertex]") { +TEST_CASE("mom contains_vertex", "[dynamic_graph][mom][accessor][contains_vertex]") { SECTION("uint32_t vertex IDs") { using G = mos_void_void_void; G g({{0, 1}, {1, 2}, {5, 10}}); @@ -654,7 +654,7 @@ TEST_CASE("moem contains_vertex", "[dynamic_graph][moem][accessor][contains_vert } } -TEST_CASE("moem try_find_vertex", "[dynamic_graph][moem][accessor][try_find_vertex]") { +TEST_CASE("mom try_find_vertex", "[dynamic_graph][mom][accessor][try_find_vertex]") { SECTION("uint32_t vertex IDs - found") { using G = mos_void_void_void; G g({{0, 1}, {1, 2}, {5, 10}}); @@ -718,7 +718,7 @@ TEST_CASE("moem try_find_vertex", "[dynamic_graph][moem][accessor][try_find_vert } } -TEST_CASE("moem vertex_at", "[dynamic_graph][moem][accessor][vertex_at]") { +TEST_CASE("mom vertex_at", "[dynamic_graph][mom][accessor][vertex_at]") { SECTION("uint32_t vertex IDs - found") { using G = mos_void_void_void; G g({{0, 1}, {1, 2}}); @@ -778,7 +778,7 @@ TEST_CASE("moem vertex_at", "[dynamic_graph][moem][accessor][vertex_at]") { // 12. load_vertices Tests //================================================================================================== -TEST_CASE("moem load_vertices", "[dynamic_graph][moem][load_vertices]") { +TEST_CASE("mom load_vertices", "[dynamic_graph][mom][load_vertices]") { SECTION("uint32_t IDs - basic load") { using G = mos_void_int_void; using vertex_data = copyable_vertex_t; @@ -829,7 +829,7 @@ TEST_CASE("moem load_vertices", "[dynamic_graph][moem][load_vertices]") { // 13. load_edges Tests //================================================================================================== -TEST_CASE("moem load_edges explicit", "[dynamic_graph][moem][load_edges]") { +TEST_CASE("mom load_edges explicit", "[dynamic_graph][mom][load_edges]") { SECTION("uint32_t IDs - basic load") { using G = mos_int_void_void; using edge_data = copyable_edge_t; @@ -883,7 +883,7 @@ TEST_CASE("moem load_edges explicit", "[dynamic_graph][moem][load_edges]") { // 14. Edge Cases and Error Handling //================================================================================================== -TEST_CASE("moem edge cases", "[dynamic_graph][moem][edge_cases]") { +TEST_CASE("mom edge cases", "[dynamic_graph][mom][edge_cases]") { SECTION("graph with single vertex (self-loop)") { using G = mos_void_void_void; G g({{0, 0}}); @@ -944,7 +944,7 @@ TEST_CASE("moem edge cases", "[dynamic_graph][moem][edge_cases]") { // 15. Const Correctness Tests //================================================================================================== -TEST_CASE("moem const correctness", "[dynamic_graph][moem][const]") { +TEST_CASE("mom const correctness", "[dynamic_graph][mom][const]") { SECTION("const graph properties") { using G = mos_int_void_void; const G g({{0, 1, 10}, {1, 2, 20}}); @@ -969,7 +969,7 @@ TEST_CASE("moem const correctness", "[dynamic_graph][moem][const]") { // 16. Memory and Resource Management Tests //================================================================================================== -TEST_CASE("moem memory management", "[dynamic_graph][moem][memory]") { +TEST_CASE("mom memory management", "[dynamic_graph][mom][memory]") { SECTION("multiple independent graphs") { using G = mos_void_void_int; G g1(100, {{0, 1}}); @@ -1005,7 +1005,7 @@ TEST_CASE("moem memory management", "[dynamic_graph][moem][memory]") { // 17. Template Instantiation Tests //================================================================================================== -TEST_CASE("moem template instantiation", "[dynamic_graph][moem][compilation]") { +TEST_CASE("mom template instantiation", "[dynamic_graph][mom][compilation]") { [[maybe_unused]] mos_void_void_void g1; [[maybe_unused]] mos_int_void_void g2; [[maybe_unused]] mos_void_int_void g3; @@ -1026,7 +1026,7 @@ TEST_CASE("moem template instantiation", "[dynamic_graph][moem][compilation]") { // 18. Sparse Vertex Behavior Tests //================================================================================================== -TEST_CASE("moem sparse vertex behavior", "[dynamic_graph][moem][sparse]") { +TEST_CASE("mom sparse vertex behavior", "[dynamic_graph][mom][sparse]") { SECTION("only referenced vertices are created") { using G = mos_void_void_void; @@ -1053,7 +1053,7 @@ TEST_CASE("moem sparse vertex behavior", "[dynamic_graph][moem][sparse]") { // 19. Edge Bidirectional Iteration Tests //================================================================================================== -TEST_CASE("moem edge bidirectional iteration", "[dynamic_graph][moem][set][iteration]") { +TEST_CASE("mom edge bidirectional iteration", "[dynamic_graph][mom][set][iteration]") { SECTION("forward iteration") { mos_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); @@ -1090,7 +1090,7 @@ TEST_CASE("moem edge bidirectional iteration", "[dynamic_graph][moem][set][itera // 20. Sourced Edge Tests //================================================================================================== -TEST_CASE("moem sourced edges", "[dynamic_graph][moem][sourced]") { +TEST_CASE("mom sourced edges", "[dynamic_graph][mom][sourced]") { SECTION("source_id access") { mos_sourced g({{0, 1}, {0, 2}, {1, 0}}); diff --git a/tests/container/dynamic_graph/test_dynamic_graph_voem.cpp b/tests/container/dynamic_graph/test_dynamic_graph_vom.cpp similarity index 88% rename from tests/container/dynamic_graph/test_dynamic_graph_voem.cpp rename to tests/container/dynamic_graph/test_dynamic_graph_vom.cpp index b96ee09..45c4371 100644 --- a/tests/container/dynamic_graph/test_dynamic_graph_voem.cpp +++ b/tests/container/dynamic_graph/test_dynamic_graph_vom.cpp @@ -3,7 +3,7 @@ * @brief Comprehensive tests for dynamic_graph with vector vertices + set edges * * Phase 4.1.2: Set Edge Container Support - * Tests voem_graph_traits (vector vertices + set edges) + * Tests vom_graph_traits (vector vertices + set edges) * * Key characteristics of std::set edges: * - Automatic deduplication (no parallel edges with same endpoints) @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include #include @@ -28,17 +28,17 @@ using namespace graph::container; // Type aliases for common test configurations using vos_void_void_void = - dynamic_graph>; + dynamic_graph>; using vos_int_void_void = - dynamic_graph>; + dynamic_graph>; using vos_void_int_void = - dynamic_graph>; + dynamic_graph>; using vos_int_int_void = - dynamic_graph>; + dynamic_graph>; using vos_void_void_int = - dynamic_graph>; + dynamic_graph>; using vos_int_int_int = - dynamic_graph>; + dynamic_graph>; using vos_string_string_string = dynamic_graph>; + vom_graph_traits>; using vos_sourced = - dynamic_graph>; + dynamic_graph>; using vos_int_sourced = - dynamic_graph>; + dynamic_graph>; // Edge and vertex data types for loading using edge_void = copyable_edge_t; @@ -72,7 +72,7 @@ size_t count_all_edges(G& g) { // 1. Construction Tests //================================================================================================== -TEST_CASE("voem default construction", "[voem][construction]") { +TEST_CASE("vom default construction", "[vom][construction]") { SECTION("creates empty graph") { vos_void_void_void g; REQUIRE(g.size() == 0); @@ -109,7 +109,7 @@ TEST_CASE("voem default construction", "[voem][construction]") { } } -TEST_CASE("voem constructor with graph value", "[voem][construction]") { +TEST_CASE("vom constructor with graph value", "[vom][construction]") { SECTION("void GV - no graph value can be passed") { vos_void_void_void g; REQUIRE(g.size() == 0); @@ -126,7 +126,7 @@ TEST_CASE("voem constructor with graph value", "[voem][construction]") { // 2. Load Edges Tests //================================================================================================== -TEST_CASE("voem load_edges", "[voem][load]") { +TEST_CASE("vom load_edges", "[vom][load]") { SECTION("simple edges") { vos_void_void_void g; std::vector ee = {{0, 1}, {0, 2}, {1, 2}}; @@ -168,7 +168,7 @@ TEST_CASE("voem load_edges", "[voem][load]") { // 3. Initializer List Construction Tests //================================================================================================== -TEST_CASE("voem initializer list construction", "[voem][construction][initializer_list]") { +TEST_CASE("vom initializer list construction", "[vom][construction][initializer_list]") { SECTION("simple initializer list") { vos_void_void_void g({{0, 1}, {0, 2}, {1, 2}}); @@ -181,7 +181,7 @@ TEST_CASE("voem initializer list construction", "[voem][construction][initialize // 4. Set-Specific Behavior: Deduplication Tests //================================================================================================== -TEST_CASE("voem edge deduplication", "[voem][set][deduplication]") { +TEST_CASE("vom edge deduplication", "[vom][set][deduplication]") { SECTION("duplicate edges are ignored - unsourced") { vos_void_void_void g; // Load edges with duplicates @@ -238,7 +238,7 @@ TEST_CASE("voem edge deduplication", "[voem][set][deduplication]") { // 5. Set-Specific Behavior: Sorted Order Tests //================================================================================================== -TEST_CASE("voem edges are sorted by target_id", "[voem][set][sorted]") { +TEST_CASE("vom edges are sorted by target_id", "[vom][set][sorted]") { SECTION("unsourced edges sorted by target_id") { // Insert edges in unsorted order vos_void_void_void g; @@ -276,7 +276,7 @@ TEST_CASE("voem edges are sorted by target_id", "[voem][set][sorted]") { // 6. Vertex Access Tests //================================================================================================== -TEST_CASE("voem vertex access", "[voem][vertex][access]") { +TEST_CASE("vom vertex access", "[vom][vertex][access]") { SECTION("operator[] access") { vos_void_void_void g({{0, 1}, {1, 2}, {2, 3}}); @@ -305,7 +305,7 @@ TEST_CASE("voem vertex access", "[voem][vertex][access]") { } } -TEST_CASE("voem vertex iteration", "[voem][vertex][iteration]") { +TEST_CASE("vom vertex iteration", "[vom][vertex][iteration]") { SECTION("range-based for") { vos_void_void_void g({{0, 1}, {1, 2}, {2, 0}}); @@ -335,7 +335,7 @@ TEST_CASE("voem vertex iteration", "[voem][vertex][iteration]") { // 7. Edge Access Tests //================================================================================================== -TEST_CASE("voem edge access", "[voem][edge][access]") { +TEST_CASE("vom edge access", "[vom][edge][access]") { SECTION("edges() returns set") { vos_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); @@ -364,7 +364,7 @@ TEST_CASE("voem edge access", "[voem][edge][access]") { } } -TEST_CASE("voem edge bidirectional iteration", "[voem][edge][iteration]") { +TEST_CASE("vom edge bidirectional iteration", "[vom][edge][iteration]") { SECTION("forward iteration") { vos_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); @@ -397,7 +397,7 @@ TEST_CASE("voem edge bidirectional iteration", "[voem][edge][iteration]") { // 8. Vertex and Edge Value Tests //================================================================================================== -TEST_CASE("voem vertex values", "[voem][vertex][value]") { +TEST_CASE("vom vertex values", "[vom][vertex][value]") { SECTION("vertex value access") { vos_void_int_void g; std::vector vv = {{0, 100}, {1, 200}}; @@ -411,7 +411,7 @@ TEST_CASE("voem vertex values", "[voem][vertex][value]") { } } -TEST_CASE("voem edge values", "[voem][edge][value]") { +TEST_CASE("vom edge values", "[vom][edge][value]") { SECTION("edge values preserved after deduplication") { // First edge with value 100 should be kept vos_int_void_void g; @@ -430,7 +430,7 @@ TEST_CASE("voem edge values", "[voem][edge][value]") { // 9. Sourced Edge Tests //================================================================================================== -TEST_CASE("voem sourced edges", "[voem][sourced]") { +TEST_CASE("vom sourced edges", "[vom][sourced]") { SECTION("source_id access") { vos_sourced g({{0, 1}, {0, 2}, {1, 0}}); @@ -468,7 +468,7 @@ TEST_CASE("voem sourced edges", "[voem][sourced]") { // 10. Self-Loop Tests //================================================================================================== -TEST_CASE("voem self-loops", "[voem][self-loop]") { +TEST_CASE("vom self-loops", "[vom][self-loop]") { SECTION("single self-loop") { vos_void_void_void g({{0, 0}}); @@ -507,7 +507,7 @@ TEST_CASE("voem self-loops", "[voem][self-loop]") { // 11. Large Graph Tests //================================================================================================== -TEST_CASE("voem large graph", "[voem][performance]") { +TEST_CASE("vom large graph", "[vom][performance]") { SECTION("1000 vertices linear chain") { std::vector ee; for (uint32_t i = 0; i < 999; ++i) { @@ -543,7 +543,7 @@ TEST_CASE("voem large graph", "[voem][performance]") { // 12. Iterator Stability Tests (std::set guarantees) //================================================================================================== -TEST_CASE("voem set iterator stability", "[voem][set][iterator]") { +TEST_CASE("vom set iterator stability", "[vom][set][iterator]") { SECTION("edge iterators are bidirectional") { vos_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); @@ -570,7 +570,7 @@ TEST_CASE("voem set iterator stability", "[voem][set][iterator]") { // 13. Algorithm Compatibility Tests //================================================================================================== -TEST_CASE("voem algorithm compatibility", "[voem][algorithm]") { +TEST_CASE("vom algorithm compatibility", "[vom][algorithm]") { SECTION("std::ranges::for_each on vertices") { vos_void_void_void g({{0, 1}, {1, 2}, {2, 0}}); @@ -613,7 +613,7 @@ TEST_CASE("voem algorithm compatibility", "[voem][algorithm]") { // 14. Edge Case Tests //================================================================================================== -TEST_CASE("voem edge cases", "[voem][edge-cases]") { +TEST_CASE("vom edge cases", "[vom][edge-cases]") { SECTION("empty graph operations") { vos_void_void_void g; @@ -657,9 +657,9 @@ TEST_CASE("voem edge cases", "[voem][edge-cases]") { // 15. Type Trait Tests //================================================================================================== -TEST_CASE("voem type traits", "[voem][traits]") { +TEST_CASE("vom type traits", "[vom][traits]") { SECTION("edge_type is correct") { - using traits = voem_graph_traits; + using traits = vom_graph_traits; using edge_t = traits::edge_type; static_assert(std::is_same_v); @@ -667,7 +667,7 @@ TEST_CASE("voem type traits", "[voem][traits]") { } SECTION("edges_type is std::set") { - using traits = voem_graph_traits; + using traits = vom_graph_traits; using edges_t = traits::edges_type; // Verify it's a set by checking it has set-specific types @@ -675,8 +675,8 @@ TEST_CASE("voem type traits", "[voem][traits]") { } SECTION("sourced trait") { - using traits_unsourced = voem_graph_traits; - using traits_sourced = voem_graph_traits; + using traits_unsourced = vom_graph_traits; + using traits_sourced = vom_graph_traits; static_assert(traits_unsourced::sourced == false); static_assert(traits_sourced::sourced == true); @@ -687,7 +687,7 @@ TEST_CASE("voem type traits", "[voem][traits]") { // 16. Complex Graph Structure Tests //================================================================================================== -TEST_CASE("voem complex structures", "[voem][complex]") { +TEST_CASE("vom complex structures", "[vom][complex]") { SECTION("complete graph K4") { std::vector ee; for (uint32_t i = 0; i < 4; ++i) { diff --git a/tests/container/dynamic_graph/test_dynamic_graph_voum.cpp b/tests/container/dynamic_graph/test_dynamic_graph_voum.cpp new file mode 100644 index 0000000..77488fe --- /dev/null +++ b/tests/container/dynamic_graph/test_dynamic_graph_voum.cpp @@ -0,0 +1,738 @@ +/** + * @file test_dynamic_graph_voum.cpp + * @brief Comprehensive tests for dynamic_graph with vector vertices + unordered_map edges + * + * Tests voum_graph_traits (vector vertices + unordered_map edges) + * + * Key characteristics of std::unordered_map edges: + * - Automatic deduplication (only one edge per target vertex) + * - Edges stored in unordered fashion (hash-bucket order, not sorted) + * - O(1) average edge insertion, lookup, and deletion + * - Forward iterators only (no bidirectional or random access) + * - Requires std::hash and operator== on VId + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace graph::container; + +// Type aliases for common test configurations +using voum_void_void_void = + dynamic_graph>; +using voum_int_void_void = + dynamic_graph>; +using voum_void_int_void = + dynamic_graph>; +using voum_int_int_void = + dynamic_graph>; +using voum_void_void_int = + dynamic_graph>; +using voum_int_int_int = + dynamic_graph>; + +using voum_string_string_string = + dynamic_graph>; + +using voum_sourced = + dynamic_graph>; +using voum_int_sourced = + dynamic_graph>; + +// Edge and vertex data types for loading +using edge_void = copyable_edge_t; +using edge_int = copyable_edge_t; +using vertex_int = copyable_vertex_t; + +// Helper function to count total edges in graph +template +size_t count_all_edges(G& g) { + size_t count = 0; + for (auto& v : g) { + count += static_cast(std::ranges::distance(v.edges())); + } + return count; +} + +//================================================================================================== +// 1. Construction Tests +//================================================================================================== + +TEST_CASE("voum default construction", "[voum][construction]") { + SECTION("creates empty graph") { + voum_void_void_void g; + REQUIRE(g.size() == 0); + } + + SECTION("with void types") { + voum_void_void_void g; + REQUIRE(g.size() == 0); + } + + SECTION("with int edge values") { + voum_int_void_void g; + REQUIRE(g.size() == 0); + } + + SECTION("with int vertex values") { + voum_void_int_void g; + REQUIRE(g.size() == 0); + } + + SECTION("with int graph value") { + voum_void_void_int g; + REQUIRE(g.size() == 0); + } + + SECTION("with all int values") { + voum_int_int_int g; + REQUIRE(g.size() == 0); + } + + SECTION("with string values") { + voum_string_string_string g; + REQUIRE(g.size() == 0); + } +} + +TEST_CASE("voum constructor with graph value", "[voum][construction]") { + SECTION("void GV - no graph value can be passed") { + voum_void_void_void g; + REQUIRE(g.size() == 0); + } + + SECTION("int GV") { + voum_void_void_int g(42); + REQUIRE(g.size() == 0); + REQUIRE(g.graph_value() == 42); + } +} + +//================================================================================================== +// 2. Load Edges Tests +//================================================================================================== + +TEST_CASE("voum load_edges", "[voum][load]") { + SECTION("simple edges") { + voum_void_void_void g; + std::vector ee = {{0, 1}, {0, 2}, {1, 2}}; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 3); + REQUIRE(count_all_edges(g) == 3); + } + + SECTION("edges with vertex count") { + voum_void_void_void g; + std::vector ee = {{0, 1}, {1, 2}}; + g.load_edges(ee, std::identity{}, 6); // Request 6 vertices + + REQUIRE(g.size() == 6); // 0 through 5 + REQUIRE(count_all_edges(g) == 2); + } + + SECTION("edges with values") { + voum_int_void_void g; + std::vector ee = {{0, 1, 100}, {0, 2, 200}}; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 3); + REQUIRE(count_all_edges(g) == 2); + + // Collect edge values (order is unspecified for unordered_map) + auto& v0 = g[0]; + std::unordered_map edge_vals; + for (const auto& edge : v0.edges()) { + edge_vals[edge.second.target_id()] = edge.second.value(); + } + REQUIRE(edge_vals[1] == 100); + REQUIRE(edge_vals[2] == 200); + } +} + +//================================================================================================== +// 3. Initializer List Construction Tests +//================================================================================================== + +TEST_CASE("voum initializer list construction", "[voum][construction][initializer_list]") { + SECTION("simple initializer list") { + voum_void_void_void g({{0, 1}, {0, 2}, {1, 2}}); + + REQUIRE(g.size() == 3); + REQUIRE(count_all_edges(g) == 3); + } +} + +//================================================================================================== +// 4. Deduplication Tests (unordered_map guarantees unique keys) +//================================================================================================== + +TEST_CASE("voum edge deduplication", "[voum][unordered_map][deduplication]") { + SECTION("duplicate edges are ignored - unsourced") { + voum_void_void_void g; + // Load edges with duplicates + std::vector ee = { + {0, 1}, {0, 1}, {0, 1}, // Three identical edges + {0, 2}, {0, 2}, // Two identical edges + {1, 2} // One unique edge + }; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 3); + // Deduplication: only 3 unique edges should exist + REQUIRE(count_all_edges(g) == 3); + + // Verify each vertex has correct number of edges + auto& v0 = g[0]; + auto& v1 = g[1]; + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 2); // 0->1, 0->2 + REQUIRE(std::distance(v1.edges().begin(), v1.edges().end()) == 1); // 1->2 + } + + SECTION("duplicate edges with different values - first value wins") { + voum_int_void_void g; + std::vector ee = { + {0, 1, 100}, {0, 1, 200}, {0, 1, 300} // Same edge, different values + }; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 2); + REQUIRE(count_all_edges(g) == 1); // Only one edge stored + + // First inserted value should be kept + auto& v0 = g[0]; + REQUIRE(v0.edges().begin()->second.value() == 100); + } + + SECTION("sourced edges - deduplication by (source_id, target_id)") { + voum_sourced g; + std::vector ee = { + {0, 1}, + {0, 1}, // Duplicates + {1, 0}, + {1, 0} // Different direction, also duplicates + }; + g.load_edges(ee, std::identity{}); + + // Should have exactly 2 unique edges (0->1 and 1->0) + REQUIRE(count_all_edges(g) == 2); + } +} + +//================================================================================================== +// 5. Unordered Behavior Tests +//================================================================================================== + +TEST_CASE("voum edges are unordered", "[voum][unordered_map][unordered]") { + SECTION("all target_ids are present regardless of order") { + voum_void_void_void g; + std::vector ee = {{0, 5}, {0, 2}, {0, 8}, {0, 1}, {0, 3}}; + g.load_edges(ee, std::identity{}); + + // Collect all target_ids (order is unspecified) + auto& v0 = g[0]; + std::set target_ids; + for (const auto& edge : v0.edges()) { + target_ids.insert(edge.second.target_id()); + } + + REQUIRE(target_ids == std::set{1, 2, 3, 5, 8}); + } + + SECTION("sourced edges - all target_ids present") { + voum_sourced g; + std::vector ee = {{0, 7}, {0, 3}, {0, 9}, {0, 1}}; + g.load_edges(ee, std::identity{}); + + auto& v0 = g[0]; + std::set target_ids; + for (const auto& edge : v0.edges()) { + target_ids.insert(edge.second.target_id()); + } + + REQUIRE(target_ids == std::set{1, 3, 7, 9}); + } +} + +//================================================================================================== +// 6. Vertex Access Tests +//================================================================================================== + +TEST_CASE("voum vertex access", "[voum][vertex][access]") { + SECTION("operator[] access") { + voum_void_void_void g({{0, 1}, {1, 2}, {2, 3}}); + + REQUIRE(g.size() == 4); + // Access each vertex + auto& v0 = g[0]; + auto& v1 = g[1]; + auto& v2 = g[2]; + auto& v3 = g[3]; + + // Verify edge counts + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 1); + REQUIRE(std::distance(v1.edges().begin(), v1.edges().end()) == 1); + REQUIRE(std::distance(v2.edges().begin(), v2.edges().end()) == 1); + REQUIRE(std::distance(v3.edges().begin(), v3.edges().end()) == 0); + } + + SECTION("const operator[] access") { + const voum_void_void_void g({{0, 1}, {1, 2}}); + + const auto& v0 = g[0]; + const auto& v1 = g[1]; + + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 1); + REQUIRE(std::distance(v1.edges().begin(), v1.edges().end()) == 1); + } +} + +TEST_CASE("voum vertex iteration", "[voum][vertex][iteration]") { + SECTION("range-based for") { + voum_void_void_void g({{0, 1}, {1, 2}, {2, 0}}); + + size_t count = 0; + for (const auto& vertex : g) { + (void)vertex; + ++count; + } + REQUIRE(count == 3); + } + + SECTION("begin/end iteration") { + voum_void_void_void g({{0, 1}, {1, 2}}); + + auto it = g.begin(); + REQUIRE(it != g.end()); + ++it; + REQUIRE(it != g.end()); + ++it; + REQUIRE(it != g.end()); + ++it; + REQUIRE(it == g.end()); + } +} + +//================================================================================================== +// 7. Edge Access Tests +//================================================================================================== + +TEST_CASE("voum edge access", "[voum][edge][access]") { + SECTION("edges() returns unordered_map") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + auto& edge_map = v0.edges(); + + REQUIRE(std::distance(edge_map.begin(), edge_map.end()) == 3); + } + + SECTION("edge target_id access") { + voum_void_void_void g({{0, 5}}); + + auto& v0 = g[0]; + auto it = v0.edges().begin(); + REQUIRE(it->second.target_id() == 5); + } + + SECTION("edge value access") { + voum_int_void_void g; + std::vector ee = {{0, 1, 42}}; + g.load_edges(ee, std::identity{}); + + auto& v0 = g[0]; + auto it = v0.edges().begin(); + REQUIRE(it->second.value() == 42); + } +} + +TEST_CASE("voum edge forward iteration", "[voum][edge][iteration]") { + SECTION("forward iteration covers all edges") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + std::set targets; + for (const auto& edge : v0.edges()) { + targets.insert(edge.second.target_id()); + } + + REQUIRE(targets.size() == 3); + REQUIRE(targets == std::set{1, 2, 3}); + } + + // Note: No reverse iteration test — unordered_map only provides forward iterators +} + +//================================================================================================== +// 8. Vertex and Edge Value Tests +//================================================================================================== + +TEST_CASE("voum vertex values", "[voum][vertex][value]") { + SECTION("vertex value access") { + voum_void_int_void g; + std::vector vv = {{0, 100}, {1, 200}}; + g.load_vertices(vv, std::identity{}); + + std::vector ee = {{0, 1}}; + g.load_edges(ee, std::identity{}); + + REQUIRE(g[0].value() == 100); + REQUIRE(g[1].value() == 200); + } +} + +TEST_CASE("voum edge values", "[voum][edge][value]") { + SECTION("edge values preserved after deduplication") { + voum_int_void_void g; + std::vector ee = {{0, 1, 100}, {0, 2, 200}}; + g.load_edges(ee, std::identity{}); + + auto& v0 = g[0]; + std::unordered_map edge_vals; + for (const auto& edge : v0.edges()) { + edge_vals[edge.second.target_id()] = edge.second.value(); + } + REQUIRE(edge_vals[1] == 100); + REQUIRE(edge_vals[2] == 200); + } +} + +//================================================================================================== +// 9. Sourced Edge Tests +//================================================================================================== + +TEST_CASE("voum sourced edges", "[voum][sourced]") { + SECTION("source_id access") { + voum_sourced g({{0, 1}, {0, 2}, {1, 0}}); + + auto& v0 = g[0]; + for (const auto& edge : v0.edges()) { + REQUIRE(edge.second.source_id() == 0); + } + + auto& v1 = g[1]; + for (const auto& edge : v1.edges()) { + REQUIRE(edge.second.source_id() == 1); + } + } + + SECTION("sourced edge with values") { + voum_int_sourced g; + std::vector ee = {{0, 1, 100}, {1, 0, 200}}; + g.load_edges(ee, std::identity{}); + + // Verify edges from vertex 0 + auto& v0 = g[0]; + auto it0 = v0.edges().begin(); + REQUIRE(it0->second.source_id() == 0); + REQUIRE(it0->second.target_id() == 1); + REQUIRE(it0->second.value() == 100); + + // Verify edges from vertex 1 + auto& v1 = g[1]; + auto it1 = v1.edges().begin(); + REQUIRE(it1->second.source_id() == 1); + REQUIRE(it1->second.target_id() == 0); + REQUIRE(it1->second.value() == 200); + } +} + +//================================================================================================== +// 10. Self-Loop Tests +//================================================================================================== + +TEST_CASE("voum self-loops", "[voum][self-loop]") { + SECTION("single self-loop") { + voum_void_void_void g({{0, 0}}); + + REQUIRE(g.size() == 1); + REQUIRE(count_all_edges(g) == 1); + + auto& v0 = g[0]; + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 1); + REQUIRE(v0.edges().begin()->second.target_id() == 0); + } + + SECTION("self-loop deduplication") { + voum_void_void_void g({{0, 0}, {0, 0}, {0, 0}}); + + // Only one self-loop should exist + REQUIRE(count_all_edges(g) == 1); + } + + SECTION("self-loop with outgoing edges") { + voum_void_void_void g({{0, 0}, {0, 1}, {0, 2}}); + + REQUIRE(count_all_edges(g) == 3); + + auto& v0 = g[0]; + std::set targets; + for (const auto& edge : v0.edges()) { + targets.insert(edge.second.target_id()); + } + + REQUIRE(targets == std::set{0, 1, 2}); + } +} + +//================================================================================================== +// 11. Large Graph Tests +//================================================================================================== + +TEST_CASE("voum large graph", "[voum][performance]") { + SECTION("1000 vertices linear chain") { + std::vector ee; + for (uint32_t i = 0; i < 999; ++i) { + ee.push_back({i, i + 1}); + } + + voum_void_void_void g; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 1000); + REQUIRE(count_all_edges(g) == 999); + } + + SECTION("star graph with 100 spokes") { + std::vector ee; + for (uint32_t i = 1; i <= 100; ++i) { + ee.push_back({0, i}); + } + + voum_void_void_void g; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 101); + REQUIRE(count_all_edges(g) == 100); + + // Vertex 0 should have all 100 edges + auto& v0 = g[0]; + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 100); + } +} + +//================================================================================================== +// 12. Iterator Tests (forward only for unordered_map) +//================================================================================================== + +TEST_CASE("voum forward iterator behavior", "[voum][unordered_map][iterator]") { + SECTION("edge iterators are forward") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + auto& edge_map = v0.edges(); + + // Forward iteration + std::set visited; + for (auto it = edge_map.begin(); it != edge_map.end(); ++it) { + visited.insert(it->second.target_id()); + } + REQUIRE(visited == std::set{1, 2, 3}); + } + + SECTION("find by key") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + auto& edge_map = v0.edges(); + + // unordered_map supports O(1) average find + auto it = edge_map.find(2); + REQUIRE(it != edge_map.end()); + REQUIRE(it->second.target_id() == 2); + + auto it_miss = edge_map.find(99); + REQUIRE(it_miss == edge_map.end()); + } +} + +//================================================================================================== +// 13. Algorithm Compatibility Tests +//================================================================================================== + +TEST_CASE("voum algorithm compatibility", "[voum][algorithm]") { + SECTION("std::ranges::for_each on vertices") { + voum_void_void_void g({{0, 1}, {1, 2}, {2, 0}}); + + size_t count = 0; + std::ranges::for_each(g, [&count](const auto& v) { + (void)v; + ++count; + }); + + REQUIRE(count == 3); + } + + SECTION("std::ranges::for_each on edges") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + size_t count = 0; + std::ranges::for_each(v0.edges(), [&count](const auto& e) { + (void)e; + ++count; + }); + + REQUIRE(count == 3); + } + + SECTION("std::find_if on edges") { + voum_int_void_void g; + std::vector ee = {{0, 1, 100}, {0, 2, 200}, {0, 3, 300}}; + g.load_edges(ee, std::identity{}); + + auto& v0 = g[0]; + auto it = std::ranges::find_if(v0.edges(), [](const auto& e) { return e.second.value() == 200; }); + + REQUIRE(it != v0.edges().end()); + REQUIRE(it->second.target_id() == 2); + } +} + +//================================================================================================== +// 14. Edge Case Tests +//================================================================================================== + +TEST_CASE("voum edge cases", "[voum][edge-cases]") { + SECTION("empty graph operations") { + voum_void_void_void g; + + REQUIRE(g.size() == 0); + REQUIRE(count_all_edges(g) == 0); + REQUIRE(g.begin() == g.end()); + } + + SECTION("single vertex no edges") { + voum_void_void_void g; + std::vector empty_edges; + g.load_edges(empty_edges, std::identity{}, 1); + + REQUIRE(g.size() == 1); + REQUIRE(count_all_edges(g) == 0); + + auto& v0 = g[0]; + REQUIRE(v0.edges().empty()); + } + + SECTION("vertices with no outgoing edges") { + voum_void_void_void g; + std::vector ee = {{0, 1}}; + g.load_edges(ee, std::identity{}, 6); + + REQUIRE(g.size() == 6); // 0 through 5 + + // Only vertex 0 has an edge + REQUIRE(std::distance(g[0].edges().begin(), g[0].edges().end()) == 1); + + // Vertices 2-5 have no outgoing edges + for (uint32_t i = 2; i <= 5; ++i) { + REQUIRE(g[i].edges().empty()); + } + } +} + +//================================================================================================== +// 15. Type Trait Tests +//================================================================================================== + +TEST_CASE("voum type traits", "[voum][traits]") { + SECTION("edge_type is correct") { + using traits = voum_graph_traits; + using edge_t = traits::edge_type; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + SECTION("edges_type is std::unordered_map") { + using traits = voum_graph_traits; + using edges_t = traits::edges_type; + + // Verify it's an unordered_map by checking it has key_type and hasher + static_assert(requires { typename edges_t::key_type; }); + static_assert(requires { typename edges_t::hasher; }); + } + + SECTION("sourced trait") { + using traits_unsourced = voum_graph_traits; + using traits_sourced = voum_graph_traits; + + static_assert(traits_unsourced::sourced == false); + static_assert(traits_sourced::sourced == true); + } +} + +//================================================================================================== +// 16. Complex Graph Structure Tests +//================================================================================================== + +TEST_CASE("voum complex structures", "[voum][complex]") { + SECTION("complete graph K4") { + std::vector ee; + for (uint32_t i = 0; i < 4; ++i) { + for (uint32_t j = 0; j < 4; ++j) { + if (i != j) { + ee.push_back({i, j}); + } + } + } + + voum_void_void_void g; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 4); + REQUIRE(count_all_edges(g) == 12); // 4 * 3 directed edges + + // Each vertex should have 3 outgoing edges + for (uint32_t i = 0; i < 4; ++i) { + REQUIRE(std::distance(g[i].edges().begin(), g[i].edges().end()) == 3); + } + } + + SECTION("cycle graph C5") { + voum_void_void_void g({{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 0}}); + + REQUIRE(g.size() == 5); + REQUIRE(count_all_edges(g) == 5); + } + + SECTION("binary tree depth 3") { + voum_void_void_void g({ + {0, 1}, + {0, 2}, // Level 1 + {1, 3}, + {1, 4}, // Level 2 left + {2, 5}, + {2, 6} // Level 2 right + }); + + REQUIRE(g.size() == 7); + REQUIRE(count_all_edges(g) == 6); + + // Root has 2 children + REQUIRE(std::distance(g[0].edges().begin(), g[0].edges().end()) == 2); + + // Internal nodes have 2 children each + REQUIRE(std::distance(g[1].edges().begin(), g[1].edges().end()) == 2); + REQUIRE(std::distance(g[2].edges().begin(), g[2].edges().end()) == 2); + + // Leaves have no children + for (uint32_t i = 3; i <= 6; ++i) { + REQUIRE(g[i].edges().empty()); + } + } +} diff --git a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp index 7291655..41f1a2b 100644 --- a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp +++ b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp @@ -1562,7 +1562,7 @@ TEST_CASE("erase_edge with iterator range", "[undirected_adjacency_list][edge][e /* TEST_CASE("graph with void vertex value", "[undirected_adjacency_list][void_types]") { - undirected_adjacency_list g; + undirected_adjacency_list g; g.create_vertex(); g.create_vertex(); g.create_edge(0, 1, 100); @@ -1577,7 +1577,7 @@ TEST_CASE("graph with void vertex value", "[undirected_adjacency_list][void_type } TEST_CASE("graph with void edge value", "[undirected_adjacency_list][void_types]") { - undirected_adjacency_list g; + undirected_adjacency_list g; g.create_vertex(10); g.create_vertex(20); g.create_edge(0, 1);