Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
403 changes: 403 additions & 0 deletions agents/index_vertex_descriptor_plan.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions docs/archive/descriptor.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,25 +110,25 @@ Edges in the graph can be stored in various ways depending on graph structure:
- MUST be a template with a single parameter for the underlying container's iterator type
- Iterator category constraints:
- **Random Access Iterator** (e.g., vector, deque): Used for index-based vertex storage
- **Bidirectional Iterator** (e.g., map, unordered_map): Used for key-value based vertex storage
- When bidirectional: the iterator's `value_type` MUST satisfy a pair-like concept where:
- **Forward Iterator** (e.g., map, unordered_map, unordered_set): Used for key-value based vertex storage
- When forward (non-random-access): the iterator's `value_type` MUST satisfy a pair-like concept where:
- The type MUST have at least 2 members (accessible via tuple protocol or pair interface)
- The first element serves as the vertex ID (key)
- This can be checked using `std::tuple_size<value_type>::value >= 2` or by requiring `.first` and `.second` members
- MUST have a single member variable that:
- MUST be `size_t index` when the iterator is a random access iterator
- MUST be the iterator type itself when the iterator is a bidirectional iterator (non-random access)
- MUST be the iterator type itself when the iterator is a forward iterator (non-random access)
- MUST provide a `vertex_id()` member function that returns the vertex's unique identifier:
- When the vertex iterator is random access: MUST return the `size_t` member value (the index)
- When the vertex iterator is bidirectional: MUST return the key (first element) from the pair-like `value_type`
- When the vertex iterator is forward (non-random-access): MUST return the key (first element) from the pair-like `value_type`
- Return type SHOULD be deduced appropriately based on iterator category
- MUST provide a public `value()` member function that returns the underlying storage handle:
- When the vertex iterator is random access: `value()` MUST return the stored `size_t` index
- When the vertex iterator is bidirectional: `value()` MUST return the stored iterator
- When the vertex iterator is forward (non-random-access): `value()` MUST return the stored iterator
- Return type SHOULD be the exact type of the underlying member (copy by value)
- MUST provide pre-increment and post-increment operators (`operator++` / `operator++(int)`) whose behavior mirrors the underlying storage:
- For random access iterators: increment operations MUST advance the `size_t` index by one
- For bidirectional iterators: increment operations MUST advance the stored iterator
- For forward iterators: increment operations MUST advance the stored iterator
- MUST be efficiently passable by value
- SHOULD support conversion to/from underlying index type (for random access case)
- MUST integrate with std::hash for unordered containers
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ for full documentation.
| Concept | Purpose |
|---------|---------|
| `direct_vertex_type<Iter>` | Random-access, index-based vertex |
| `keyed_vertex_type<Iter>` | Bidirectional, key-based vertex |
| `keyed_vertex_type<Iter>` | Forward, key-based vertex |
| `vertex_iterator<Iter>` | Either direct or keyed |
| `random_access_vertex_pattern<Iter>` | Inner value: return whole element |
| `pair_value_vertex_pattern<Iter>` | Inner value: return `.second` |
Expand Down
127 changes: 120 additions & 7 deletions docs/reference/vertex-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
> This page documents two complementary concept families for vertex types:
**inner value patterns** (how vertex data is accessed) and
**storage patterns** (how vertex IDs are stored/extracted). Both families live
in `graph::adj_list::detail` and are re-exported via `<graph/graph.hpp>`.
in `graph::adj_list` and are re-exported via `<graph/graph.hpp>`.

</td>
</tr></table>
Expand All @@ -17,6 +17,25 @@ in `graph::adj_list::detail` and are re-exported via `<graph/graph.hpp>`.

---

## Foundation Concepts

These building-block concepts determine whether a type is "pair-like" and
are used internally by both the inner value and storage pattern concepts.

| Concept | Definition |
|---------|------------|
| `pair_like<T>` | Has `std::tuple_size<T>::value >= 2` and supports `std::get<0>`, `std::get<1>` (tuple protocol) |
| `has_first_second<T>` | Has `.first` and `.second` members |
| `pair_like_value<T>` | `pair_like<T> \|\| has_first_second<T>` — disjunction of both |

```cpp
// Used to constrain keyed_vertex_type, pair_value_vertex_pattern, etc.
template <typename T>
concept pair_like_value = pair_like<T> || has_first_second<T>;
```

---

## Inner Value Patterns

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

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

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

### Examples

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

