Skip to content

Commit 7b060e8

Browse files
authored
Vert patterns (#19)
* Fix bidirectional→forward iterator terminology in vertex patterns - descriptor.hpp: update pair_value_vertex_pattern and whole_value_vertex_pattern concepts to use std::forward_iterator instead of std::bidirectional_iterator, enabling unordered_map/set support; update all associated comments - vertex_descriptor.hpp: replace 'bidirectional' with 'forward' in all comments - graph_cpo.hpp: update three comments referring to 'bidirectional containers' to say 'forward containers' - vertex-patterns.md: fix iterator requirements in Inner Value Patterns table; add Foundation Concepts section; add vertex_pattern_type and vertex_id_type to Storage Detection Traits table; fix vertex_id() return type (auto → decltype(auto)); add vertex_descriptor and vertex_descriptor_view class references; fix namespace (adj_list::detail → adj_list) - concepts.md: keyed_vertex_type description: Bidirectional → Forward - archive/descriptor.md: replace bidirectional iterator spec with forward iterator for key-value vertex storage * feat: add index_iterator alias and index_vertex_descriptor types - vertex_descriptor.hpp: add index_iterator (iota_view iterator) and index_vertex_descriptor type aliases for index-only graphs - vertex_descriptor_view.hpp: add index_vertex_descriptor_view alias - test_vertex_descriptor.cpp: add 6 tests covering type properties, default construction, hashing, view iteration, empty range, and non-zero start offset * feat: constrain inner_value/underlying_value to container-backed descriptors - descriptor.hpp: add index_only_vertex and container_backed_vertex concepts using C++20 iter_value_t/iter_reference_t traits; index-only iterators (like iota_view) return by value (non-reference), distinguishing them from container iterators over integral types - vertex_descriptor.hpp: add requires clauses to all four inner_value/underlying_value overloads preventing use on index-only descriptors - test_vertex_descriptor.cpp: add concept verification and container-backed descriptor positive tests * refactor: migrate compressed_graph to index_iterator - Replace conditional_t<const/non-const, row_index_vector::iterator> with index_iterator in vertices(), vertices(g, pid), find_vertex(), and edges() friend functions - Add 'using adj_list::index_iterator' to container namespace imports - The const/non-const distinction is irrelevant for index-only descriptors that only store size_t * feat: export index vertex types from graph.hpp - index_iterator, index_vertex_descriptor, index_vertex_descriptor_view - index_only_vertex, container_backed_vertex concepts
1 parent 0d574b1 commit 7b060e8

11 files changed

Lines changed: 726 additions & 53 deletions

File tree

agents/index_vertex_descriptor_plan.md

Lines changed: 403 additions & 0 deletions
Large diffs are not rendered by default.

docs/archive/descriptor.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,25 +110,25 @@ Edges in the graph can be stored in various ways depending on graph structure:
110110
- MUST be a template with a single parameter for the underlying container's iterator type
111111
- Iterator category constraints:
112112
- **Random Access Iterator** (e.g., vector, deque): Used for index-based vertex storage
113-
- **Bidirectional Iterator** (e.g., map, unordered_map): Used for key-value based vertex storage
114-
- When bidirectional: the iterator's `value_type` MUST satisfy a pair-like concept where:
113+
- **Forward Iterator** (e.g., map, unordered_map, unordered_set): Used for key-value based vertex storage
114+
- When forward (non-random-access): the iterator's `value_type` MUST satisfy a pair-like concept where:
115115
- The type MUST have at least 2 members (accessible via tuple protocol or pair interface)
116116
- The first element serves as the vertex ID (key)
117117
- This can be checked using `std::tuple_size<value_type>::value >= 2` or by requiring `.first` and `.second` members
118118
- MUST have a single member variable that:
119119
- MUST be `size_t index` when the iterator is a random access iterator
120-
- MUST be the iterator type itself when the iterator is a bidirectional iterator (non-random access)
120+
- MUST be the iterator type itself when the iterator is a forward iterator (non-random access)
121121
- MUST provide a `vertex_id()` member function that returns the vertex's unique identifier:
122122
- When the vertex iterator is random access: MUST return the `size_t` member value (the index)
123-
- When the vertex iterator is bidirectional: MUST return the key (first element) from the pair-like `value_type`
123+
- When the vertex iterator is forward (non-random-access): MUST return the key (first element) from the pair-like `value_type`
124124
- Return type SHOULD be deduced appropriately based on iterator category
125125
- MUST provide a public `value()` member function that returns the underlying storage handle:
126126
- When the vertex iterator is random access: `value()` MUST return the stored `size_t` index
127-
- When the vertex iterator is bidirectional: `value()` MUST return the stored iterator
127+
- When the vertex iterator is forward (non-random-access): `value()` MUST return the stored iterator
128128
- Return type SHOULD be the exact type of the underlying member (copy by value)
129129
- MUST provide pre-increment and post-increment operators (`operator++` / `operator++(int)`) whose behavior mirrors the underlying storage:
130130
- For random access iterators: increment operations MUST advance the `size_t` index by one
131-
- For bidirectional iterators: increment operations MUST advance the stored iterator
131+
- For forward iterators: increment operations MUST advance the stored iterator
132132
- MUST be efficiently passable by value
133133
- SHOULD support conversion to/from underlying index type (for random access case)
134134
- MUST integrate with std::hash for unordered containers

docs/reference/concepts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ for full documentation.
285285
| Concept | Purpose |
286286
|---------|---------|
287287
| `direct_vertex_type<Iter>` | Random-access, index-based vertex |
288-
| `keyed_vertex_type<Iter>` | Bidirectional, key-based vertex |
288+
| `keyed_vertex_type<Iter>` | Forward, key-based vertex |
289289
| `vertex_iterator<Iter>` | Either direct or keyed |
290290
| `random_access_vertex_pattern<Iter>` | Inner value: return whole element |
291291
| `pair_value_vertex_pattern<Iter>` | Inner value: return `.second` |

docs/reference/vertex-patterns.md

Lines changed: 120 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
> This page documents two complementary concept families for vertex types:
99
**inner value patterns** (how vertex data is accessed) and
1010
**storage patterns** (how vertex IDs are stored/extracted). Both families live
11-
in `graph::adj_list::detail` and are re-exported via `<graph/graph.hpp>`.
11+
in `graph::adj_list` and are re-exported via `<graph/graph.hpp>`.
1212

1313
</td>
1414
</tr></table>
@@ -17,6 +17,25 @@ in `graph::adj_list::detail` and are re-exported via `<graph/graph.hpp>`.
1717
1818
---
1919

20+
## Foundation Concepts
21+
22+
These building-block concepts determine whether a type is "pair-like" and
23+
are used internally by both the inner value and storage pattern concepts.
24+
25+
| Concept | Definition |
26+
|---------|------------|
27+
| `pair_like<T>` | Has `std::tuple_size<T>::value >= 2` and supports `std::get<0>`, `std::get<1>` (tuple protocol) |
28+
| `has_first_second<T>` | Has `.first` and `.second` members |
29+
| `pair_like_value<T>` | `pair_like<T> \|\| has_first_second<T>` — disjunction of both |
30+
31+
```cpp
32+
// Used to constrain keyed_vertex_type, pair_value_vertex_pattern, etc.
33+
template <typename T>
34+
concept pair_like_value = pair_like<T> || has_first_second<T>;
35+
```
36+
37+
---
38+
2039
## Inner Value Patterns
2140

2241
The `inner_value()` method on a vertex descriptor returns the vertex data
@@ -33,8 +52,8 @@ container type.
3352
| Concept | Iterator Requirements | `inner_value()` Returns | Typical Containers |
3453
|---------|----------------------|------------------------|--------------------|
3554
| `random_access_vertex_pattern<Iter>` | Random-access iterator | `container[index]` — the whole element | `std::vector`, `std::deque` |
36-
| `pair_value_vertex_pattern<Iter>` | Bidirectional, pair-like value type | `.second` — the mapped value | `std::map`, `std::unordered_map` |
37-
| `whole_value_vertex_pattern<Iter>` | Bidirectional, non-pair value type | `*iter` — the whole dereferenced value | Custom bidirectional containers |
55+
| `pair_value_vertex_pattern<Iter>` | Forward, pair-like value type | `.second` — the mapped value | `std::map`, `std::unordered_map` |
56+
| `whole_value_vertex_pattern<Iter>` | Forward, non-pair value type | `*iter` — the whole dereferenced value | Custom forward containers |
3857

3958
```cpp
4059
// Disjunction of all three
@@ -104,7 +123,7 @@ stored and extracted:
104123
| Concept | Iterator Requirements | Vertex ID Type | Access Pattern |
105124
|---------|----------------------|---------------|----------------|
106125
| `direct_vertex_type<Iter>` | Random-access | `std::size_t` (the index) | `container[index]` |
107-
| `keyed_vertex_type<Iter>` | Bidirectional, pair-like value | Key type (`.first`) | `(*iter).first` for ID, `.second` for data |
126+
| `keyed_vertex_type<Iter>` | Forward, pair-like value | Key type (`.first`) | `(*iter).first` for ID, `.second` for data |
108127

109128
```cpp
110129
// Disjunction
@@ -123,8 +142,10 @@ matches exactly one.
123142
| `vertex_storage_pattern<Iter>` | Struct with `::is_direct`, `::is_keyed` booleans |
124143
| `vertex_storage_pattern_v<Iter>` | Variable template shortcut |
125144
| `vertex_pattern` (enum) | `direct`, `keyed` |
145+
| `vertex_pattern_type<Iter>` | Struct with `::value` of type `vertex_pattern` |
126146
| `vertex_pattern_type_v<Iter>` | Variable template returning the enum value |
127-
| `vertex_id_type_t<Iter>` | Extracts the vertex ID type: `size_t` for direct, key type for keyed |
147+
| `vertex_id_type<Iter>` | Struct with `::type` alias; specializations for direct (`.first`/`.second`) and keyed (tuple protocol) |
148+
| `vertex_id_type_t<Iter>` | Alias for `vertex_id_type<Iter>::type`: `size_t` for direct, key type for keyed |
128149

129150
### Examples
130151

@@ -146,11 +167,11 @@ static_assert(std::same_as<vertex_id_type_t<MapIter>, std::string>);
146167
The `vertex_id()` function in `vertex_descriptor` dispatches at compile time:
147168

148169
```cpp
149-
constexpr auto vertex_id() const noexcept {
170+
constexpr decltype(auto) vertex_id() const noexcept {
150171
if constexpr (std::random_access_iterator<VertexIter>) {
151172
return storage_; // direct: return index (size_t)
152173
} else {
153-
return std::get<0>(*storage_); // keyed: extract key
174+
return std::get<0>(*storage_); // keyed: return const& to key
154175
}
155176
}
156177
```
@@ -172,6 +193,98 @@ Both families work together to give `vertex_descriptor` complete type safety:
172193
173194
---
174195
196+
## `vertex_descriptor<VertexIter>` Class Reference
197+
198+
Defined in `<graph/adj_list/vertex_descriptor.hpp>`.
199+
A lightweight, type-safe handle to a vertex stored in a container.
200+
Constrained by `vertex_iterator<VertexIter>`.
201+
202+
### Type Aliases
203+
204+
| Alias | Definition |
205+
|-------|------------|
206+
| `iterator_type` | `VertexIter` |
207+
| `value_type` | `typename std::iterator_traits<VertexIter>::value_type` |
208+
| `storage_type` | `std::size_t` for random-access iterators, `VertexIter` otherwise |
209+
210+
### Constructors
211+
212+
| Signature | Notes |
213+
|-----------|-------|
214+
| `constexpr vertex_descriptor() noexcept` | Default; requires `std::default_initializable<storage_type>` |
215+
| `constexpr explicit vertex_descriptor(storage_type val) noexcept` | Construct from index or iterator |
216+
217+
### Member Functions
218+
219+
| Function | Returns | Description |
220+
|----------|---------|-------------|
221+
| `value()` | `storage_type` | The raw storage: index (`size_t`) or iterator |
222+
| `vertex_id()` | `decltype(auto)` | Vertex ID — index by value for direct, `const&` to key for keyed |
223+
| `underlying_value(Container&)` | `decltype(auto)` | Full container element (`container[i]` or `*iter`); const overload provided |
224+
| `inner_value(Container&)` | `decltype(auto)` | Vertex data excluding the key (`.second` for maps, whole value for vectors); const overload provided |
225+
| `operator++()` / `operator++(int)` | `vertex_descriptor&` / `vertex_descriptor` | Pre/post-increment |
226+
| `operator<=>` / `operator==` | | Defaulted three-way and equality comparison |
227+
228+
### `std::hash` Specialization
229+
230+
A `std::hash<vertex_descriptor<VertexIter>>` specialization is provided,
231+
hashing the index for direct storage or the `vertex_id()` for keyed storage.
232+
This enables use in `std::unordered_set` and `std::unordered_map`.
233+
234+
---
235+
236+
## `vertex_descriptor_view<VertexIter>` Class Reference
237+
238+
Defined in `<graph/adj_list/vertex_descriptor_view.hpp>`.
239+
A forward-only view over vertex storage that synthesizes `vertex_descriptor`
240+
objects on-the-fly. Inherits from `std::ranges::view_interface`.
241+
242+
### Type Aliases
243+
244+
| Alias | Definition |
245+
|-------|------------|
246+
| `vertex_desc` | `vertex_descriptor<VertexIter>` |
247+
| `storage_type` | `typename vertex_desc::storage_type` |
248+
249+
### Constructors
250+
251+
| Signature | Notes |
252+
|-----------|-------|
253+
| `constexpr vertex_descriptor_view() noexcept` | Default |
254+
| `constexpr vertex_descriptor_view(storage_type begin, storage_type end) noexcept` | From index/iterator range; requires random-access |
255+
| `constexpr explicit vertex_descriptor_view(Container&) noexcept` | From mutable container; requires `sized_range` or random-access |
256+
| `constexpr explicit vertex_descriptor_view(const Container&) noexcept` | From const container |
257+
258+
### Member Functions
259+
260+
| Function | Description |
261+
|----------|-------------|
262+
| `begin()` / `end()` | Forward iterators yielding `vertex_descriptor` by value |
263+
| `cbegin()` / `cend()` | Const equivalents |
264+
| `size()` | O(1) — cached at construction |
265+
266+
### Inner `iterator` Class
267+
268+
| Property | Value |
269+
|----------|-------|
270+
| `iterator_category` | `std::forward_iterator_tag` |
271+
| `value_type` | `vertex_descriptor<VertexIter>` |
272+
| Dereference | Returns descriptor **by value** (synthesized) |
273+
274+
### Deduction Guides
275+
276+
```cpp
277+
vertex_descriptor_view(Container&) -> vertex_descriptor_view<typename Container::iterator>;
278+
vertex_descriptor_view(const Container&) -> vertex_descriptor_view<typename Container::const_iterator>;
279+
```
280+
281+
### Range Traits
282+
283+
`std::ranges::enable_borrowed_range` is specialized to `true` — the view
284+
does not own the underlying data.
285+
286+
---
287+
175288
## Compile-Time Dispatch Pattern
176289

177290
```cpp

include/graph/adj_list/descriptor.hpp

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct in_edge_tag {};
2020
/**
2121
* @brief Concept to check if a type is pair-like (has at least 2 members accessible via tuple protocol)
2222
*
23-
* This is used to constrain bidirectional iterator value_types for vertex storage.
23+
* This is used to constrain forward iterator value_types for vertex storage.
2424
*/
2525
template <typename T>
2626
concept pair_like = requires {
@@ -89,7 +89,7 @@ concept keyed_vertex_type = std::forward_iterator<Iter> && !std::random_access_i
8989
*
9090
* A vertex iterator must be either:
9191
* - Random access (direct/indexed storage)
92-
* - Bidirectional with pair-like value_type (keyed storage)
92+
* - Forward with pair-like value_type (keyed storage)
9393
*/
9494
template <typename Iter>
9595
concept vertex_iterator = direct_vertex_type<Iter> || keyed_vertex_type<Iter>;
@@ -111,25 +111,25 @@ concept random_access_vertex_pattern = std::random_access_iterator<Iter>;
111111
/**
112112
* @brief Concept for pair-value vertex pattern (map-like)
113113
*
114-
* Used with bidirectional iterators where the value_type is pair-like.
114+
* Used with forward iterators where the value_type is pair-like.
115115
* inner_value returns the .second part (the data, excluding the key).
116116
* Pattern: *iterator -> pair{key, data}, inner_value returns data&
117-
* Example: std::map<int, VertexData> where inner_value returns VertexData& (.second)
117+
* Example: std::map<int, VertexData> or std::unordered_map<int, VertexData> where inner_value returns VertexData& (.second)
118118
*/
119119
template <typename Iter>
120-
concept pair_value_vertex_pattern = std::bidirectional_iterator<Iter> && !std::random_access_iterator<Iter> &&
120+
concept pair_value_vertex_pattern = std::forward_iterator<Iter> && !std::random_access_iterator<Iter> &&
121121
pair_like_value<typename std::iterator_traits<Iter>::value_type>;
122122

123123
/**
124-
* @brief Concept for whole-value vertex pattern (custom bidirectional)
124+
* @brief Concept for whole-value vertex pattern (custom forward)
125125
*
126-
* Used with bidirectional iterators where the value_type is NOT pair-like.
126+
* Used with forward iterators where the value_type is NOT pair-like.
127127
* inner_value returns the entire element (same as underlying_value).
128128
* Pattern: *iterator -> value, inner_value returns value&
129-
* Example: Custom bidirectional container with non-pair value_type
129+
* Example: Custom forward container with non-pair value_type
130130
*/
131131
template <typename Iter>
132-
concept whole_value_vertex_pattern = std::bidirectional_iterator<Iter> && !std::random_access_iterator<Iter> &&
132+
concept whole_value_vertex_pattern = std::forward_iterator<Iter> && !std::random_access_iterator<Iter> &&
133133
!pair_like_value<typename std::iterator_traits<Iter>::value_type>;
134134

135135
/**
@@ -274,6 +274,36 @@ template <typename Iter>
274274
requires vertex_iterator<Iter>
275275
using vertex_id_type_t = typename vertex_id_type<Iter>::type;
276276

277+
// =============================================================================
278+
// Index-Only Vertex Detection
279+
// =============================================================================
280+
281+
/**
282+
* @brief Concept for index-only vertex iterators (no physical container backing)
283+
*
284+
* An index-only vertex iterator is a random-access iterator whose value_type
285+
* is integral AND whose reference type is not an actual reference (i.e., it
286+
* returns by value, like iota_view iterators). This distinguishes generated
287+
* index sequences from container iterators over integral types (e.g.,
288+
* vector<int>::iterator has reference = int&, so it is NOT index-only).
289+
*
290+
* For these iterators, inner_value() and underlying_value() are not meaningful
291+
* because there is no physical container to index into.
292+
*/
293+
template <typename Iter>
294+
concept index_only_vertex = std::random_access_iterator<Iter> &&
295+
std::integral<std::iter_value_t<Iter>> &&
296+
!std::is_reference_v<std::iter_reference_t<Iter>>;
297+
298+
/**
299+
* @brief Concept for container-backed vertex iterators
300+
*
301+
* The inverse of index_only_vertex. These iterators reference elements in
302+
* a physical container, so inner_value() and underlying_value() are valid.
303+
*/
304+
template <typename Iter>
305+
concept container_backed_vertex = vertex_iterator<Iter> && !index_only_vertex<Iter>;
306+
277307
// =============================================================================
278308
// Edge Value Type Concepts for target_id() Extraction
279309
// =============================================================================

include/graph/adj_list/detail/graph_cpo.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ namespace _cpo_impls {
343343
* The descriptor default works for standard containers:
344344
* - Random-access containers (vector, deque): returns the index
345345
* - Associative containers (map): returns the key
346-
* - Bidirectional containers: returns the iterator position
346+
* - Forward containers: returns the iterator position
347347
*
348348
* @tparam G Graph type
349349
* @tparam U Vertex descriptor type (constrained to be a vertex_descriptor_type)
@@ -391,7 +391,7 @@ inline namespace _cpo_instances {
391391
* The type of the unique identifier returned by vertex_id(g, u), with references stripped.
392392
* - For random-access containers (vector, deque): size_t (index)
393393
* - For associative containers (map): key type (e.g. std::string)
394-
* - For bidirectional containers: iterator-based ID
394+
* - For forward containers: iterator-based ID
395395
*/
396396
template <typename G>
397397
using vertex_id_t = std::remove_cvref_t<
@@ -3005,7 +3005,7 @@ namespace _cpo_impls {
30053005
* - Uses u.inner_value(g) which returns the actual vertex data
30063006
* - For random-access containers (vector): returns container[index]
30073007
* - For associative containers (map): returns the .second value
3008-
* - For bidirectional containers: returns the dereferenced value
3008+
* - For forward containers: returns the dereferenced value
30093009
*
30103010
* This provides access to user-defined vertex properties/data stored in the graph.
30113011
*

0 commit comments

Comments
 (0)