```cpp
constexpr auto vertex_id() const noexcept {
constexpr decltype(auto) vertex_id() const noexcept {
if constexpr (std::random_access_iterator<VertexIter>) {
return storage_; // direct: return index (size_t)
} else {
return std::get<0>(*storage_); // keyed: extract key
return std::get<0>(*storage_); // keyed: return const& to key
}
}
```
Expand All @@ -172,6 +193,98 @@ Both families work together to give `vertex_descriptor` complete type safety:

---

## `vertex_descriptor<VertexIter>` Class Reference

Defined in `<graph/adj_list/vertex_descriptor.hpp>`.
A lightweight, type-safe handle to a vertex stored in a container.
Constrained by `vertex_iterator<VertexIter>`.

### Type Aliases

| Alias | Definition |
|-------|------------|
| `iterator_type` | `VertexIter` |
| `value_type` | `typename std::iterator_traits<VertexIter>::value_type` |
| `storage_type` | `std::size_t` for random-access iterators, `VertexIter` otherwise |

### Constructors

| Signature | Notes |
|-----------|-------|
| `constexpr vertex_descriptor() noexcept` | Default; requires `std::default_initializable<storage_type>` |
| `constexpr explicit vertex_descriptor(storage_type val) noexcept` | Construct from index or iterator |

### Member Functions

| Function | Returns | Description |
|----------|---------|-------------|
| `value()` | `storage_type` | The raw storage: index (`size_t`) or iterator |
| `vertex_id()` | `decltype(auto)` | Vertex ID — index by value for direct, `const&` to key for keyed |
| `underlying_value(Container&)` | `decltype(auto)` | Full container element (`container[i]` or `*iter`); const overload provided |
| `inner_value(Container&)` | `decltype(auto)` | Vertex data excluding the key (`.second` for maps, whole value for vectors); const overload provided |
| `operator++()` / `operator++(int)` | `vertex_descriptor&` / `vertex_descriptor` | Pre/post-increment |
| `operator<=>` / `operator==` | | Defaulted three-way and equality comparison |

### `std::hash` Specialization

A `std::hash<vertex_descriptor<VertexIter>>` specialization is provided,
hashing the index for direct storage or the `vertex_id()` for keyed storage.
This enables use in `std::unordered_set` and `std::unordered_map`.

---

## `vertex_descriptor_view<VertexIter>` Class Reference

Defined in `<graph/adj_list/vertex_descriptor_view.hpp>`.
A forward-only view over vertex storage that synthesizes `vertex_descriptor`
objects on-the-fly. Inherits from `std::ranges::view_interface`.

### Type Aliases

| Alias | Definition |
|-------|------------|
| `vertex_desc` | `vertex_descriptor<VertexIter>` |
| `storage_type` | `typename vertex_desc::storage_type` |

### Constructors

| Signature | Notes |
|-----------|-------|
| `constexpr vertex_descriptor_view() noexcept` | Default |
| `constexpr vertex_descriptor_view(storage_type begin, storage_type end) noexcept` | From index/iterator range; requires random-access |
| `constexpr explicit vertex_descriptor_view(Container&) noexcept` | From mutable container; requires `sized_range` or random-access |
| `constexpr explicit vertex_descriptor_view(const Container&) noexcept` | From const container |

### Member Functions

| Function | Description |
|----------|-------------|
| `begin()` / `end()` | Forward iterators yielding `vertex_descriptor` by value |
| `cbegin()` / `cend()` | Const equivalents |
| `size()` | O(1) — cached at construction |

### Inner `iterator` Class

| Property | Value |
|----------|-------|
| `iterator_category` | `std::forward_iterator_tag` |
| `value_type` | `vertex_descriptor<VertexIter>` |
| Dereference | Returns descriptor **by value** (synthesized) |

### Deduction Guides

```cpp
vertex_descriptor_view(Container&) -> vertex_descriptor_view<typename Container::iterator>;
vertex_descriptor_view(const Container&) -> vertex_descriptor_view<typename Container::const_iterator>;
```

### Range Traits

`std::ranges::enable_borrowed_range` is specialized to `true` — the view
does not own the underlying data.

---

## Compile-Time Dispatch Pattern

```cpp
Expand Down
48 changes: 39 additions & 9 deletions include/graph/adj_list/descriptor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct in_edge_tag {};
/**
* @brief Concept to check if a type is pair-like (has at least 2 members accessible via tuple protocol)
*
* This is used to constrain bidirectional iterator value_types for vertex storage.
* This is used to constrain forward iterator value_types for vertex storage.
*/
template <typename T>
concept pair_like = requires {
Expand Down Expand Up @@ -89,7 +89,7 @@ concept keyed_vertex_type = std::forward_iterator<Iter> && !std::random_access_i
*
* A vertex iterator must be either:
* - Random access (direct/indexed storage)
* - Bidirectional with pair-like value_type (keyed storage)
* - Forward with pair-like value_type (keyed storage)
*/
template <typename Iter>
concept vertex_iterator = direct_vertex_type<Iter> || keyed_vertex_type<Iter>;
Expand All @@ -111,25 +111,25 @@ concept random_access_vertex_pattern = std::random_access_iterator<Iter>;
/**
* @brief Concept for pair-value vertex pattern (map-like)
*
* Used with bidirectional iterators where the value_type is pair-like.
* Used with forward iterators where the value_type is pair-like.
* inner_value returns the .second part (the data, excluding the key).
* Pattern: *iterator -> pair{key, data}, inner_value returns data&
* Example: std::map<int, VertexData> where inner_value returns VertexData& (.second)
* Example: std::map<int, VertexData> or std::unordered_map<int, VertexData> where inner_value returns VertexData& (.second)
*/
template <typename Iter>
concept pair_value_vertex_pattern = std::bidirectional_iterator<Iter> && !std::random_access_iterator<Iter> &&
concept pair_value_vertex_pattern = std::forward_iterator<Iter> && !std::random_access_iterator<Iter> &&
pair_like_value<typename std::iterator_traits<Iter>::value_type>;

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

/**
Expand Down Expand Up @@ -274,6 +274,36 @@ template <typename Iter>
requires vertex_iterator<Iter>
using vertex_id_type_t = typename vertex_id_type<Iter>::type;

// =============================================================================
// Index-Only Vertex Detection
// =============================================================================

/**
* @brief Concept for index-only vertex iterators (no physical container backing)
*
* An index-only vertex iterator is a random-access iterator whose value_type
* is integral AND whose reference type is not an actual reference (i.e., it
* returns by value, like iota_view iterators). This distinguishes generated
* index sequences from container iterators over integral types (e.g.,
* vector<int>::iterator has reference = int&, so it is NOT index-only).
*
* For these iterators, inner_value() and underlying_value() are not meaningful
* because there is no physical container to index into.
*/
template <typename Iter>
concept index_only_vertex = std::random_access_iterator<Iter> &&
std::integral<std::iter_value_t<Iter>> &&
!std::is_reference_v<std::iter_reference_t<Iter>>;

/**
* @brief Concept for container-backed vertex iterators
*
* The inverse of index_only_vertex. These iterators reference elements in
* a physical container, so inner_value() and underlying_value() are valid.
*/
template <typename Iter>
concept container_backed_vertex = vertex_iterator<Iter> && !index_only_vertex<Iter>;

// =============================================================================
// Edge Value Type Concepts for target_id() Extraction
// =============================================================================
Expand Down
6 changes: 3 additions & 3 deletions include/graph/adj_list/detail/graph_cpo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ namespace _cpo_impls {
* The descriptor default works for standard containers:
* - Random-access containers (vector, deque): returns the index
* - Associative containers (map): returns the key
* - Bidirectional containers: returns the iterator position
* - Forward containers: returns the iterator position
*
* @tparam G Graph type
* @tparam U Vertex descriptor type (constrained to be a vertex_descriptor_type)
Expand Down Expand Up @@ -391,7 +391,7 @@ inline namespace _cpo_instances {
* The type of the unique identifier returned by vertex_id(g, u), with references stripped.
* - For random-access containers (vector, deque): size_t (index)
* - For associative containers (map): key type (e.g. std::string)
* - For bidirectional containers: iterator-based ID
* - For forward containers: iterator-based ID
*/
template <typename G>
using vertex_id_t = std::remove_cvref_t<
Expand Down Expand Up @@ -3005,7 +3005,7 @@ namespace _cpo_impls {
* - Uses u.inner_value(g) which returns the actual vertex data
* - For random-access containers (vector): returns container[index]
* - For associative containers (map): returns the .second value
* - For bidirectional containers: returns the dereferenced value
* - For forward containers: returns the dereferenced value
*
* This provides access to user-defined vertex properties/data stored in the graph.
*
Expand Down
Loading
Loading