From 3083c0ac51d9990a06d564ab73018cfd1e2bd95d Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 5 Jul 2026 17:16:38 +0200 Subject: [PATCH 01/13] wip: deducing this for vertex methods --- include/gl/graph.hpp | 157 ++++++++++++------ include/gl/graph_traits.hpp | 18 +- include/gl/types/properties.hpp | 6 +- .../gl/test_graph_topology_builders.cpp | 27 ++- 4 files changed, 132 insertions(+), 76 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index d8830d7b..c987d895 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -228,8 +228,10 @@ class graph final { /// @brief Integral type used to identify vertices and edges. using id_type = typename traits_type::id_type; - /// @brief The descriptor type representing a vertex. + /// @brief The descriptor type representing a vertex of the graph. using vertex_type = typename traits_type::vertex_type; + /// @brief The descriptor type representing an immutable vertex of the graph. + using const_vertex_type = typename traits_type::const_vertex_type; /// @brief Type representing the properties attached to a vertex. using vertex_properties_type = typename traits_type::vertex_properties_type; /// @brief Type mapping vertex IDs to their respective properties. @@ -238,17 +240,40 @@ class graph final { empty_properties_map, std::vector>; - /// @brief The descriptor type representing an edge. + template + using deduced_vertex_properties_type = std::conditional_t< + std::is_const_v>, + const vertex_properties_type, + vertex_properties_type>; + + template + using deduced_vertex_type = std::conditional_t< + std::is_const_v>, + const_vertex_type, + vertex_type>; + + /// @brief The descriptor type representing an edge of the graph. using edge_type = typename traits_type::edge_type; + /// @brief The descriptor type representing an immutable edge of the graph. + using const_edge_type = typename traits_type::const_edge_type; /// @brief Type representing the properties attached to an edge. using edge_properties_type = typename traits_type::edge_properties_type; - /// @brief Type mapping edge IDs to their respective properties. using edge_properties_map_type = std::conditional_t< traits::c_empty_properties, empty_properties_map, std::vector>; + template + using deduced_edge_properties_type = std::conditional_t< + std::is_const_v>, + const edge_properties_type, + edge_properties_type>; + + template + using deduced_edge_type = std:: + conditional_t>, const_edge_type, edge_type>; + /// @brief Default constructor creates an empty graph. graph() = default; @@ -294,9 +319,9 @@ class graph final { const auto new_vertex_id = static_cast(this->_n_vertices++); if constexpr (traits::c_non_empty_properties) - return vertex_descriptor{new_vertex_id, this->_vertex_properties.emplace_back()}; + return vertex_type{new_vertex_id, this->_vertex_properties.emplace_back()}; else - return vertex_descriptor{new_vertex_id}; + return vertex_type{new_vertex_id}; } /// @brief Adds a new vertex with the given properties to the graph. @@ -307,7 +332,7 @@ class graph final { requires(traits::c_non_empty_properties) { this->_impl.add_vertex(); - return vertex_descriptor{ + return vertex_type{ static_cast(this->_n_vertices++), this->_vertex_properties.emplace_back(std::move(properties)) }; @@ -408,53 +433,67 @@ class graph final { } /// @brief Returns a vertex descriptor bounds-checked by ID. + /// @param self The explicit object parameter. /// @param vertex_id The ID of the vertex. - /// @return The corresponding vertex descriptor. + /// @return The corresponding vertex descriptor (const or mutable). /// @throws std::invalid_argument If the ID is invalid. - [[nodiscard]] vertex_type vertex(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); - return this->vertex_unchecked(vertex_id); + template + [[nodiscard]] deduced_vertex_type vertex(this Self&& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); + return self.vertex_unchecked(vertex_id); } /// @brief Returns a vertex descriptor bounds-checked by ID (alias for `vertex`). + /// @param self The explicit object parameter. /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor. /// @throws std::invalid_argument If the ID is invalid. - [[nodiscard]] gl_attr_force_inline vertex_type at(const id_type vertex_id) const { - return this->vertex(vertex_id); + template + [[nodiscard]] gl_attr_force_inline deduced_vertex_type at( + this Self&& self, const id_type vertex_id + ) { + return self.vertex(vertex_id); } /// @brief Returns a vertex descriptor without bounds checking. + /// @param self The explicit object parameter. /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor. /// /// > [!WARNING] Undefined Behavior /// > /// > No bounds checking is performed. Passing an invalid ID results in Undefined Behavior. - [[nodiscard]] gl_attr_force_inline vertex_type vertex_unchecked(const id_type vertex_id - ) const noexcept { + template + [[nodiscard]] gl_attr_force_inline deduced_vertex_type vertex_unchecked( + this Self&& self, const id_type vertex_id + ) { if constexpr (traits::c_non_empty_properties) - return vertex_descriptor{vertex_id, this->_vertex_properties[vertex_id]}; + return deduced_vertex_type{vertex_id, self._vertex_properties[vertex_id]}; else - return vertex_descriptor{vertex_id}; + return deduced_vertex_type{vertex_id}; } /// @brief Returns a vertex descriptor without bounds checking (array access style). + /// @param self The explicit object parameter. /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor. /// /// > [!WARNING] Undefined Behavior /// > /// > No bounds checking is performed. Passing an invalid ID results in Undefined Behavior. - [[nodiscard]] gl_attr_force_inline vertex_type operator[](const id_type vertex_id - ) const noexcept { - return this->vertex_unchecked(vertex_id); + template + [[nodiscard]] gl_attr_force_inline deduced_vertex_type operator[]( + this Self&& self, const id_type vertex_id + ) noexcept { + return self.vertex_unchecked(vertex_id); } /// @brief Returns a lazily evaluated view of all vertex descriptors in the graph. + /// @param self The explicit object parameter. /// @return A view yielding descriptors for every vertex. - [[nodiscard]] gl_attr_force_inline auto vertices() const noexcept { - return this->vertex_ids() | std::views::transform(this->_create_vertex_descriptor()); + template + [[nodiscard]] gl_attr_force_inline auto vertices(this Self&& self) noexcept { + return self.vertex_ids() | std::views::transform(self._create_vertex_descriptor()); } /// @brief Returns a lazily evaluated view of all active vertex IDs in the graph. @@ -465,21 +504,25 @@ class graph final { /// @brief Retrieves the neighbor vertex IDs for a specific vertex. /// @copydetails detail::graph_doc_anchors::neighbors() + /// @param self The explicit object parameter. /// @param vertex_id The ID of the source vertex. /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto neighbors(const id_type vertex_id) const { - return this->neighbor_ids(vertex_id) - | std::views::transform(this->_create_vertex_descriptor()); + template + [[nodiscard]] gl_attr_force_inline auto neighbors(this Self&& self, const id_type vertex_id) { + return self.neighbor_ids(vertex_id) + | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the neighbor vertex descriptors for a specific vertex. /// @copydetails detail::graph_doc_anchors::neighbors() + /// @param self The explicit object parameter. /// @param vertex The source vertex descriptor. /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto neighbors(vertex_type vertex) const { - return this->neighbors(vertex.id()); + template + [[nodiscard]] gl_attr_force_inline auto neighbors(this Self&& self, vertex_type vertex) { + return self.neighbors(vertex.id()); } /// @brief Retrieves the neighbor vertex IDs for a specific vertex. @@ -503,21 +546,25 @@ class graph final { /// @brief Retrieves the predecessor vertex IDs for a vertex. /// @copydetails detail::graph_doc_anchors::predecessors() + /// @param self The explicit object parameter. /// @param vertex_id The ID of the target vertex. /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto predecessors(const id_type vertex_id) const { - return this->predecessor_ids(vertex_id) - | std::views::transform(this->_create_vertex_descriptor()); + template + [[nodiscard]] gl_attr_force_inline auto predecessors(this Self&& self, const id_type vertex_id) { + return self.predecessor_ids(vertex_id) + | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the predecessor vertex descriptors (incoming edges) for a vertex. /// @copydetails detail::graph_doc_anchors::predecessors() + /// @param self The explicit object parameter. /// @param vertex The target vertex descriptor. /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto predecessors(vertex_type vertex) const { - return this->predecessors(vertex.id()); + template + [[nodiscard]] gl_attr_force_inline auto predecessors(this Self&& self, vertex_type vertex) { + return self.predecessors(vertex.id()); } /// @brief Retrieves the predecessor vertex IDs for a vertex. @@ -541,21 +588,25 @@ class graph final { /// @brief Retrieves the successor vertex IDs for a vertex. /// @copydetails detail::graph_doc_anchors::successors() + /// @param self The explicit object parameter. /// @param vertex_id The ID of the source vertex. /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto successors(const id_type vertex_id) const { - return this->successor_ids(vertex_id) - | std::views::transform(this->_create_vertex_descriptor()); + template + [[nodiscard]] gl_attr_force_inline auto successors(this Self&& self, const id_type vertex_id) { + return self.successor_ids(vertex_id) + | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the successor vertex descriptors (outgoing edges) for a vertex. /// @copydetails detail::graph_doc_anchors::successors() + /// @param self The explicit object parameter. /// @param vertex The source vertex descriptor. /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto successors(vertex_type vertex) const { - return this->successors(vertex.id()); + template + [[nodiscard]] gl_attr_force_inline auto successors(this Self&& self, vertex_type vertex) { + return self.successors(vertex.id()); } /// @brief Retrieves the successor vertex IDs for a vertex. @@ -578,23 +629,28 @@ class graph final { } /// @brief Retrieves a mutable reference to a vertex's properties. + /// @param self The explicit object parameter. /// @param id The ID of the vertex. /// @return A reference to the properties attached to the vertex. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline vertex_properties_type& vertex_properties(const id_type id - ) const + template + [[nodiscard]] gl_attr_force_inline deduced_vertex_properties_type& vertex_properties( + this Self&& self, const id_type id + ) requires(traits::c_non_empty_properties) { - this->_verify_vertex_id(id); - return this->_vertex_properties[id]; + self._verify_vertex_id(id); + return self._vertex_properties[id]; } /// @brief Retrieves a random-access view over all vertex properties in the graph. + /// @param self The explicit object parameter. /// @return A view mapping each active vertex index to its property. - [[nodiscard]] gl_attr_force_inline auto vertex_properties_map() const noexcept + template + [[nodiscard]] gl_attr_force_inline auto vertex_properties_map(this Self&& self) noexcept requires(traits::c_non_empty_properties) { - return std::views::all(this->_vertex_properties); + return std::views::all(self._vertex_properties); } // --- degree getters --- @@ -1212,17 +1268,19 @@ class graph final { // --- transformations --- - gl_attr_force_inline auto _create_vertex_descriptor() const noexcept + template + gl_attr_force_inline auto _create_vertex_descriptor(this Self&&) noexcept requires(traits::c_empty_properties) { - return [](const id_type id) { return vertex_type{id}; }; + return [](const id_type id) { return deduced_vertex_type{id}; }; } - gl_attr_force_inline auto _create_vertex_descriptor() const noexcept + template + gl_attr_force_inline auto _create_vertex_descriptor(this Self&& self) noexcept requires(traits::c_non_empty_properties) { - return [&pmap = this->_vertex_properties](const id_type id) { - return vertex_type{id, pmap[to_idx(id)]}; + return [&pmap = self._vertex_properties](const id_type id) { + return deduced_vertex_type{id, pmap[to_idx(id)]}; }; } @@ -1400,10 +1458,7 @@ class graph final { implementation_type _impl{}; - /// @todo Replace mutability with proper const-correct getter overloads to ensure thread safety guarantees associated with the const qualifier - [[no_unique_address]] mutable vertex_properties_map_type _vertex_properties{}; - - /// @todo Replace mutability with proper const-correct getter overloads to ensure thread safety guarantees associated with the const qualifier + [[no_unique_address]] vertex_properties_map_type _vertex_properties{}; [[no_unique_address]] mutable edge_properties_map_type _edge_properties{}; }; diff --git a/include/gl/graph_traits.hpp b/include/gl/graph_traits.hpp index af50ddd0..6bda6933 100644 --- a/include/gl/graph_traits.hpp +++ b/include/gl/graph_traits.hpp @@ -13,6 +13,8 @@ #include "gl/edge_descriptor.hpp" #include "gl/types/core.hpp" +#include + namespace gl { /// @ingroup GL-Core @@ -62,15 +64,19 @@ struct graph_traits { /// @brief The type of graph element indentifiers (i.e. vertex and edge IDs). using id_type = IdType; - /// @brief The vertex descriptor type associated with this graph, defined based on the specified vertex properties and identifier type. - using vertex_type = vertex_descriptor; /// @brief The type of properties associated with the vertex descriptor. - using vertex_properties_type = typename vertex_type::properties_type; + using vertex_properties_type = std::remove_cvref_t; + /// @brief The descriptor type representing a vertex of a graph. + using vertex_type = vertex_descriptor; + /// @brief The descriptor type representing an immutable vertex of a graph. + using const_vertex_type = vertex_descriptor; - /// @brief The edge descriptor type associated with this graph, defined based on the specified edge properties and identifier type. - using edge_type = edge_descriptor; /// @brief The type of properties associated with the edge descriptor. - using edge_properties_type = typename edge_type::properties_type; + using edge_properties_type = std::remove_cvref_t; + /// @brief The descriptor type representing an edge of a graph. + using edge_type = edge_descriptor; + /// @brief The descriptor type representing an immutable edge of a graph. + using const_edge_type = edge_descriptor; }; /// @ingroup GL-Core diff --git a/include/gl/types/properties.hpp b/include/gl/types/properties.hpp index 8e394470..203727f2 100644 --- a/include/gl/types/properties.hpp +++ b/include/gl/types/properties.hpp @@ -12,6 +12,7 @@ #include #include +#include #include namespace gl { @@ -256,7 +257,7 @@ namespace traits { /// /// @tparam T The type to evaluate against the concept. template -concept c_properties = std::semiregular; +concept c_properties = std::semiregular>; /// @ingroup GL-Traits /// @brief Validates if a type is specifically the @ref gl::empty_properties tag. @@ -266,7 +267,8 @@ concept c_properties = std::semiregular; /// /// @tparam T The type to evaluate against the concept. template -concept c_empty_properties = c_properties and std::same_as; +concept c_empty_properties = + c_properties and std::same_as, gl::empty_properties>; /// @ingroup GL-Traits /// @brief Validates if a property type contains actual user-defined data. diff --git a/tests/source/gl/test_graph_topology_builders.cpp b/tests/source/gl/test_graph_topology_builders.cpp index e7aa78fb..1f4041e7 100644 --- a/tests/source/gl/test_graph_topology_builders.cpp +++ b/tests/source/gl/test_graph_topology_builders.cpp @@ -47,9 +47,8 @@ namespace predicate { template [[nodiscard]] auto is_vertex_fully_connected(const GraphType& graph) { - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { - return std::ranges::all_of(graph.vertices(), [&](const vertex_type& target) { + return [&graph](const auto& source) { + return std::ranges::all_of(graph.vertices(), [&](const auto& target) { return source == target or graph.has_edge(source, target); }); }; @@ -57,9 +56,8 @@ template template [[nodiscard]] auto is_vertex_not_connected(const GraphType& graph) { - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { - return std::ranges::none_of(graph.vertices(), [&](const vertex_type& target) { + return [&graph](const auto& source) { + return std::ranges::none_of(graph.vertices(), [&](const auto& target) { return graph.has_edge(source, target); }); }; @@ -69,8 +67,7 @@ template [[nodiscard]] auto is_vertex_not_connected_to_any_from( const GraphType& graph, const auto& vertex_it_range ) { - using vertex_type = typename GraphType::vertex_type; - return [&](const vertex_type& source) { + return [&](const auto& source) { return std::ranges::none_of(vertex_it_range, [&](const auto& vertex) { return vertex != source and (graph.has_edge(source, vertex) or graph.has_edge(vertex, source)); @@ -81,8 +78,7 @@ template template [[nodiscard]] auto is_vertex_connected_to_next_only(const GraphType& graph) { using id_type = typename GraphType::id_type; - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { + return [&graph](const auto& source) { const auto next_vertex_id = static_cast((source.id() + 1uz) % graph.n_vertices()); const auto next_vertex = graph[next_vertex_id]; @@ -95,8 +91,7 @@ template template [[nodiscard]] auto is_vertex_connected_to_prev_only(const GraphType& graph) { using id_type = typename GraphType::id_type; - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { + return [&graph](const auto& source) { const auto prev_vertex_id = static_cast((source.id() + graph.n_vertices() - 1uz) % graph.n_vertices()); const auto prev_vertex = graph[prev_vertex_id]; @@ -110,8 +105,7 @@ template template [[nodiscard]] auto is_vertex_connected_to_id_adjacent(const GraphType& graph) { using id_type = typename GraphType::id_type; - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { + return [&graph](const auto& source) { const auto next_vertex_id = static_cast((source.id() + 1uz) % graph.n_vertices()); const auto next_vertex = graph[next_vertex_id]; @@ -128,8 +122,7 @@ template template [[nodiscard]] auto is_connected_to_binary_chlidren(const GraphType& graph) { - using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { + return [&graph](const auto& source) { const auto target_ids = gl::topology::detail::get_bintree_target_ids(source.id()); if (target_ids.first >= graph.n_vertices()) @@ -182,7 +175,7 @@ TEST_CASE_TEMPLATE_DEFINE( "directional_tag-independent graph topology builders tests", GraphType, graph_type_template ) { using graph_type = GraphType; - using vertex_type = typename graph_type::vertex_type; + using vertex_type = typename graph_type::const_vertex_type; SUBCASE("clique(n_vertices) should build a fully connected graph of size n_vertices") { const auto clique = gl::topology::clique(constants::n_elements_top); From 0288e94e438ddd8bf13abc5a2e892a4227236435 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 6 Jul 2026 11:47:45 +0200 Subject: [PATCH 02/13] working deducing this for vertices --- include/gl/graph.hpp | 99 +++++++++++++------ include/gl/vertex_descriptor.hpp | 28 ++++-- .../gl/test_graph_topology_builders.cpp | 4 +- 3 files changed, 91 insertions(+), 40 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index c987d895..2954ec1e 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -90,6 +90,16 @@ concept c_flat_matrix_graph = template concept c_adjacency_matrix_graph = c_matrix_graph or c_flat_matrix_graph; +template +concept c_graph_vertex = + c_graph + and c_one_of, typename G::vertex_type, typename G::const_vertex_type>; + +template +concept c_graph_edge = + c_graph + and c_one_of, typename G::edge_type, typename G::const_edge_type>; + } // namespace traits template @@ -175,7 +185,7 @@ struct to_impl; /// ### API Design: IDs vs. Descriptors /// The `graph` class exposes a dual API to accommodate different performance and ergonomic needs: /// -/// - **Inputs**: Most query methods are overloaded to accept either a raw `id_type` or a `vertex_type`/`edge_type` descriptor. They are functionally identical. +/// - **Inputs**: Most query methods are overloaded to accept either a raw `id_type` or a vertex/edge descriptor types. They are functionally identical. /// - **Outputs**: Methods ending in `_ids` (e.g., `neighbor_ids`) return views of raw integral IDs. Methods without this suffix (e.g., `neighbors`) automatically map those IDs to the proper descriptor objects. /// - **Performance**: Descriptor-returning methods incur a slight overhead if the graph utilizes properties, as the property reference must be fetched and bound to each descriptor. If you only need topology, prefer the `_ids` variants. /// @@ -383,7 +393,7 @@ class graph final { /// @param vertex The descriptor of the vertex to remove. /// @throws std::invalid_argument If the vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::remove_vertex_wrn() - gl_attr_force_inline void remove_vertex(vertex_type vertex) { + gl_attr_force_inline void remove_vertex(traits::c_graph_vertex auto vertex) { this->remove_vertex(vertex.id()); } @@ -408,9 +418,9 @@ class graph final { /// @param vertex_rng A sized range containing the descriptors of vertices to remove. /// @throws std::invalid_argument If any vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::remove_vertex_wrn() - gl_attr_force_inline void remove_vertices( - const traits::c_forward_range_of auto& vertex_rng - ) { + template + requires(traits::c_forward_range and traits::c_graph_vertex, graph>) + gl_attr_force_inline void remove_vertices(const VertexRng& vertex_rng) { this->remove_vertices( vertex_rng | std::views::transform([](const auto& v) { return v.id(); }) ); @@ -428,7 +438,8 @@ class graph final { /// @brief Checks if the given vertex descriptor is valid in the graph. /// @param vertex The vertex descriptor to check. /// @return `true` if it exists, `false` otherwise. - [[nodiscard]] gl_attr_force_inline bool has_vertex(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline bool has_vertex(traits::c_graph_vertex auto vertex + ) const { return this->has_vertex(vertex.id()); } @@ -521,7 +532,9 @@ class graph final { /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. template - [[nodiscard]] gl_attr_force_inline auto neighbors(this Self&& self, vertex_type vertex) { + [[nodiscard]] gl_attr_force_inline auto neighbors( + this Self&& self, traits::c_graph_vertex auto vertex + ) { return self.neighbors(vertex.id()); } @@ -540,7 +553,8 @@ class graph final { /// @param vertex The source vertex descriptor. /// @return A view of all adjacent vertex IDs. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto neighbor_ids(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(traits::c_graph_vertex auto vertex + ) const { return this->neighbor_ids(vertex.id()); } @@ -563,7 +577,9 @@ class graph final { /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. template - [[nodiscard]] gl_attr_force_inline auto predecessors(this Self&& self, vertex_type vertex) { + [[nodiscard]] gl_attr_force_inline auto predecessors( + this Self&& self, traits::c_graph_vertex auto vertex + ) { return self.predecessors(vertex.id()); } @@ -582,7 +598,9 @@ class graph final { /// @param vertex The target vertex descriptor. /// @return A view of all predecessor vertex IDs. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto predecessor_ids(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto predecessor_ids( + traits::c_graph_vertex auto vertex + ) const { return this->predecessor_ids(vertex.id()); } @@ -605,7 +623,9 @@ class graph final { /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. template - [[nodiscard]] gl_attr_force_inline auto successors(this Self&& self, vertex_type vertex) { + [[nodiscard]] gl_attr_force_inline auto successors( + this Self&& self, traits::c_graph_vertex auto vertex + ) { return self.successors(vertex.id()); } @@ -624,7 +644,8 @@ class graph final { /// @param vertex The source vertex descriptor. /// @return A view of all successor vertex IDs. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto successor_ids(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto successor_ids(traits::c_graph_vertex auto vertex + ) const { return this->successor_ids(vertex.id()); } @@ -670,7 +691,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return The total degree. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline size_type degree(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline size_type degree(traits::c_graph_vertex auto vertex + ) const { return this->degree(vertex.id()); } @@ -695,7 +717,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return The in-degree. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline size_type in_degree(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline size_type in_degree(traits::c_graph_vertex auto vertex + ) const { return this->in_degree(vertex.id()); } @@ -720,7 +743,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return The out-degree. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline size_type out_degree(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(traits::c_graph_vertex auto vertex) const { return this->out_degree(vertex.id()); } @@ -786,7 +810,7 @@ class graph final { /// @return A descriptor representing the newly created edge. /// @throws std::invalid_argument If either vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::add_edge_note() - gl_attr_force_inline edge_type add_edge(vertex_type source, vertex_type target) { + gl_attr_force_inline edge_type add_edge(traits::c_graph_vertex auto source, traits::c_graph_vertex auto target) { return this->add_edge(source.id(), target.id()); } @@ -798,7 +822,7 @@ class graph final { /// @throws std::invalid_argument If either vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::add_edge_note() gl_attr_force_inline edge_type add_edge_with( - vertex_type source, vertex_type target, const edge_properties_type& properties + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target, const edge_properties_type& properties ) requires(traits::c_non_empty_properties) { @@ -836,9 +860,11 @@ class graph final { /// @param target_rng A sized range of target vertex descriptors. /// @throws std::invalid_argument If any vertex ID is invalid. /// @copydetails detail::graph_doc_anchors::add_edge_note() - void add_edges_from( - vertex_type source, const traits::c_sized_range_of auto& target_rng - ) { + template + requires(traits::c_sized_range and traits::c_graph_vertex, graph>) + void add_edges_from(traits::c_graph_vertex auto source, const TargetRng& target_rng) { + using rng_vertex_type = std::ranges::range_value_t; + this->_verify_vertex_id(source.id()); for (auto target : target_rng) this->_verify_vertex_id(target.id()); @@ -848,7 +874,7 @@ class graph final { this->_impl.add_edges_from( std::views::iota(static_cast(prev_n_edges), this->_n_edges), source.id(), - target_rng | std::views::transform(&vertex_type::id) + target_rng | std::views::transform(&rng_vertex_type::id) ); @@ -921,7 +947,9 @@ class graph final { /// @param target The target vertex descriptor. /// @return `true` if an edge exists, `false` otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline bool has_edge(vertex_type source, vertex_type target) const { + [[nodiscard]] gl_attr_force_inline bool has_edge( + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target + ) const { return this->has_edge(source.id(), target.id()); } @@ -945,7 +973,7 @@ class graph final { /// @return `true` if an edge exists, `false` otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. [[nodiscard]] gl_attr_force_inline std::optional edge( - vertex_type source, vertex_type target + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target ) const { return this->edge(source.id(), target.id()); } @@ -971,7 +999,7 @@ class graph final { /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex descriptor is invalid. [[nodiscard]] gl_attr_force_inline std::vector edges( - vertex_type source, vertex_type target + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target ) const { return this->edges(source.id(), target.id()); } @@ -992,7 +1020,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto incident_edges(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto incident_edges(traits::c_graph_vertex auto vertex + ) const { return this->incident_edges(vertex.id()); } @@ -1012,7 +1041,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto in_edges(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto in_edges(traits::c_graph_vertex auto vertex + ) const { return this->in_edges(vertex.id()); } @@ -1032,7 +1062,8 @@ class graph final { /// @param vertex The vertex descriptor. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto out_edges(vertex_type vertex) const { + [[nodiscard]] gl_attr_force_inline auto out_edges(traits::c_graph_vertex auto vertex + ) const { return this->out_edges(vertex.id()); } @@ -1101,8 +1132,9 @@ class graph final { /// @param target The target vertex descriptor. /// @return `true` if the given vertices are adjacent, `false` otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline bool are_adjacent(vertex_type source, vertex_type target) - const { + [[nodiscard]] gl_attr_force_inline bool are_adjacent( + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target + ) const { return this->are_adjacent(source.id(), target.id()); } @@ -1134,7 +1166,9 @@ class graph final { /// @param edge The edge descriptor. /// @return `true` if the vertex is incident to the edge, `false` otherwise. /// @throws std::invalid_argument If either the vertex or the edge descriptor is invalid. - [[nodiscard]] bool are_incident(vertex_type vertex, const edge_type& edge) const { + [[nodiscard]] bool are_incident( + traits::c_graph_vertex auto vertex, const edge_type& edge + ) const { this->_verify_vertex_id(vertex.id()); this->_verify_edge(edge); return edge.is_incident_with(vertex.id()); @@ -1148,8 +1182,9 @@ class graph final { /// @param vertex The vertex descriptor. /// @return `true` if the vertex is incident to the edge, `false` otherwise. /// @throws std::invalid_argument If either the vertex or the edge descriptor is invalid. - [[nodiscard]] gl_attr_force_inline bool are_incident(const edge_type& edge, vertex_type vertex) - const { + [[nodiscard]] gl_attr_force_inline bool are_incident( + const edge_type& edge, traits::c_graph_vertex auto vertex + ) const { return this->are_incident(vertex, edge); } diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index cb244f5d..673b7188 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -91,6 +91,17 @@ class vertex_descriptor final { requires(traits::c_non_empty_properties) : _id(id), _properties(properties) {} + /// @brief Implicit converting constructor from a non-const descriptor to a const descriptor. + /// @tparam NonConstProps The non-const property type. + template + requires(std::same_as) + vertex_descriptor(const vertex_descriptor& other) noexcept + : _id(other.id()) { + if constexpr (traits::c_non_empty_properties) { + this->_properties = other.properties(); + } + } + /// @brief Returns an invalid vertex descriptor (for empty properties). /// @return A `vertex_descriptor` holding the `invalid_id`. [[nodiscard]] gl_attr_force_inline static vertex_descriptor invalid() noexcept @@ -121,21 +132,26 @@ class vertex_descriptor final { /// @brief Destructor. ~vertex_descriptor() = default; - /// @brief Equality comparison operator. + /// @brief Cross-type equality comparison operator. + /// @tparam OtherProperties The property type of the other descriptor. /// @param other The vertex descriptor to compare against. /// @return `true` if both descriptors hold the same ID, `false` otherwise. - [[nodiscard]] gl_attr_force_inline bool operator==(const vertex_descriptor& other + template + [[nodiscard]] gl_attr_force_inline bool operator==( + const vertex_descriptor& other ) const noexcept { - return this->_id == other._id; + return this->_id == other.id(); } - /// @brief Three-way comparison operator. + /// @brief Cross-type three-way comparison operator. + /// @tparam OtherProperties The property type of the other descriptor. /// @param other The vertex descriptor to compare against. /// @return The strong ordering result based on the underlying IDs. + template [[nodiscard]] gl_attr_force_inline std::strong_ordering operator<=>( - const vertex_descriptor& other + const vertex_descriptor& other ) const noexcept { - return this->_id <=> other._id; + return this->_id <=> other.id(); } /// @brief Boolean conversion operator. diff --git a/tests/source/gl/test_graph_topology_builders.cpp b/tests/source/gl/test_graph_topology_builders.cpp index 1f4041e7..39ae114b 100644 --- a/tests/source/gl/test_graph_topology_builders.cpp +++ b/tests/source/gl/test_graph_topology_builders.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace gl_testing { @@ -175,7 +176,6 @@ TEST_CASE_TEMPLATE_DEFINE( "directional_tag-independent graph topology builders tests", GraphType, graph_type_template ) { using graph_type = GraphType; - using vertex_type = typename graph_type::const_vertex_type; SUBCASE("clique(n_vertices) should build a fully connected graph of size n_vertices") { const auto clique = gl::topology::clique(constants::n_elements_top); @@ -208,7 +208,7 @@ TEST_CASE_TEMPLATE_DEFINE( // verify that all vertices from A are connected to all vertices from B and vice versa CHECK(std::ranges::all_of( vertices_a | std::views::take(constants::n_elements_top), - [&](const vertex_type& source) { + [&](const auto& source) { return std::ranges::all_of(vertices_b, [&](const auto& vertex) { return biclique.has_edge(source, vertex) and biclique.has_edge(vertex, source); }); From 3a730d6301b0e7e168de07c0ea05fd3fa2786538 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 6 Jul 2026 11:56:18 +0200 Subject: [PATCH 03/13] doc comments alignment --- include/gl/graph.hpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 2954ec1e..c7b92f2e 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -90,11 +90,19 @@ concept c_flat_matrix_graph = template concept c_adjacency_matrix_graph = c_matrix_graph or c_flat_matrix_graph; +/// @ingroup GL-Traits +/// @brief Concept checking if a type is a mutable or immutable vertex descriptor associated with the given graph. +/// @tparam V The type of the vertex descriptor. +/// @tparam G The type of the graph. template concept c_graph_vertex = c_graph and c_one_of, typename G::vertex_type, typename G::const_vertex_type>; +/// @ingroup GL-Traits +/// @brief Concept checking if a type is a mutable or immutable edge descriptor associated with the given graph. +/// @tparam E The type of the edge descriptor. +/// @tparam G The type of the graph. template concept c_graph_edge = c_graph @@ -415,6 +423,7 @@ class graph final { } /// @brief Removes a range of vertices using their descriptors. + /// @tparam VertexRng A forward range type containing graph's vertex descriptors. /// @param vertex_rng A sized range containing the descriptors of vertices to remove. /// @throws std::invalid_argument If any vertex descriptor is invalid. /// @copydetails detail::graph_doc_anchors::remove_vertex_wrn() @@ -513,7 +522,7 @@ class graph final { return std::views::iota(initial_id_v, this->_n_vertices); } - /// @brief Retrieves the neighbor vertex IDs for a specific vertex. + /// @brief Retrieves the neighbor vertex descriptors for a specific vertex. /// @copydetails detail::graph_doc_anchors::neighbors() /// @param self The explicit object parameter. /// @param vertex_id The ID of the source vertex. @@ -558,7 +567,7 @@ class graph final { return this->neighbor_ids(vertex.id()); } - /// @brief Retrieves the predecessor vertex IDs for a vertex. + /// @brief Retrieves the predecessor vertex descriptors (incoming edges) for a vertex. /// @copydetails detail::graph_doc_anchors::predecessors() /// @param self The explicit object parameter. /// @param vertex_id The ID of the target vertex. @@ -604,7 +613,7 @@ class graph final { return this->predecessor_ids(vertex.id()); } - /// @brief Retrieves the successor vertex IDs for a vertex. + /// @brief Retrieves the successor vertex descriptors (outgoing edges) for a vertex. /// @copydetails detail::graph_doc_anchors::successors() /// @param self The explicit object parameter. /// @param vertex_id The ID of the source vertex. @@ -856,6 +865,7 @@ class graph final { } /// @brief Dispatches multiple edge insertions connecting one source to many targets. + /// @tparam TargetRng A sized range type containing graph's vertex descriptors. /// @param source The source vertex descriptor. /// @param target_rng A sized range of target vertex descriptors. /// @throws std::invalid_argument If any vertex ID is invalid. @@ -956,7 +966,7 @@ class graph final { /// @brief Retrieves an edge (if it exists) connecting the source to the target. /// @param source_id The source vertex ID. /// @param target_id The target vertex ID. - /// @return `true` if an edge exists, `false` otherwise. + /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex ID is invalid. [[nodiscard]] std::optional edge(const id_type source_id, const id_type target_id) const { @@ -970,7 +980,7 @@ class graph final { /// @brief Retrieves an edge (if it exists) connecting the source to the target. /// @param source The source vertex descriptor. /// @param target The target vertex descriptor. - /// @return `true` if an edge exists, `false` otherwise. + /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. [[nodiscard]] gl_attr_force_inline std::optional edge( traits::c_graph_vertex auto source, traits::c_graph_vertex auto target @@ -1016,7 +1026,7 @@ class graph final { return this->_impl.incident_edges(vertex_id); } - /// @brief Retrieves all incident with a vertex. + /// @brief Retrieves all edges incident with a vertex. /// @param vertex The vertex descriptor. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. From 0634f9870cd41ba9d46d9d0c5e266c7777cb7b35 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 6 Jul 2026 12:01:04 +0200 Subject: [PATCH 04/13] updated hypergraph complexity tables --- docs/hgl/architecture.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/hgl/architecture.md b/docs/hgl/architecture.md index 5295cba1..f057bc84 100644 --- a/docs/hgl/architecture.md +++ b/docs/hgl/architecture.md @@ -281,10 +281,10 @@ The complexities of topological queries is identical for standard (`list_t`) and | Query Operation | Bidirectional | Vertex-Major | Hyperedge-Major | | :--- | :--- | :--- | :--- | | **Check Incidence** $(v, e)$ | $O(\log(\min(deg(v), \vert e \vert)))$ | $O(\log(deg(v)))$ | $O(\log(\vert e \vert))$ | -| **Iterate Incident Hyperedges** of $v$ | $O(deg(v))$ | $O(deg(v))$ | $O(\vert V \vert + I)$ (Scan All) | -| **Iterate Incident Vertices** of $e$ | $O(\vert e \vert)$ | $O(\vert E \vert + I)$ (Scan All) | $O(\vert e \vert)$ | -| **Get Degree** of $v$ | $O(1)$ | $O(1)$ | $O(\vert V \vert + I)$ | -| **Get Size** of $e$ | $O(1)$ | $O(\vert E \vert + I)$ | $O(1)$ | +| **Iterate Incident Hyperedges** of $v$ | $O(deg(v))$ | $O(deg(v))$ | $O(\vert E \vert + I)$ (Scan All) | +| **Iterate Incident Vertices** of $e$ | $O(\vert e \vert)$ | $O(\vert V \vert + I)$ (Scan All) | $O(\vert e \vert)$ | +| **Get Degree** of $v$ | $O(1)$ | $O(1)$ | $O(\vert E \vert + I)$ | +| **Get Size** of $e$ | $O(1)$ | $O(\vert V \vert + I)$ | $O(1)$ | **Structural Mutations (Standard Incidence List, `list_t`):** @@ -328,8 +328,8 @@ The complexities of topological queries is identical for standard (`matrix_t`) a | Mutation Operation | `matrix_t` (Standard) | `flat_matrix_t` (Flat) | | :--- | :--- | :--- | -| **Add Vertex** | $O(\vert E \vert)$ amortized if `vertex_major_t`
$O(\vert V \vert \cdot \vert E \vert)$ if `hyperedge_major_t` | $O(\vert V \vert \cdot \vert E \vert)$ | -| **Add Hyperedge** | $O(\vert V \vert \cdot \vert E \vert)$ if `vertex_major_t`
$O(\vert V \vert)$ amortized if `hyperedge_major_t` | $O(\vert V \vert \cdot \vert E \vert)$ | +| **Add Vertex** | $O(\vert E \vert)$ amortized | $O(\vert V \vert \cdot \vert E \vert)$ | +| **Add Hyperedge** | $O(\vert V \vert)$ amortized | $O(\vert V \vert \cdot \vert E \vert)$ | | **Remove Vertex / Hyperedge** | $O(\vert V \vert \cdot \vert E \vert)$ (Shift rows/cols) | $O(\vert V \vert \cdot \vert E \vert)$ | | **Add / Remove Incidence** (Bind/Unbind) | $O(1)$ | $O(1)$ | From a1f8cc9f7c15012c0f69f9735f4a843f9dd3b802 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Thu, 23 Jul 2026 14:37:43 +0200 Subject: [PATCH 05/13] i dont remember --- include/gl/edge_descriptor.hpp | 12 +++++++ include/gl/graph.hpp | 56 +++++++++++++++++--------------- include/gl/vertex_descriptor.hpp | 1 + 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index a567cf12..db6e84da 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -105,6 +105,18 @@ class edge_descriptor final { requires(traits::c_non_empty_properties) : _id(id), _vertices(source, target), _properties(properties) {} + /// @brief Implicit converting constructor from a non-const descriptor to a const descriptor. + /// @tparam NonConstProps The non-const property type. + /// @param other The edge descriptor to convert from. + template + requires(std::same_as) + edge_descriptor(const edge_descriptor& other) noexcept + : _id(other.id()), _vertices(other._vertices) { + if constexpr (traits::c_non_empty_properties) { + this->_properties = other.properties(); + } + } + /// @brief Returns an invalid edge descriptor (for empty properties). /// @return An `edge_descriptor` holding `invalid_id` for edge and vertex IDs. [[nodiscard]] gl_attr_force_inline static edge_descriptor invalid() noexcept diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index c7b92f2e..619fac2a 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -39,13 +39,13 @@ concept c_graph = c_instantiation_of; /// @brief Concept checking if a graph is directed. /// @see gl::directed_t "directed_t" : For the directional tag used to specify directed graph configuration. template -concept c_directed_graph = c_graph and c_directed_edge; +concept c_directed_graph = c_graph and std::same_as; /// @ingroup GL-Traits /// @brief Concept checking if a graph is undirected. /// @see gl::undirected_t "undirected_t" : For the directional tag used to specify undirected graph configuration. template -concept c_undirected_graph = c_graph and c_undirected_edge; +concept c_undirected_graph = c_graph and std::same_as; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes the standard adjacency list representation. @@ -252,35 +252,35 @@ class graph final { using const_vertex_type = typename traits_type::const_vertex_type; /// @brief Type representing the properties attached to a vertex. using vertex_properties_type = typename traits_type::vertex_properties_type; - /// @brief Type mapping vertex IDs to their respective properties. - using vertex_properties_map_type = std::conditional_t< - traits::c_empty_properties, - empty_properties_map, - std::vector>; - template - using deduced_vertex_properties_type = std::conditional_t< - std::is_const_v>, - const vertex_properties_type, - vertex_properties_type>; + /// @brief The descriptor type representing an edge of the graph. + using edge_type = typename traits_type::edge_type; + /// @brief The descriptor type representing an immutable edge of the graph. + using const_edge_type = typename traits_type::const_edge_type; + /// @brief Type representing the properties attached to an edge. + using edge_properties_type = typename traits_type::edge_properties_type; +private: template using deduced_vertex_type = std::conditional_t< std::is_const_v>, const_vertex_type, vertex_type>; - /// @brief The descriptor type representing an edge of the graph. - using edge_type = typename traits_type::edge_type; - /// @brief The descriptor type representing an immutable edge of the graph. - using const_edge_type = typename traits_type::const_edge_type; - /// @brief Type representing the properties attached to an edge. - using edge_properties_type = typename traits_type::edge_properties_type; - /// @brief Type mapping edge IDs to their respective properties. - using edge_properties_map_type = std::conditional_t< - traits::c_empty_properties, + template + using deduced_vertex_properties_type = std::conditional_t< + std::is_const_v>, + const vertex_properties_type, + vertex_properties_type>; + + using vertex_properties_map_type = std::conditional_t< + traits::c_empty_properties, empty_properties_map, - std::vector>; + std::vector>; + + template + using deduced_edge_type = std:: + conditional_t>, const_edge_type, edge_type>; template using deduced_edge_properties_type = std::conditional_t< @@ -288,10 +288,12 @@ class graph final { const edge_properties_type, edge_properties_type>; - template - using deduced_edge_type = std:: - conditional_t>, const_edge_type, edge_type>; + using edge_properties_map_type = std::conditional_t< + traits::c_empty_properties, + empty_properties_map, + std::vector>; +public: /// @brief Default constructor creates an empty graph. graph() = default; @@ -1263,8 +1265,6 @@ class graph final { friend struct detail::to_impl; private: - using fmt_traits = io::detail::graph_fmt_traits; - graph(const graph& other) = default; // --- element validation --- @@ -1331,6 +1331,8 @@ class graph final { // --- I/O utility --- + using fmt_traits = io::detail::graph_fmt_traits; + struct concise_target_formatter { edge_type edge; id_type src_id; diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 673b7188..52f8e7a8 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -93,6 +93,7 @@ class vertex_descriptor final { /// @brief Implicit converting constructor from a non-const descriptor to a const descriptor. /// @tparam NonConstProps The non-const property type. + /// @param other The vertex descriptor to convert from. template requires(std::same_as) vertex_descriptor(const vertex_descriptor& other) noexcept From 557915d4f7664058bb20b75ca24fda1c97311d14 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Thu, 23 Jul 2026 15:42:37 +0200 Subject: [PATCH 06/13] wip: edge const correctness --- include/gl/graph.hpp | 158 +++++++++--------- include/gl/impl/adjacency_list.hpp | 76 +++++---- include/gl/impl/adjacency_matrix.hpp | 81 ++++----- .../gl/impl/specialized/adjacency_list.hpp | 58 +++---- .../impl/specialized/flat_adjacency_list.hpp | 56 +++---- tests/source/gl/test_adjacency_list.cpp | 4 +- 6 files changed, 220 insertions(+), 213 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 619fac2a..d60cd2e7 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -455,7 +455,6 @@ class graph final { } /// @brief Returns a vertex descriptor bounds-checked by ID. - /// @param self The explicit object parameter. /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor (const or mutable). /// @throws std::invalid_argument If the ID is invalid. @@ -466,7 +465,6 @@ class graph final { } /// @brief Returns a vertex descriptor bounds-checked by ID (alias for `vertex`). - /// @param self The explicit object parameter. /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor. /// @throws std::invalid_argument If the ID is invalid. @@ -478,7 +476,6 @@ class graph final { } /// @brief Returns a vertex descriptor without bounds checking. - /// @param self The explicit object parameter. /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor. /// @@ -496,7 +493,6 @@ class graph final { } /// @brief Returns a vertex descriptor without bounds checking (array access style). - /// @param self The explicit object parameter. /// @param vertex_id The ID of the vertex. /// @return The corresponding vertex descriptor. /// @@ -511,10 +507,8 @@ class graph final { } /// @brief Returns a lazily evaluated view of all vertex descriptors in the graph. - /// @param self The explicit object parameter. /// @return A view yielding descriptors for every vertex. - template - [[nodiscard]] gl_attr_force_inline auto vertices(this Self&& self) noexcept { + [[nodiscard]] gl_attr_force_inline auto vertices(this auto&& self) noexcept { return self.vertex_ids() | std::views::transform(self._create_vertex_descriptor()); } @@ -526,25 +520,21 @@ class graph final { /// @brief Retrieves the neighbor vertex descriptors for a specific vertex. /// @copydetails detail::graph_doc_anchors::neighbors() - /// @param self The explicit object parameter. /// @param vertex_id The ID of the source vertex. /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - template - [[nodiscard]] gl_attr_force_inline auto neighbors(this Self&& self, const id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline auto neighbors(this auto&& self, const id_type vertex_id) { return self.neighbor_ids(vertex_id) | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the neighbor vertex descriptors for a specific vertex. /// @copydetails detail::graph_doc_anchors::neighbors() - /// @param self The explicit object parameter. /// @param vertex The source vertex descriptor. /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. - template [[nodiscard]] gl_attr_force_inline auto neighbors( - this Self&& self, traits::c_graph_vertex auto vertex + this auto&& self, traits::c_graph_vertex auto vertex ) { return self.neighbors(vertex.id()); } @@ -571,25 +561,21 @@ class graph final { /// @brief Retrieves the predecessor vertex descriptors (incoming edges) for a vertex. /// @copydetails detail::graph_doc_anchors::predecessors() - /// @param self The explicit object parameter. /// @param vertex_id The ID of the target vertex. /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - template - [[nodiscard]] gl_attr_force_inline auto predecessors(this Self&& self, const id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline auto predecessors(this auto&& self, const id_type vertex_id) { return self.predecessor_ids(vertex_id) | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the predecessor vertex descriptors (incoming edges) for a vertex. /// @copydetails detail::graph_doc_anchors::predecessors() - /// @param self The explicit object parameter. /// @param vertex The target vertex descriptor. /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - template [[nodiscard]] gl_attr_force_inline auto predecessors( - this Self&& self, traits::c_graph_vertex auto vertex + this auto&& self, traits::c_graph_vertex auto vertex ) { return self.predecessors(vertex.id()); } @@ -617,25 +603,21 @@ class graph final { /// @brief Retrieves the successor vertex descriptors (outgoing edges) for a vertex. /// @copydetails detail::graph_doc_anchors::successors() - /// @param self The explicit object parameter. /// @param vertex_id The ID of the source vertex. /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - template - [[nodiscard]] gl_attr_force_inline auto successors(this Self&& self, const id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline auto successors(this auto&& self, const id_type vertex_id) { return self.successor_ids(vertex_id) | std::views::transform(self._create_vertex_descriptor()); } /// @brief Retrieves the successor vertex descriptors (outgoing edges) for a vertex. /// @copydetails detail::graph_doc_anchors::successors() - /// @param self The explicit object parameter. /// @param vertex The source vertex descriptor. /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. - template [[nodiscard]] gl_attr_force_inline auto successors( - this Self&& self, traits::c_graph_vertex auto vertex + this auto&& self, traits::c_graph_vertex auto vertex ) { return self.successors(vertex.id()); } @@ -661,7 +643,6 @@ class graph final { } /// @brief Retrieves a mutable reference to a vertex's properties. - /// @param self The explicit object parameter. /// @param id The ID of the vertex. /// @return A reference to the properties attached to the vertex. /// @throws std::invalid_argument If the vertex ID is invalid. @@ -676,10 +657,8 @@ class graph final { } /// @brief Retrieves a random-access view over all vertex properties in the graph. - /// @param self The explicit object parameter. /// @return A view mapping each active vertex index to its property. - template - [[nodiscard]] gl_attr_force_inline auto vertex_properties_map(this Self&& self) noexcept + [[nodiscard]] gl_attr_force_inline auto vertex_properties_map(this auto&& self) noexcept requires(traits::c_non_empty_properties) { return std::views::all(self._vertex_properties); @@ -970,13 +949,17 @@ class graph final { /// @param target_id The target vertex ID. /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex ID is invalid. - [[nodiscard]] std::optional edge(const id_type source_id, const id_type target_id) - const { - this->_verify_vertex_ids(source_id, target_id); + template + [[nodiscard]] std::optional> edge( + this Self&& self, const id_type source_id, const id_type target_id + ) { + self._verify_vertex_ids(source_id, target_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.edge(source_id, target_id, this->_edge_properties); + return self._impl.edge>( + source_id, target_id, self._edge_properties + ); else - return this->_impl.edge(source_id, target_id); + return self._impl.edge>(source_id, target_id); } /// @brief Retrieves an edge (if it exists) connecting the source to the target. @@ -984,10 +967,13 @@ class graph final { /// @param target The target vertex descriptor. /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline std::optional edge( - traits::c_graph_vertex auto source, traits::c_graph_vertex auto target - ) const { - return this->edge(source.id(), target.id()); + template + [[nodiscard]] gl_attr_force_inline std::optional> edge( + this Self&& self, + traits::c_graph_vertex auto source, + traits::c_graph_vertex auto target + ) { + return self.edge(source.id(), target.id()); } /// @brief Retrieves all parallel edges connecting the source to the target. @@ -995,14 +981,17 @@ class graph final { /// @param target_id The target vertex ID. /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex ID is invalid. - [[nodiscard]] inline std::vector edges( - const id_type source_id, const id_type target_id - ) const { - this->_verify_vertex_ids(source_id, target_id); + template + [[nodiscard]] inline std::vector> edges( + this Self&& self, const id_type source_id, const id_type target_id + ) { + self._verify_vertex_ids(source_id, target_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.edges(source_id, target_id, this->_edge_properties); + return self._impl.edges>( + source_id, target_id, self._edge_properties + ); else - return this->_impl.edges(source_id, target_id); + return self._impl.edges>(source_id, target_id); } /// @brief Retrieves all parallel edges connecting the source to the target. @@ -1010,94 +999,111 @@ class graph final { /// @param target The target vertex descriptor. /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline std::vector edges( - traits::c_graph_vertex auto source, traits::c_graph_vertex auto target - ) const { - return this->edges(source.id(), target.id()); + template + [[nodiscard]] gl_attr_force_inline std::vector> edges( + this Self&& self, + traits::c_graph_vertex auto source, + traits::c_graph_vertex auto target + ) { + return self.edges>(source.id(), target.id()); } /// @brief Retrieves all edges incident with a vertex. /// @param vertex_id The vertex ID. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto incident_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] inline auto incident_edges(this auto&& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.incident_edges(vertex_id, this->_edge_properties); + return self._impl.incident_edges>( + vertex_id, self._edge_properties + ); else - return this->_impl.incident_edges(vertex_id); + return self._impl.incident_edges>(vertex_id); } /// @brief Retrieves all edges incident with a vertex. /// @param vertex The vertex descriptor. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto incident_edges(traits::c_graph_vertex auto vertex - ) const { - return this->incident_edges(vertex.id()); + template + [[nodiscard]] gl_attr_force_inline auto incident_edges( + this auto&& self, traits::c_graph_vertex auto vertex + ) { + return self.incident_edges>(vertex.id()); } /// @brief Retrieves all incoming edges of a vertex (going into the vertex). /// @param vertex_id The vertex ID. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto in_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] inline auto in_edges(this Self&& self, const id_type vertex_id) const { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.in_edges(vertex_id, this->_edge_properties); + return self._impl.in_edges>(vertex_id, this->_edge_properties); else - return this->_impl.in_edges(vertex_id); + return self._impl.in_edges>(vertex_id); } /// @brief Retrieves all incoming edges of a vertex (going into the vertex). /// @param vertex The vertex descriptor. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto in_edges(traits::c_graph_vertex auto vertex + template + [[nodiscard]] gl_attr_force_inline auto in_edges( + this Self&& self, traits::c_graph_vertex auto vertex ) const { - return this->in_edges(vertex.id()); + return self.in_edges>(vertex.id()); } /// @brief Retrieves all outgoing edges of a vertex (going out of the vertex). /// @param vertex_id The vertex ID. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto out_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] inline auto out_edges(this auto&& self, const id_type vertex_id) const { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.out_edges(vertex_id, this->_edge_properties); + return self._impl.out_edges(vertex_id, this->_edge_properties); else - return this->_impl.out_edges(vertex_id); + return self._impl.out_edges(vertex_id); } /// @brief Retrieves all outgoing edges of a vertex (going out of the vertex). /// @param vertex The vertex descriptor. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto out_edges(traits::c_graph_vertex auto vertex + template + [[nodiscard]] gl_attr_force_inline auto out_edges( + this auto&& self, traits::c_graph_vertex auto vertex ) const { - return this->out_edges(vertex.id()); + return self.out_edges(vertex.id()); } /// @brief Retrieves a mutable reference to an edge's properties. /// @param id The ID of the edge. /// @return A reference to the properties attached to the edge. /// @throws std::invalid_argument If the edge ID is invalid. - [[nodiscard]] edge_properties_type& edge_properties(const id_type id) const + template + [[nodiscard]] gl_attr_force_inline deduced_edge_properties_type& edge_properties( + this Self&& self, const id_type id + ) requires(traits::c_non_empty_properties) { - if (id >= this->_n_edges) + if (id >= self._n_edges) throw std::invalid_argument(std::format("Got invalid edge id [{}]", id)); - return this->_edge_properties[id]; + return self._edge_properties[id]; } /// @brief Retrieves a view over all edge properties in the graph. /// @return A view mapping each active edge index to its property. - [[nodiscard]] gl_attr_force_inline auto edge_properties_map() const noexcept + [[nodiscard]] gl_attr_force_inline auto edge_properties_map(this auto&& self) noexcept requires(traits::c_non_empty_properties) { - return std::views::all(this->_edge_properties); + return std::views::all(self._edge_properties); } // --- adjacency and incidence methods --- @@ -1313,15 +1319,13 @@ class graph final { // --- transformations --- - template - gl_attr_force_inline auto _create_vertex_descriptor(this Self&&) noexcept + gl_attr_force_inline auto _create_vertex_descriptor(this auto&&) noexcept requires(traits::c_empty_properties) { return [](const id_type id) { return deduced_vertex_type{id}; }; } - template - gl_attr_force_inline auto _create_vertex_descriptor(this Self&& self) noexcept + gl_attr_force_inline auto _create_vertex_descriptor(this auto&& self) noexcept requires(traits::c_non_empty_properties) { return [&pmap = self._vertex_properties](const id_type id) { @@ -1506,7 +1510,7 @@ class graph final { implementation_type _impl{}; [[no_unique_address]] vertex_properties_map_type _vertex_properties{}; - [[no_unique_address]] mutable edge_properties_map_type _edge_properties{}; + [[no_unique_address]] edge_properties_map_type _edge_properties{}; }; // --- general graph utility --- diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index f58cb73f..b5d49271 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -30,14 +30,8 @@ namespace impl { template class adjacency_list final { public: - using representation_tag = typename GraphTraits::representation_tag; - using id_type = typename GraphTraits::id_type; - - using vertex_type = typename GraphTraits::vertex_type; - using edge_type = typename GraphTraits::edge_type; - using item_type = specialized::incidence_item; - using adjacency_storage_type = typename specialized::adjacency_list_impl_traits< - adjacency_list>::template storage_type; + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; adjacency_list() = default; @@ -121,14 +115,14 @@ class adjacency_list final { specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } - void remove_edge(const edge_type& edge) { + void remove_edge(const auto& edge) { specialized_impl::remove_edge(*this, edge); for (auto&& inc : this->_list) for (auto& item : inc) item.edge_id -= static_cast(item.edge_id > edge.id()); } - std::vector remove_edges(const traits::c_range_of auto& edges) { + std::vector remove_edges(const traits::c_range auto& edges) { for (const auto& edge : edges) specialized_impl::remove_edge(*this, edge); auto removed_edge_ids = @@ -146,93 +140,101 @@ class adjacency_list final { ); } - [[nodiscard]] gl_attr_force_inline bool has_edge(const edge_type& edge) const { + [[nodiscard]] gl_attr_force_inline bool has_edge(const auto& edge) const { return std::ranges::contains( this->_list[to_idx(edge.source())], item_type{edge.target(), edge.id()} ); } - [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { const auto& out_edges = this->_list[to_idx(source_id)]; const auto item_it = std::ranges::find(out_edges, target_id, &item_type::vertex_id); if (item_it == out_edges.cend()) return std::nullopt; - return std::make_optional(item_it->edge_id, source_id, target_id); + return std::make_optional(item_it->edge_id, source_id, target_id); } - [[nodiscard]] std::optional edge( + template + [[nodiscard]] std::optional edge( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { const auto& out_edges = this->_list[to_idx(source_id)]; const auto item_it = std::ranges::find(out_edges, target_id, [](auto item) { return item.vertex_id; }); if (item_it == out_edges.cend()) return std::nullopt; - return std::make_optional( + return std::make_optional( item_it->edge_id, source_id, target_id, edge_properties_map[to_idx(item_it->edge_id)] ); } - [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { return this->_list[source_id] | std::views::filter([&target_id](auto item) { return item.vertex_id == target_id; }) | std::views::transform([source_id](auto item) { - return edge_type{item.edge_id, source_id, item.vertex_id}; + return EdgeType{item.edge_id, source_id, item.vertex_id}; }) | std::ranges::to(); } - [[nodiscard]] std::vector edges( + template + [[nodiscard]] std::vector edges( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return this->_list[source_id] | std::views::filter([&target_id](auto item) { return item.vertex_id == target_id; }) | std::views::transform([source_id, &edge_properties_map](auto item) { - return edge_type{ + return EdgeType{ item.edge_id, source_id, item.vertex_id, edge_properties_map[item.edge_id] }; }) | std::ranges::to(); } + template [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return specialized_impl::incident_edges(*this, vertex_id); } + template [[nodiscard]] gl_attr_force_inline auto incident_edges( id_type vertex_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return specialized_impl::in_edges(*this, vertex_id) | std::views::transform([vertex_id](auto item) { - return edge_type{item.edge_id, item.vertex_id, vertex_id}; + return EdgeType{item.edge_id, item.vertex_id, vertex_id}; }); } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return specialized_impl::in_edges(*this, vertex_id) | std::views::transform([vertex_id, &edge_properties_map](auto item) { - return edge_type{ + return EdgeType{ item.edge_id, item.vertex_id, vertex_id, @@ -241,21 +243,23 @@ class adjacency_list final { }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return this->_list[to_idx(vertex_id)] | std::views::transform([vertex_id](auto item) { - return edge_type{item.edge_id, vertex_id, item.vertex_id}; + return EdgeType{item.edge_id, vertex_id, item.vertex_id}; }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return this->_list[to_idx(vertex_id)] | std::views::transform([vertex_id, &edge_properties_map](auto item) { - return edge_type{ + return EdgeType{ item.edge_id, vertex_id, item.vertex_id, @@ -278,7 +282,11 @@ class adjacency_list final { #endif private: - using specialized_impl = typename specialized::adjacency_list_impl_traits::type; + using impl_traits = specialized::adjacency_list_impl_traits; + using specialized_impl = typename impl_traits::type; + using item_type = specialized::incidence_item; + using adjacency_storage_type = typename impl_traits::template storage_type; + friend specialized_impl; void _remap_element_ids(id_type removed_vertex_id, std::vector& removed_edge_ids) { diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index 2c3204ff..2366d079 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -30,14 +30,8 @@ namespace impl { template class adjacency_matrix final { public: - using representation_tag = typename GraphTraits::representation_tag; - using id_type = typename GraphTraits::id_type; - - using vertex_type = typename GraphTraits::vertex_type; - using edge_type = typename GraphTraits::edge_type; - - using adjacency_storage_type = typename specialized::adjacency_matrix_impl_traits< - adjacency_matrix>::template storage_type; + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; adjacency_matrix() = default; @@ -123,7 +117,7 @@ class adjacency_matrix final { specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } - void remove_edge(const edge_type& edge) { + void remove_edge(const auto& edge) { specialized_impl::remove_edge(*this, edge); for (auto&& row : this->_matrix) for (auto& edge_id : row) @@ -131,7 +125,7 @@ class adjacency_matrix final { edge_id--; } - std::vector remove_edges(const traits::c_range_of auto& edges) { + std::vector remove_edges(const traits::c_range auto& edges) { for (const auto& edge : edges) specialized_impl::remove_edge(*this, edge); auto removed_edge_ids = @@ -147,79 +141,83 @@ class adjacency_matrix final { return specialized_impl::get_entry(*this, source_id, target_id) != invalid_id; } - [[nodiscard]] bool has_edge(const edge_type& edge) const { + [[nodiscard]] bool has_edge(const auto& edge) const { return specialized_impl::get_entry(*this, edge.source(), edge.target()) == edge.id(); } - [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); if (edge_id == invalid_id) return std::nullopt; - return std::make_optional(edge_id, source_id, target_id); + return std::make_optional(edge_id, source_id, target_id); } - [[nodiscard]] std::optional edge( + [[nodiscard]] std::optional edge( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); if (edge_id == invalid_id) return std::nullopt; - return std::make_optional( + return std::make_optional( edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)] ); } - [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); if (edge_id == invalid_id) - return std::vector(); - return std::vector{ - edge_type{edge_id, source_id, target_id} + return std::vector(); + return std::vector{ + EdgeType{edge_id, source_id, target_id} }; } - [[nodiscard]] std::vector edges( + [[nodiscard]] std::vector edges( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); if (edge_id == invalid_id) - return std::vector(); - return std::vector{ - edge_type{edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)]} + return std::vector(); + return std::vector{ + EdgeType{edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)]} }; } + template [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return specialized_impl::incident_edges(*this, vertex_id); } + template [[nodiscard]] gl_attr_force_inline auto incident_edges( id_type vertex_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return std::views::iota(initial_id_v, this->_matrix.size()) | std::views::filter([this, vertex_id](auto source_id) { return specialized_impl::get_entry(*this, source_id, vertex_id) != invalid_id; }) | std::views::transform([this, vertex_id](auto source_id) { - return edge_type{ + return EdgeType{ specialized_impl::get_entry(*this, source_id, vertex_id), source_id, vertex_id @@ -227,9 +225,10 @@ class adjacency_matrix final { }); } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return std::views::iota(initial_id_v, this->_matrix.size()) | std::views::filter([this, vertex_id](auto source_id) { @@ -237,14 +236,15 @@ class adjacency_matrix final { }) | std::views::transform([this, vertex_id, &edge_properties_map](auto source_id) { const auto edge_id = specialized_impl::get_entry(*this, source_id, vertex_id); - return edge_type{ + return EdgeType{ edge_id, source_id, vertex_id, edge_properties_map[to_idx(edge_id)] }; }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return this->_matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { @@ -253,13 +253,14 @@ class adjacency_matrix final { }) | std::views::transform([vertex_id](auto entry) { auto [target_id, edge_id] = entry; - return edge_type{edge_id, vertex_id, static_cast(target_id)}; + return EdgeType{edge_id, vertex_id, static_cast(target_id)}; }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return this->_matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { @@ -268,7 +269,7 @@ class adjacency_matrix final { }) | std::views::transform([vertex_id, &edge_properties_map](auto entry) { const auto [target_id, edge_id] = entry; - return edge_type{ + return EdgeType{ edge_id, vertex_id, static_cast(target_id), @@ -292,8 +293,10 @@ class adjacency_matrix final { #endif private: - using specialized_impl = - typename specialized::adjacency_matrix_impl_traits::type; + using impl_traits = specialized::adjacency_matrix_impl_traits; + using specialized_impl = typename impl_traits::type; + using adjacency_storage_type = typename impl_traits::template storage_type; + friend specialized_impl; void _remap_element_ids(std::vector& removed_edge_ids) { diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 6927fdfb..abc01cc1 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -53,12 +53,11 @@ template AdjListItem> } // namespace detail -template AdjacencyList> -requires(traits::c_directed_edge) +template struct directed_adjacency_list { - using impl_type = AdjacencyList; + using traits_type = GraphTraits; + using impl_type = adjacency_list; using id_type = typename impl_type::id_type; - using edge_type = typename impl_type::edge_type; using item_type = incidence_item; // --- vertex modifiers --- @@ -184,25 +183,30 @@ struct directed_adjacency_list { inc_edges_source.emplace_back(target_id, edge_id); } - gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { + gl_attr_force_inline static void remove_edge(impl_type& self, const auto& edge) { auto& inc_edges = self._list[to_idx(edge.source())]; inc_edges.erase(detail::strict_find(inc_edges, edge)); } // --- edge getters --- + template [[nodiscard]] gl_attr_force_inline static auto incident_edges( const impl_type& self, id_type vertex_id ) { - return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); + return util::concat( + self.template in_edges(vertex_id), + self.template out_edges(vertex_id) + ); } + template [[nodiscard]] gl_attr_force_inline static auto incident_edges( const impl_type& self, id_type vertex_id, auto& edge_properties_map ) { return util::concat( - self.in_edges(vertex_id, edge_properties_map), - self.out_edges(vertex_id, edge_properties_map) + self.template in_edges(vertex_id, edge_properties_map), + self.template out_edges(vertex_id, edge_properties_map) ); } @@ -222,12 +226,11 @@ struct directed_adjacency_list { } }; -template AdjacencyList> -requires(traits::c_undirected_edge) +template struct undirected_adjacency_list { - using impl_type = AdjacencyList; + using traits_type = GraphTraits; + using impl_type = adjacency_list; using id_type = typename impl_type::id_type; - using edge_type = typename impl_type::edge_type; using item_type = incidence_item; // --- vertex modifiers --- @@ -340,7 +343,7 @@ struct undirected_adjacency_list { } } - static void remove_edge(impl_type& self, const edge_type& edge) { + static void remove_edge(impl_type& self, const auto& edge) { auto& inc_edges_first = self._list[to_idx(edge.source())]; auto& inc_edges_second = self._list[to_idx(edge.target())]; @@ -351,16 +354,18 @@ struct undirected_adjacency_list { // --- edge getters --- + template [[nodiscard]] gl_attr_force_inline static auto incident_edges( const impl_type& self, id_type vertex_id ) { - return self.out_edges(vertex_id); + return self.template out_edges(vertex_id); } + template [[nodiscard]] gl_attr_force_inline static auto incident_edges( const impl_type& self, id_type vertex_id, auto& edge_properties_map ) { - return self.out_edges(vertex_id, edge_properties_map); + return self.template out_edges(vertex_id, edge_properties_map); } [[nodiscard]] gl_attr_force_inline static auto in_edges( @@ -370,7 +375,7 @@ struct undirected_adjacency_list { } }; -template AdjacencyList> +template struct adjacency_list_impl_traits { using type = void; @@ -378,21 +383,12 @@ struct adjacency_list_impl_traits { using storage_type = void; }; -template AdjacencyList> -requires traits::c_directed_edge - and std::same_as -struct adjacency_list_impl_traits { - using type = directed_adjacency_list; - - template - using storage_type = std::vector>; -}; - -template AdjacencyList> -requires traits::c_undirected_edge - and std::same_as -struct adjacency_list_impl_traits { - using type = undirected_adjacency_list; +template +struct adjacency_list_impl_traits { + using type = std::conditional_t< + traits::c_directed_graph_traits, + directed_adjacency_list, + undirected_adjacency_list>; template using storage_type = std::vector>; diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/specialized/flat_adjacency_list.hpp index 9424a05a..0ea3f898 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/specialized/flat_adjacency_list.hpp @@ -17,12 +17,11 @@ namespace gl::impl::specialized { -template AdjacencyList> -requires(traits::c_directed_edge) +template struct directed_flat_adjacency_list { - using impl_type = AdjacencyList; + using traits_type = GraphTraits; + using impl_type = adjacency_list; using id_type = typename impl_type::id_type; - using edge_type = typename impl_type::edge_type; using item_type = incidence_item; // --- vertex modifiers --- @@ -145,7 +144,7 @@ struct directed_flat_adjacency_list { self._list.push_back(to_idx(source_id), {target_id, edge_id}); } - gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { + gl_attr_force_inline static void remove_edge(impl_type& self, const auto& edge) { const auto edge_src = to_idx(edge.source()); auto segment = self._list[edge_src]; const auto it = detail::strict_find(segment, edge); @@ -155,18 +154,23 @@ struct directed_flat_adjacency_list { // --- edge getters --- + template [[nodiscard]] gl_attr_force_inline static auto incident_edges( const impl_type& self, id_type vertex_id ) { - return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); + return util::concat( + self.template in_edges(vertex_id), + self.template out_edges(vertex_id) + ); } + template [[nodiscard]] gl_attr_force_inline static auto incident_edges( const impl_type& self, id_type vertex_id, auto& edge_properties_map ) { return util::concat( - self.in_edges(vertex_id, edge_properties_map), - self.out_edges(vertex_id, edge_properties_map) + self.template in_edges(vertex_id, edge_properties_map), + self.template out_edges(vertex_id, edge_properties_map) ); } @@ -186,12 +190,11 @@ struct directed_flat_adjacency_list { } }; -template AdjacencyList> -requires(traits::c_undirected_edge) +template struct undirected_flat_adjacency_list { - using impl_type = AdjacencyList; + using traits_type = GraphTraits; + using impl_type = adjacency_list; using id_type = typename impl_type::id_type; - using edge_type = typename impl_type::edge_type; using item_type = incidence_item; // --- vertex modifiers --- @@ -308,7 +311,7 @@ struct undirected_flat_adjacency_list { } } - static void remove_edge(impl_type& self, const edge_type& edge) { + static void remove_edge(impl_type& self, const auto& edge) { const auto src_idx = to_idx(edge.source()); const auto tgt_idx = to_idx(edge.target()); @@ -331,16 +334,18 @@ struct undirected_flat_adjacency_list { // --- edge getters --- + template [[nodiscard]] gl_attr_force_inline static auto incident_edges( const impl_type& self, id_type vertex_id ) { - return self.out_edges(vertex_id); + return self.template out_edges(vertex_id); } + template [[nodiscard]] gl_attr_force_inline static auto incident_edges( const impl_type& self, id_type vertex_id, auto& edge_properties_map ) { - return self.out_edges(vertex_id, edge_properties_map); + return self.template out_edges(vertex_id, edge_properties_map); } [[nodiscard]] gl_attr_force_inline static auto in_edges( @@ -350,21 +355,12 @@ struct undirected_flat_adjacency_list { } }; -template AdjacencyList> -requires traits::c_directed_edge - and std::same_as -struct adjacency_list_impl_traits { - using type = directed_flat_adjacency_list; - - template - using storage_type = flat_jagged_vector; -}; - -template AdjacencyList> -requires traits::c_undirected_edge - and std::same_as -struct adjacency_list_impl_traits { - using type = undirected_flat_adjacency_list; +template +struct adjacency_list_impl_traits { + using type = std::conditional_t< + traits::c_directed_graph_traits, + directed_flat_adjacency_list, + undirected_flat_adjacency_list>; template using storage_type = flat_jagged_vector; diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index 5d43221b..14bba1ca 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -119,7 +119,7 @@ constexpr gl::size_type n_inc_edges_for_fully_connected_vertex = constants::n_el template struct test_directed_adjacency_list : public test_adjacency_list { using sut_type = SutType; - using edge_type = typename sut_type::edge_type; + using edge_type = typename sut_type::traits_type::edge_type; edge_type add_edge(const auto source_id, const auto target_id) { const auto new_edge_id = this->next_edge_id++; @@ -466,7 +466,7 @@ TEST_CASE_TEMPLATE_INSTANTIATE( template struct test_undirected_adjacency_list : public test_adjacency_list { using sut_type = SutType; - using edge_type = typename sut_type::edge_type; + using edge_type = typename sut_type::traits_type::edge_type; edge_type add_edge(const auto source_id, const auto target_id) { const auto new_edge_id = this->next_edge_id++; From 32c1271d191cc6b0cc3435bd2594ca8589bb8712 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Fri, 24 Jul 2026 22:21:31 +0200 Subject: [PATCH 07/13] wip: adjacency list mixin refactor --- include/gl/graph.hpp | 115 +++---- include/gl/impl/adjacency_list.hpp | 108 +++---- .../gl/impl/specialized/adjacency_list.hpp | 285 ++++++----------- .../impl/specialized/flat_adjacency_list.hpp | 302 +++++++----------- 4 files changed, 302 insertions(+), 508 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index d60cd2e7..5e9099c8 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -949,17 +949,13 @@ class graph final { /// @param target_id The target vertex ID. /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex ID is invalid. - template - [[nodiscard]] std::optional> edge( - this Self&& self, const id_type source_id, const id_type target_id - ) { - self._verify_vertex_ids(source_id, target_id); + [[nodiscard]] std::optional edge(const id_type source_id, const id_type target_id) + const { + this->_verify_vertex_ids(source_id, target_id); if constexpr (traits::c_non_empty_properties) - return self._impl.edge>( - source_id, target_id, self._edge_properties - ); + return this->_impl.edge(source_id, target_id, this->_edge_properties); else - return self._impl.edge>(source_id, target_id); + return this->_impl.edge(source_id, target_id); } /// @brief Retrieves an edge (if it exists) connecting the source to the target. @@ -967,13 +963,10 @@ class graph final { /// @param target The target vertex descriptor. /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. - template - [[nodiscard]] gl_attr_force_inline std::optional> edge( - this Self&& self, - traits::c_graph_vertex auto source, - traits::c_graph_vertex auto target - ) { - return self.edge(source.id(), target.id()); + [[nodiscard]] gl_attr_force_inline std::optional edge( + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target + ) const { + return this->edge(source.id(), target.id()); } /// @brief Retrieves all parallel edges connecting the source to the target. @@ -981,17 +974,14 @@ class graph final { /// @param target_id The target vertex ID. /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex ID is invalid. - template - [[nodiscard]] inline std::vector> edges( - this Self&& self, const id_type source_id, const id_type target_id - ) { - self._verify_vertex_ids(source_id, target_id); + [[nodiscard]] inline std::vector edges( + const id_type source_id, const id_type target_id + ) const { + this->_verify_vertex_ids(source_id, target_id); if constexpr (traits::c_non_empty_properties) - return self._impl.edges>( - source_id, target_id, self._edge_properties - ); + return this->_impl.edges(source_id, target_id, this->_edge_properties); else - return self._impl.edges>(source_id, target_id); + return this->_impl.edges(source_id, target_id); } /// @brief Retrieves all parallel edges connecting the source to the target. @@ -999,111 +989,94 @@ class graph final { /// @param target The target vertex descriptor. /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex descriptor is invalid. - template - [[nodiscard]] gl_attr_force_inline std::vector> edges( - this Self&& self, - traits::c_graph_vertex auto source, - traits::c_graph_vertex auto target - ) { - return self.edges>(source.id(), target.id()); + [[nodiscard]] gl_attr_force_inline std::vector edges( + traits::c_graph_vertex auto source, traits::c_graph_vertex auto target + ) const { + return this->edges(source.id(), target.id()); } /// @brief Retrieves all edges incident with a vertex. /// @param vertex_id The vertex ID. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex ID is invalid. - template - [[nodiscard]] inline auto incident_edges(this auto&& self, const id_type vertex_id) { - self._verify_vertex_id(vertex_id); + [[nodiscard]] inline auto incident_edges(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return self._impl.incident_edges>( - vertex_id, self._edge_properties - ); + return this->_impl.incident_edges(vertex_id, this->_edge_properties); else - return self._impl.incident_edges>(vertex_id); + return this->_impl.incident_edges(vertex_id); } /// @brief Retrieves all edges incident with a vertex. /// @param vertex The vertex descriptor. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - template - [[nodiscard]] gl_attr_force_inline auto incident_edges( - this auto&& self, traits::c_graph_vertex auto vertex - ) { - return self.incident_edges>(vertex.id()); + [[nodiscard]] gl_attr_force_inline auto incident_edges(traits::c_graph_vertex auto vertex + ) const { + return this->incident_edges(vertex.id()); } /// @brief Retrieves all incoming edges of a vertex (going into the vertex). /// @param vertex_id The vertex ID. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex ID is invalid. - template - [[nodiscard]] inline auto in_edges(this Self&& self, const id_type vertex_id) const { - self._verify_vertex_id(vertex_id); + [[nodiscard]] inline auto in_edges(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return self._impl.in_edges>(vertex_id, this->_edge_properties); + return this->_impl.in_edges(vertex_id, this->_edge_properties); else - return self._impl.in_edges>(vertex_id); + return this->_impl.in_edges(vertex_id); } /// @brief Retrieves all incoming edges of a vertex (going into the vertex). /// @param vertex The vertex descriptor. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - template - [[nodiscard]] gl_attr_force_inline auto in_edges( - this Self&& self, traits::c_graph_vertex auto vertex + [[nodiscard]] gl_attr_force_inline auto in_edges(traits::c_graph_vertex auto vertex ) const { - return self.in_edges>(vertex.id()); + return this->in_edges(vertex.id()); } /// @brief Retrieves all outgoing edges of a vertex (going out of the vertex). /// @param vertex_id The vertex ID. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex ID is invalid. - template - [[nodiscard]] inline auto out_edges(this auto&& self, const id_type vertex_id) const { - self._verify_vertex_id(vertex_id); + [[nodiscard]] inline auto out_edges(const id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return self._impl.out_edges(vertex_id, this->_edge_properties); + return this->_impl.out_edges(vertex_id, this->_edge_properties); else - return self._impl.out_edges(vertex_id); + return this->_impl.out_edges(vertex_id); } /// @brief Retrieves all outgoing edges of a vertex (going out of the vertex). /// @param vertex The vertex descriptor. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - template - [[nodiscard]] gl_attr_force_inline auto out_edges( - this auto&& self, traits::c_graph_vertex auto vertex + [[nodiscard]] gl_attr_force_inline auto out_edges(traits::c_graph_vertex auto vertex ) const { - return self.out_edges(vertex.id()); + return this->out_edges(vertex.id()); } /// @brief Retrieves a mutable reference to an edge's properties. /// @param id The ID of the edge. /// @return A reference to the properties attached to the edge. /// @throws std::invalid_argument If the edge ID is invalid. - template - [[nodiscard]] gl_attr_force_inline deduced_edge_properties_type& edge_properties( - this Self&& self, const id_type id - ) + [[nodiscard]] edge_properties_type& edge_properties(const id_type id) const requires(traits::c_non_empty_properties) { - if (id >= self._n_edges) + if (id >= this->_n_edges) throw std::invalid_argument(std::format("Got invalid edge id [{}]", id)); - return self._edge_properties[id]; + return this->_edge_properties[id]; } /// @brief Retrieves a view over all edge properties in the graph. /// @return A view mapping each active edge index to its property. - [[nodiscard]] gl_attr_force_inline auto edge_properties_map(this auto&& self) noexcept + [[nodiscard]] gl_attr_force_inline auto edge_properties_map() const noexcept requires(traits::c_non_empty_properties) { - return std::views::all(self._edge_properties); + return std::views::all(this->_edge_properties); } // --- adjacency and incidence methods --- @@ -1510,7 +1483,7 @@ class graph final { implementation_type _impl{}; [[no_unique_address]] vertex_properties_map_type _vertex_properties{}; - [[no_unique_address]] edge_properties_map_type _edge_properties{}; + [[no_unique_address]] mutable edge_properties_map_type _edge_properties{}; }; // --- general graph utility --- diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index b5d49271..dcf85860 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -28,10 +28,11 @@ struct to_impl; namespace impl { template -class adjacency_list final { +class adjacency_list final : public specialized::adjacency_list_impl_traits::type { public: using traits_type = GraphTraits; using id_type = typename traits_type::id_type; + using item_type = specialized::incidence_item; adjacency_list() = default; @@ -56,67 +57,38 @@ class adjacency_list final { } std::vector remove_vertex(id_type vertex_id) { - auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); + auto removed_edge_ids = this->_remove_vertex_impl(vertex_id); this->_remap_element_ids(vertex_id, removed_edge_ids); return removed_edge_ids; } // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline auto neighbor_ids(id_type vertex_id) const { - return specialized_impl::neighbor_ids(*this, vertex_id); + [[nodiscard]] auto neighbor_ids(id_type vertex_id) const { + if constexpr (traits::c_directed_graph_traits) + return util::concat(this->successor_ids(vertex_id), this->predecessor_ids(vertex_id)); + else + return this->_list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); } - [[nodiscard]] gl_attr_force_inline auto predecessor_ids(id_type vertex_id) const { - return specialized_impl::predecessor_ids(*this, vertex_id); + [[nodiscard]] auto predecessor_ids(id_type vertex_id) const { + if constexpr (traits::c_directed_graph_traits) + return this->_in_edges_impl(vertex_id) | std::views::transform(&item_type::vertex_id); + else + return this->neighbor_ids(vertex_id); } [[nodiscard]] gl_attr_force_inline auto successor_ids(id_type vertex_id) const { - return specialized_impl::successor_ids(*this, vertex_id); - } - - // --- degree getters --- - - [[nodiscard]] gl_attr_force_inline size_type degree(id_type vertex_id) const { - return specialized_impl::degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type in_degree(id_type vertex_id) const { - return specialized_impl::in_degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type out_degree(id_type vertex_id) const { - return specialized_impl::out_degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { - return specialized_impl::degree_map(*this); - } - - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { - return specialized_impl::in_degree_map(*this); - } - - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { - return specialized_impl::out_degree_map(*this); + if constexpr (traits::c_directed_graph_traits) + return this->_list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); + else + return this->neighbor_ids(vertex_id); } // --- edge modifiers --- - gl_attr_force_inline void add_edge(id_type id, id_type source_id, id_type target_id) { - specialized_impl::add_edge(*this, id, source_id, target_id); - } - - gl_attr_force_inline void add_edges_from( - const traits::c_forward_range_of auto& edge_ids, - id_type source_id, - const traits::c_forward_range_of auto& target_ids - ) { - specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); - } - void remove_edge(const auto& edge) { - specialized_impl::remove_edge(*this, edge); + this->_remove_edge_impl(edge); for (auto&& inc : this->_list) for (auto& item : inc) item.edge_id -= static_cast(item.edge_id > edge.id()); @@ -124,7 +96,8 @@ class adjacency_list final { std::vector remove_edges(const traits::c_range auto& edges) { for (const auto& edge : edges) - specialized_impl::remove_edge(*this, edge); + this->_remove_edge_impl(edge); + auto removed_edge_ids = edges | std::views::transform([](const auto& edge) { return edge.id(); }) | std::ranges::to(); @@ -164,8 +137,7 @@ class adjacency_list final { requires(traits::c_has_non_empty_properties) { const auto& out_edges = this->_list[to_idx(source_id)]; - const auto item_it = - std::ranges::find(out_edges, target_id, [](auto item) { return item.vertex_id; }); + const auto item_it = std::ranges::find(out_edges, target_id, &item_type::vertex_id); if (item_it == out_edges.cend()) return std::nullopt; return std::make_optional( @@ -191,11 +163,14 @@ class adjacency_list final { ) const requires(traits::c_has_non_empty_properties) { - return this->_list[source_id] + return this->_list[to_idx(source_id)] | std::views::filter([&target_id](auto item) { return item.vertex_id == target_id; }) | std::views::transform([source_id, &edge_properties_map](auto item) { return EdgeType{ - item.edge_id, source_id, item.vertex_id, edge_properties_map[item.edge_id] + item.edge_id, + source_id, + item.vertex_id, + edge_properties_map[to_idx(item.edge_id)] }; }) | std::ranges::to(); @@ -205,7 +180,15 @@ class adjacency_list final { [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const requires(traits::c_has_empty_properties) { - return specialized_impl::incident_edges(*this, vertex_id); + if constexpr (traits::c_directed_graph_traits) { + return util::concat( + this->template in_edges(vertex_id), + this->template out_edges(vertex_id) + ); + } + else { + return this->template out_edges(vertex_id); + } } template @@ -214,15 +197,22 @@ class adjacency_list final { ) const requires(traits::c_has_non_empty_properties) { - return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); + if constexpr (traits::c_directed_graph_traits) { + return util::concat( + this->template in_edges(vertex_id, edge_properties_map), + this->template out_edges(vertex_id, edge_properties_map) + ); + } + else { + return this->template out_edges(vertex_id, edge_properties_map); + } } template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const requires(traits::c_has_empty_properties) { - return specialized_impl::in_edges(*this, vertex_id) - | std::views::transform([vertex_id](auto item) { + return this->_in_edges_impl(vertex_id) | std::views::transform([vertex_id](auto item) { return EdgeType{item.edge_id, item.vertex_id, vertex_id}; }); } @@ -232,7 +222,7 @@ class adjacency_list final { const requires(traits::c_has_non_empty_properties) { - return specialized_impl::in_edges(*this, vertex_id) + return this->_in_edges_impl(vertex_id) | std::views::transform([vertex_id, &edge_properties_map](auto item) { return EdgeType{ item.edge_id, @@ -282,12 +272,10 @@ class adjacency_list final { #endif private: - using impl_traits = specialized::adjacency_list_impl_traits; - using specialized_impl = typename impl_traits::type; - using item_type = specialized::incidence_item; + using impl_traits = specialized::adjacency_list_impl_traits; using adjacency_storage_type = typename impl_traits::template storage_type; - friend specialized_impl; + friend typename impl_traits::type; void _remap_element_ids(id_type removed_vertex_id, std::vector& removed_edge_ids) { std::ranges::sort(removed_edge_ids); diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index abc01cc1..8a594a2c 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -54,68 +54,19 @@ template AdjListItem> } // namespace detail template -struct directed_adjacency_list { +class directed_adjacency_list { +public: using traits_type = GraphTraits; - using impl_type = adjacency_list; - using id_type = typename impl_type::id_type; + using id_type = typename traits_type::id_type; using item_type = incidence_item; - // --- vertex modifiers --- - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); - - // remove all edges incident to the vertex - for (auto idx = 0uz; idx < self._list.size(); ++idx) { - auto& inc_edges = self._list[idx]; - if (idx == vertex_idx or inc_edges.empty()) - continue; - - const auto removed_subrng = - std::ranges::remove_if(inc_edges, [vertex_id, &removed_edges](auto item) { - if (item.vertex_id == vertex_id) { - removed_edges.push_back(item.edge_id); - return true; - } - return false; - }); - inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); - } - - // remove the list of edges incident from the vertex entirely - self._list.erase(self._list.begin() + to_diff(vertex_id)); - return removed_edges; - } - - // --- vertex getters --- - - [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { - return util::concat(successor_ids(self, vertex_id), predecessor_ids(self, vertex_id)); - } - - [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { - return in_edges(self, vertex_id) | std::views::transform(&item_type::vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); - } - // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { - return in_degree(self, vertex_id) + out_degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type degree(this auto&& self, id_type vertex_id) { + return self.in_degree(vertex_id) + self.out_degree(vertex_id); } - [[nodiscard]] static size_type in_degree(const impl_type& self, id_type vertex_id) { + [[nodiscard]] size_type in_degree(this auto&& self, id_type vertex_id) { size_type in_deg = 0uz; for (const auto& out_edges : self._list) in_deg += static_cast( @@ -125,26 +76,23 @@ struct directed_adjacency_list { return in_deg; } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type out_degree(this auto&& self, id_type vertex_id) { return self._list[to_idx(vertex_id)].size(); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this auto&& self) { std::vector degree_map(self._list.size(), 0uz); for (auto idx = 0uz; idx < self._list.size(); ++idx) { degree_map[idx] += self._list[idx].size(); - std::ranges::for_each(self._list[idx], [°ree_map](auto item) { + for (auto item : self._list[idx]) ++degree_map[to_idx(item.vertex_id)]; - }); } return degree_map; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + [[nodiscard]] std::vector in_degree_map(this auto&& self) { std::vector in_degree_map(self._list.size(), 0uz); for (const auto& inc_edges : self._list) @@ -154,63 +102,73 @@ struct directed_adjacency_list { return in_degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self - ) { - return self._list - | std::views::transform([](const auto& inc_edges) { return inc_edges.size(); }) + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this auto&& self) { + return self._list | std::views::transform([](auto&& inc_edges) { return inc_edges.size(); }) | std::ranges::to>(); } // --- edge modifiers --- - gl_attr_force_inline static void add_edge( - impl_type& self, id_type edge_id, id_type source_id, id_type target_id + gl_attr_force_inline void add_edge( + this auto&& self, id_type edge_id, id_type source_id, id_type target_id ) { self._list[to_idx(source_id)].emplace_back(target_id, edge_id); } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto&& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids ) { auto& inc_edges_source = self._list[to_idx(source_id)]; - inc_edges_source.reserve(inc_edges_source.size() + target_ids.size()); + inc_edges_source.reserve(inc_edges_source.size() + std::ranges::size(target_ids)); for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) inc_edges_source.emplace_back(target_id, edge_id); } - gl_attr_force_inline static void remove_edge(impl_type& self, const auto& edge) { - auto& inc_edges = self._list[to_idx(edge.source())]; - inc_edges.erase(detail::strict_find(inc_edges, edge)); - } +protected: + // --- vertex modifiers --- - // --- edge getters --- + std::vector _remove_vertex_impl(this auto&& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); - template - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return util::concat( - self.template in_edges(vertex_id), - self.template out_edges(vertex_id) - ); + auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + + // remove all edges incident to the vertex + for (auto idx = 0uz; idx < self._list.size(); ++idx) { + auto& inc_edges = self._list[idx]; + if (idx == vertex_idx or inc_edges.empty()) + continue; + + const auto removed_subrng = + std::ranges::remove_if(inc_edges, [vertex_id, &removed_edges](auto item) { + if (item.vertex_id == vertex_id) { + removed_edges.push_back(item.edge_id); + return true; + } + return false; + }); + inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); + } + + self._list.erase(self._list.begin() + to_diff(vertex_id)); + return removed_edges; } - template - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return util::concat( - self.template in_edges(vertex_id, edge_properties_map), - self.template out_edges(vertex_id, edge_properties_map) - ); + // --- edge modifiers --- + + gl_attr_force_inline void _remove_edge_impl(this auto&& self, const auto& edge) { + auto& inc_edges = self._list[to_idx(edge.source())]; + inc_edges.erase(detail::strict_find(inc_edges, edge)); } - [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { + // --- edge getters --- + + [[nodiscard]] auto _in_edges_impl(this auto&& self, id_type vertex_id) { std::vector in_edges; for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { auto in_edges_view = @@ -218,7 +176,7 @@ struct directed_adjacency_list { return item.vertex_id == tgt_id; }) | std::views::transform([src_id](auto item) { - return incidence_item{src_id, item.edge_id}; + return item_type{src_id, item.edge_id}; }); in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); } @@ -227,114 +185,63 @@ struct directed_adjacency_list { }; template -struct undirected_adjacency_list { +class undirected_adjacency_list { +public: using traits_type = GraphTraits; - using impl_type = adjacency_list; - using id_type = typename impl_type::id_type; + using id_type = typename traits_type::id_type; using item_type = incidence_item; - // --- vertex modifiers --- - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - // remove all edges incident with the vertex (scan only the selected vertices) - for (auto item : self._list[vertex_idx]) { - if (item.vertex_id == vertex_id) - continue; // will be removed with the vertex's list - - auto& inc_edges = self._list[to_idx(item.vertex_id)]; - const auto removed_subrng = std::ranges::remove_if(inc_edges, [vertex_id](auto item) { - return item.vertex_id == vertex_id; - }); - inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); - } - - // remove the list of edges incident from the vertex entirely - const auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); - self._list.erase(self._list.begin() + to_diff(vertex_id)); - return removed_edges; - } - - // --- vertex getters --- - - [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); - } - // --- degree getters --- - [[nodiscard]] static size_type degree(const impl_type& self, id_type vertex_id) { + [[nodiscard]] size_type degree(this auto&& self, id_type vertex_id) { size_type degree = 0uz; for (auto item : self._list[to_idx(vertex_id)]) degree += 1uz + static_cast(item.vertex_id == vertex_id); return degree; } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type in_degree(this auto&& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type out_degree(this auto&& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this auto&& self) { std::vector degree_map; degree_map.reserve(self._list.size()); for (id_type id = initial_id; id < self._list.size(); ++id) - degree_map.push_back(degree(self, id)); + degree_map.push_back(self.degree(id)); return degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this auto&& self) { + return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this auto&& self) { + return self.degree_map(); } // --- edge modifiers --- - static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { + gl_attr_force_inline void add_edge( + this auto&& self, id_type edge_id, id_type source_id, id_type target_id + ) { self._list[to_idx(source_id)].emplace_back(target_id, edge_id); if (target_id != source_id) self._list[to_idx(target_id)].emplace_back(source_id, edge_id); } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto&& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids ) { auto& inc_edges_source = self._list[to_idx(source_id)]; - inc_edges_source.reserve(inc_edges_source.size() + target_ids.size()); + inc_edges_source.reserve(inc_edges_source.size() + std::ranges::size(target_ids)); for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) { inc_edges_source.emplace_back(target_id, edge_id); @@ -343,7 +250,33 @@ struct undirected_adjacency_list { } } - static void remove_edge(impl_type& self, const auto& edge) { +protected: + // --- vertex modifiers --- + + std::vector _remove_vertex_impl(this auto&& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + for (auto item : self._list[vertex_idx]) { + if (item.vertex_id == vertex_id) + continue; + + auto& inc_edges = self._list[to_idx(item.vertex_id)]; + const auto removed_subrng = std::ranges::remove_if(inc_edges, [vertex_id](auto item) { + return item.vertex_id == vertex_id; + }); + inc_edges.erase(removed_subrng.begin(), removed_subrng.end()); + } + + const auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + self._list.erase(self._list.begin() + to_diff(vertex_id)); + return removed_edges; + } + + // --- edge modifiers --- + + void _remove_edge_impl(this auto&& self, const auto& edge) { auto& inc_edges_first = self._list[to_idx(edge.source())]; auto& inc_edges_second = self._list[to_idx(edge.target())]; @@ -354,23 +287,7 @@ struct undirected_adjacency_list { // --- edge getters --- - template - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return self.template out_edges(vertex_id); - } - - template - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return self.template out_edges(vertex_id, edge_properties_map); - } - - [[nodiscard]] gl_attr_force_inline static auto in_edges( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline auto _in_edges_impl(this auto&& self, id_type vertex_id) { return std::views::all(self._list[to_idx(vertex_id)]); } }; diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/specialized/flat_adjacency_list.hpp index 0ea3f898..f0804070 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/specialized/flat_adjacency_list.hpp @@ -18,107 +18,48 @@ namespace gl::impl::specialized { template -struct directed_flat_adjacency_list { +class directed_flat_adjacency_list { +public: using traits_type = GraphTraits; - using impl_type = adjacency_list; - using id_type = typename impl_type::id_type; + using id_type = typename traits_type::id_type; using item_type = incidence_item; - // --- vertex modifiers --- - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - // extract out-edges - using item_type = typename impl_type::item_type; - auto removed_edges = - self._list[vertex_idx] | std::views::transform(&item_type::edge_id) - | std::ranges::to(); - - // rebuild the graph (faster then shifting the entire data block for each removed edge) - typename impl_type::adjacency_storage_type new_list; - new_list.reserve_segments(self._list.size() - 1uz); - new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); - - std::vector buffer; - for (auto idx = 0uz; idx < self._list.size(); ++idx) { - if (idx == vertex_idx) - continue; - - buffer.clear(); - for (auto item : self._list[idx]) { - if (item.vertex_id == vertex_id) - removed_edges.push_back(item.edge_id); // remove in-edge - else - buffer.push_back(item); - } - new_list.push_back(buffer); - } - - self._list = std::move(new_list); - return removed_edges; - } - - // --- vertex getters --- - - [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { - return util::concat(predecessor_ids(self, vertex_id), successor_ids(self, vertex_id)); - } - - [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { - return in_edges(self, vertex_id) | std::views::transform(&item_type::vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); - } - // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { - return in_degree(self, vertex_id) + out_degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type degree(this auto&& self, id_type vertex_id) { + return self.in_degree(vertex_id) + self.out_degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type in_degree(this auto&& self, id_type vertex_id) { return static_cast( std::ranges::count(self._list.data_view(), vertex_id, &item_type::vertex_id) ); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)].size(); + [[nodiscard]] gl_attr_force_inline size_type out_degree(this auto&& self, id_type vertex_id) { + return self._list.segment_size(to_idx(vertex_id)); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this auto&& self) { std::vector degree_map(self._list.size(), 0uz); for (auto idx = 0uz; idx < self._list.size(); ++idx) { degree_map[idx] += self._list.segment_size(idx); for (auto item : self._list[idx]) - ++degree_map[item.vertex_id]; + ++degree_map[to_idx(item.vertex_id)]; } return degree_map; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + [[nodiscard]] std::vector in_degree_map(this auto&& self) { std::vector in_degree_map(self._list.size(), 0uz); for (auto item : self._list.data_view()) ++in_degree_map[to_idx(item.vertex_id)]; return in_degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self - ) { + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this auto&& self) { std::vector out_degree; out_degree.reserve(self._list.size()); for (auto idx = 0uz; idx < self._list.size(); ++idx) @@ -128,87 +69,35 @@ struct directed_flat_adjacency_list { // --- edge modifiers --- - gl_attr_force_inline static void add_edge( - impl_type& self, id_type edge_id, id_type source_id, id_type target_id + gl_attr_force_inline void add_edge( + this auto&& self, id_type edge_id, id_type source_id, id_type target_id ) { - self._list.push_back(to_idx(source_id), {target_id, edge_id}); + self._list.emplace_back(to_idx(source_id), target_id, edge_id); } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto&& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids ) { for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) - self._list.push_back(to_idx(source_id), {target_id, edge_id}); - } - - gl_attr_force_inline static void remove_edge(impl_type& self, const auto& edge) { - const auto edge_src = to_idx(edge.source()); - auto segment = self._list[edge_src]; - const auto it = detail::strict_find(segment, edge); - const auto pos = static_cast(std::distance(segment.begin(), it)); - self._list.erase(edge_src, pos); - } - - // --- edge getters --- - - template - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return util::concat( - self.template in_edges(vertex_id), - self.template out_edges(vertex_id) - ); - } - - template - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return util::concat( - self.template in_edges(vertex_id, edge_properties_map), - self.template out_edges(vertex_id, edge_properties_map) - ); - } - - [[nodiscard]] static auto in_edges(const impl_type& self, id_type vertex_id) { - std::vector in_edges; - for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { - auto in_edges_view = - self._list[to_idx(src_id)] | std::views::filter([tgt_id = vertex_id](auto item) { - return item.vertex_id == tgt_id; - }) - | std::views::transform([src_id](auto item) { - return incidence_item{src_id, item.edge_id}; - }); - in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); - } - return in_edges; + self._list.emplace_back(to_idx(source_id), target_id, edge_id); } -}; - -template -struct undirected_flat_adjacency_list { - using traits_type = GraphTraits; - using impl_type = adjacency_list; - using id_type = typename impl_type::id_type; - using item_type = incidence_item; +protected: // --- vertex modifiers --- - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto&& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); - // all removed edges are stored in the vertex's list segment + // extract out-edges auto removed_edges = self._list[vertex_idx] | std::views::transform(&item_type::edge_id) | std::ranges::to(); // rebuild the graph (faster then shifting the entire data block for each removed edge) - typename impl_type::adjacency_storage_type new_list; + typename adjacency_list_impl_traits::storage_type new_list; new_list.reserve_segments(self._list.size() - 1uz); new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); @@ -218,10 +107,12 @@ struct undirected_flat_adjacency_list { continue; buffer.clear(); - for (auto item : self._list[idx]) - if (item.vertex_id != vertex_id) + for (auto item : self._list[idx]) { + if (item.vertex_id == vertex_id) + removed_edges.push_back(item.edge_id); // remove in-edge + else buffer.push_back(item); - + } new_list.push_back(buffer); } @@ -229,77 +120,84 @@ struct undirected_flat_adjacency_list { return removed_edges; } - // --- vertex getters --- + // --- edge modifiers --- - [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)] | std::views::transform(&item_type::vertex_id); + gl_attr_force_inline void _remove_edge_impl(this auto&& self, const auto& edge) { + const auto edge_src = to_idx(edge.source()); + auto segment = self._list[edge_src]; + const auto it = detail::strict_find(segment, edge); + const auto pos = static_cast(std::distance(segment.begin(), it)); + self._list.erase(edge_src, pos); } - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); - } + // --- edge getters --- - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); + [[nodiscard]] auto _in_edges_impl(this auto&& self, id_type vertex_id) { + std::vector in_edges; + for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { + auto in_edges_view = + self._list[to_idx(src_id)] | std::views::filter([tgt_id = vertex_id](auto item) { + return item.vertex_id == tgt_id; + }) + | std::views::transform([src_id](auto item) { + return item_type{src_id, item.edge_id}; + }); + in_edges.insert(in_edges.end(), in_edges_view.begin(), in_edges_view.end()); + } + return in_edges; } +}; + +template +class undirected_flat_adjacency_list { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using item_type = incidence_item; // --- degree getters --- - [[nodiscard]] static size_type degree(const impl_type& self, id_type vertex_id) { + [[nodiscard]] size_type degree(this auto&& self, id_type vertex_id) { size_type degree = 0uz; for (auto item : self._list[to_idx(vertex_id)]) degree += 1uz + static_cast(item.vertex_id == vertex_id); return degree; } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type in_degree(this auto&& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type out_degree(this auto&& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this auto&& self) { std::vector degree_map; degree_map.reserve(self._list.size()); for (id_type id = initial_id; id < self._list.size(); ++id) - degree_map.push_back(degree(self, id)); + degree_map.push_back(self.degree(id)); return degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this auto&& self) { + return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this auto&& self) { + return self.degree_map(); } // --- edge modifiers --- - static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { + void add_edge(this auto&& self, id_type edge_id, id_type source_id, id_type target_id) { self._list.push_back(to_idx(source_id), {target_id, edge_id}); if (target_id != source_id) self._list.push_back(to_idx(target_id), {source_id, edge_id}); } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto&& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -311,7 +209,42 @@ struct undirected_flat_adjacency_list { } } - static void remove_edge(impl_type& self, const auto& edge) { +protected: + // --- vertex modifiers --- + + std::vector _remove_vertex_impl(this auto&& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + // all removed edges are stored in the vertex's list segment + auto removed_edges = + self._list[vertex_idx] | std::views::transform(&item_type::edge_id) + | std::ranges::to(); + + // rebuild the graph (faster then shifting the entire data block for each removed edge) + typename adjacency_list_impl_traits::storage_type new_list; + new_list.reserve_segments(self._list.size() - 1uz); + new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); + + std::vector buffer; + for (auto idx = 0uz; idx < self._list.size(); ++idx) { + if (idx == vertex_idx) + continue; + + buffer.clear(); + for (auto item : self._list[idx]) + if (item.vertex_id != vertex_id) + buffer.push_back(item); + + new_list.push_back(buffer); + } + + self._list = std::move(new_list); + return removed_edges; + } + + // --- edge modifiers --- + + void _remove_edge_impl(this auto&& self, const auto& edge) { const auto src_idx = to_idx(edge.source()); const auto tgt_idx = to_idx(edge.target()); @@ -323,8 +256,7 @@ struct undirected_flat_adjacency_list { self._list.erase(src_idx, pos); } - // remove from the target segment (if edge not a self-loop) - if (src_idx != tgt_idx) { + if (not edge.is_loop()) { auto inc_edges = self._list[tgt_idx]; const auto it = detail::strict_find(inc_edges, edge); const auto pos = static_cast(std::distance(inc_edges.begin(), it)); @@ -334,24 +266,8 @@ struct undirected_flat_adjacency_list { // --- edge getters --- - template - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return self.template out_edges(vertex_id); - } - - template - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return self.template out_edges(vertex_id, edge_properties_map); - } - - [[nodiscard]] gl_attr_force_inline static auto in_edges( - const impl_type& self, id_type vertex_id - ) { - return self._list[to_idx(vertex_id)]; + [[nodiscard]] gl_attr_force_inline auto _in_edges_impl(this auto&& self, id_type vertex_id) { + return std::views::all(self._list[to_idx(vertex_id)]); } }; From b0a4a411c111f38e823184fa9ed448f8020d1230 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 27 Jul 2026 00:23:34 +0200 Subject: [PATCH 08/13] working refactored adjacency list??? --- include/gl/graph.hpp | 6 +- include/gl/impl/adjacency_list.hpp | 15 ++-- include/gl/impl/adjacency_matrix.hpp | 81 +++++++++---------- .../gl/impl/specialized/adjacency_list.hpp | 27 ++++--- .../impl/specialized/flat_adjacency_list.hpp | 22 +++-- 5 files changed, 84 insertions(+), 67 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 5e9099c8..e93ace05 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -1292,13 +1292,15 @@ class graph final { // --- transformations --- - gl_attr_force_inline auto _create_vertex_descriptor(this auto&&) noexcept + template + gl_attr_force_inline auto _create_vertex_descriptor(this Self&&) noexcept requires(traits::c_empty_properties) { return [](const id_type id) { return deduced_vertex_type{id}; }; } - gl_attr_force_inline auto _create_vertex_descriptor(this auto&& self) noexcept + template + gl_attr_force_inline auto _create_vertex_descriptor(this Self&& self) noexcept requires(traits::c_non_empty_properties) { return [&pmap = self._vertex_properties](const id_type id) { diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index dcf85860..8e4955cf 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -28,11 +28,13 @@ struct to_impl; namespace impl { template -class adjacency_list final : public specialized::adjacency_list_impl_traits::type { +class adjacency_list final : public specialized::adjacency_list_base_t { public: using traits_type = GraphTraits; + using base_type = specialized::adjacency_list_base_t; using id_type = typename traits_type::id_type; - using item_type = specialized::incidence_item; + using item_type = typename base_type::item_type; + using storage_type = typename base_type::storage_type; adjacency_list() = default; @@ -264,6 +266,8 @@ class adjacency_list final : public specialized::adjacency_list_impl_traits friend struct gl::detail::to_impl; @@ -272,11 +276,6 @@ class adjacency_list final : public specialized::adjacency_list_impl_traits; - using adjacency_storage_type = typename impl_traits::template storage_type; - - friend typename impl_traits::type; - void _remap_element_ids(id_type removed_vertex_id, std::vector& removed_edge_ids) { std::ranges::sort(removed_edge_ids); removed_edge_ids.erase( @@ -298,7 +297,7 @@ class adjacency_list final : public specialized::adjacency_list_impl_traits class adjacency_matrix final { public: - using traits_type = GraphTraits; - using id_type = typename traits_type::id_type; + using representation_tag = typename GraphTraits::representation_tag; + using id_type = typename GraphTraits::id_type; + + using vertex_type = typename GraphTraits::vertex_type; + using edge_type = typename GraphTraits::edge_type; + + using adjacency_storage_type = typename specialized::adjacency_matrix_impl_traits< + adjacency_matrix>::template storage_type; adjacency_matrix() = default; @@ -117,7 +123,7 @@ class adjacency_matrix final { specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } - void remove_edge(const auto& edge) { + void remove_edge(const edge_type& edge) { specialized_impl::remove_edge(*this, edge); for (auto&& row : this->_matrix) for (auto& edge_id : row) @@ -125,7 +131,7 @@ class adjacency_matrix final { edge_id--; } - std::vector remove_edges(const traits::c_range auto& edges) { + std::vector remove_edges(const traits::c_range_of auto& edges) { for (const auto& edge : edges) specialized_impl::remove_edge(*this, edge); auto removed_edge_ids = @@ -141,83 +147,79 @@ class adjacency_matrix final { return specialized_impl::get_entry(*this, source_id, target_id) != invalid_id; } - [[nodiscard]] bool has_edge(const auto& edge) const { + [[nodiscard]] bool has_edge(const edge_type& edge) const { return specialized_impl::get_entry(*this, edge.source(), edge.target()) == edge.id(); } - template - [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); if (edge_id == invalid_id) return std::nullopt; - return std::make_optional(edge_id, source_id, target_id); + return std::make_optional(edge_id, source_id, target_id); } - [[nodiscard]] std::optional edge( + [[nodiscard]] std::optional edge( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); if (edge_id == invalid_id) return std::nullopt; - return std::make_optional( + return std::make_optional( edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)] ); } - [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); if (edge_id == invalid_id) - return std::vector(); - return std::vector{ - EdgeType{edge_id, source_id, target_id} + return std::vector(); + return std::vector{ + edge_type{edge_id, source_id, target_id} }; } - [[nodiscard]] std::vector edges( + [[nodiscard]] std::vector edges( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); if (edge_id == invalid_id) - return std::vector(); - return std::vector{ - EdgeType{edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)]} + return std::vector(); + return std::vector{ + edge_type{edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)]} }; } - template [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return specialized_impl::incident_edges(*this, vertex_id); } - template [[nodiscard]] gl_attr_force_inline auto incident_edges( id_type vertex_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); } - template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return std::views::iota(initial_id_v, this->_matrix.size()) | std::views::filter([this, vertex_id](auto source_id) { return specialized_impl::get_entry(*this, source_id, vertex_id) != invalid_id; }) | std::views::transform([this, vertex_id](auto source_id) { - return EdgeType{ + return edge_type{ specialized_impl::get_entry(*this, source_id, vertex_id), source_id, vertex_id @@ -225,10 +227,9 @@ class adjacency_matrix final { }); } - template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return std::views::iota(initial_id_v, this->_matrix.size()) | std::views::filter([this, vertex_id](auto source_id) { @@ -236,15 +237,14 @@ class adjacency_matrix final { }) | std::views::transform([this, vertex_id, &edge_properties_map](auto source_id) { const auto edge_id = specialized_impl::get_entry(*this, source_id, vertex_id); - return EdgeType{ + return edge_type{ edge_id, source_id, vertex_id, edge_properties_map[to_idx(edge_id)] }; }); } - template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return this->_matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { @@ -253,14 +253,13 @@ class adjacency_matrix final { }) | std::views::transform([vertex_id](auto entry) { auto [target_id, edge_id] = entry; - return EdgeType{edge_id, vertex_id, static_cast(target_id)}; + return edge_type{edge_id, vertex_id, static_cast(target_id)}; }); } - template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return this->_matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { @@ -269,7 +268,7 @@ class adjacency_matrix final { }) | std::views::transform([vertex_id, &edge_properties_map](auto entry) { const auto [target_id, edge_id] = entry; - return EdgeType{ + return edge_type{ edge_id, vertex_id, static_cast(target_id), @@ -293,10 +292,8 @@ class adjacency_matrix final { #endif private: - using impl_traits = specialized::adjacency_matrix_impl_traits; - using specialized_impl = typename impl_traits::type; - using adjacency_storage_type = typename impl_traits::template storage_type; - + using specialized_impl = + typename specialized::adjacency_matrix_impl_traits::type; friend specialized_impl; void _remap_element_ids(std::vector& removed_edge_ids) { diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 8a594a2c..89f25bee 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -32,7 +32,7 @@ struct incidence_item { id_type vertex_id; id_type edge_id; - [[nodiscard]] bool operator==(const incidence_item&) const = default; + [[nodiscard]] constexpr bool operator==(const incidence_item&) const noexcept = default; }; namespace detail { @@ -59,6 +59,7 @@ class directed_adjacency_list { using traits_type = GraphTraits; using id_type = typename traits_type::id_type; using item_type = incidence_item; + using storage_type = std::vector>; // --- degree getters --- @@ -128,6 +129,11 @@ class directed_adjacency_list { inc_edges_source.emplace_back(target_id, edge_id); } + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const directed_adjacency_list&, const directed_adjacency_list&) = default; + protected: // --- vertex modifiers --- @@ -190,6 +196,7 @@ class undirected_adjacency_list { using traits_type = GraphTraits; using id_type = typename traits_type::id_type; using item_type = incidence_item; + using storage_type = std::vector>; // --- degree getters --- @@ -250,6 +257,11 @@ class undirected_adjacency_list { } } + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const undirected_adjacency_list&, const undirected_adjacency_list&) = default; + protected: // --- vertex modifiers --- @@ -293,22 +305,19 @@ class undirected_adjacency_list { }; template -struct adjacency_list_impl_traits { +struct adjacency_list_base { using type = void; - - template - using storage_type = void; }; +template +using adjacency_list_base_t = typename adjacency_list_base::type; + template -struct adjacency_list_impl_traits { +struct adjacency_list_base { using type = std::conditional_t< traits::c_directed_graph_traits, directed_adjacency_list, undirected_adjacency_list>; - - template - using storage_type = std::vector>; }; } // namespace specialized diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/specialized/flat_adjacency_list.hpp index f0804070..270c4b83 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/specialized/flat_adjacency_list.hpp @@ -23,6 +23,7 @@ class directed_flat_adjacency_list { using traits_type = GraphTraits; using id_type = typename traits_type::id_type; using item_type = incidence_item; + using storage_type = flat_jagged_vector; // --- degree getters --- @@ -85,6 +86,11 @@ class directed_flat_adjacency_list { self._list.emplace_back(to_idx(source_id), target_id, edge_id); } + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const directed_flat_adjacency_list&, const directed_flat_adjacency_list&) = default; + protected: // --- vertex modifiers --- @@ -97,7 +103,7 @@ class directed_flat_adjacency_list { | std::ranges::to(); // rebuild the graph (faster then shifting the entire data block for each removed edge) - typename adjacency_list_impl_traits::storage_type new_list; + storage_type new_list{}; new_list.reserve_segments(self._list.size() - 1uz); new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); @@ -154,6 +160,7 @@ class undirected_flat_adjacency_list { using traits_type = GraphTraits; using id_type = typename traits_type::id_type; using item_type = incidence_item; + using storage_type = flat_jagged_vector; // --- degree getters --- @@ -209,6 +216,12 @@ class undirected_flat_adjacency_list { } } + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const undirected_flat_adjacency_list&, const undirected_flat_adjacency_list&) = + default; + protected: // --- vertex modifiers --- @@ -221,7 +234,7 @@ class undirected_flat_adjacency_list { | std::ranges::to(); // rebuild the graph (faster then shifting the entire data block for each removed edge) - typename adjacency_list_impl_traits::storage_type new_list; + storage_type new_list{}; new_list.reserve_segments(self._list.size() - 1uz); new_list.reserve_data(self._list.data_size() - self._list[vertex_idx].size()); @@ -272,14 +285,11 @@ class undirected_flat_adjacency_list { }; template -struct adjacency_list_impl_traits { +struct adjacency_list_base { using type = std::conditional_t< traits::c_directed_graph_traits, directed_flat_adjacency_list, undirected_flat_adjacency_list>; - - template - using storage_type = flat_jagged_vector; }; } // namespace gl::impl::specialized From 581f0c30e0e9a5c3c9254ad8eefa5295fc138cf9 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 27 Jul 2026 00:52:11 +0200 Subject: [PATCH 09/13] some small modifications --- include/gl/graph.hpp | 14 ++--- .../gl/impl/specialized/adjacency_list.hpp | 51 ++++++++++-------- .../impl/specialized/flat_adjacency_list.hpp | 52 +++++++++++-------- 3 files changed, 66 insertions(+), 51 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index e93ace05..f4922125 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -459,7 +459,7 @@ class graph final { /// @return The corresponding vertex descriptor (const or mutable). /// @throws std::invalid_argument If the ID is invalid. template - [[nodiscard]] deduced_vertex_type vertex(this Self&& self, const id_type vertex_id) { + [[nodiscard]] deduced_vertex_type vertex(this Self& self, const id_type vertex_id) { self._verify_vertex_id(vertex_id); return self.vertex_unchecked(vertex_id); } @@ -470,7 +470,7 @@ class graph final { /// @throws std::invalid_argument If the ID is invalid. template [[nodiscard]] gl_attr_force_inline deduced_vertex_type at( - this Self&& self, const id_type vertex_id + this Self& self, const id_type vertex_id ) { return self.vertex(vertex_id); } @@ -484,7 +484,7 @@ class graph final { /// > No bounds checking is performed. Passing an invalid ID results in Undefined Behavior. template [[nodiscard]] gl_attr_force_inline deduced_vertex_type vertex_unchecked( - this Self&& self, const id_type vertex_id + this Self& self, const id_type vertex_id ) { if constexpr (traits::c_non_empty_properties) return deduced_vertex_type{vertex_id, self._vertex_properties[vertex_id]}; @@ -501,7 +501,7 @@ class graph final { /// > No bounds checking is performed. Passing an invalid ID results in Undefined Behavior. template [[nodiscard]] gl_attr_force_inline deduced_vertex_type operator[]( - this Self&& self, const id_type vertex_id + this Self& self, const id_type vertex_id ) noexcept { return self.vertex_unchecked(vertex_id); } @@ -648,7 +648,7 @@ class graph final { /// @throws std::invalid_argument If the vertex ID is invalid. template [[nodiscard]] gl_attr_force_inline deduced_vertex_properties_type& vertex_properties( - this Self&& self, const id_type id + this Self& self, const id_type id ) requires(traits::c_non_empty_properties) { @@ -1293,14 +1293,14 @@ class graph final { // --- transformations --- template - gl_attr_force_inline auto _create_vertex_descriptor(this Self&&) noexcept + gl_attr_force_inline auto _create_vertex_descriptor(this Self&) noexcept requires(traits::c_empty_properties) { return [](const id_type id) { return deduced_vertex_type{id}; }; } template - gl_attr_force_inline auto _create_vertex_descriptor(this Self&& self) noexcept + gl_attr_force_inline auto _create_vertex_descriptor(this Self& self) noexcept requires(traits::c_non_empty_properties) { return [&pmap = self._vertex_properties](const id_type id) { diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 89f25bee..34b6366c 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -63,11 +63,11 @@ class directed_adjacency_list { // --- degree getters --- - [[nodiscard]] gl_attr_force_inline size_type degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { return self.in_degree(vertex_id) + self.out_degree(vertex_id); } - [[nodiscard]] size_type in_degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] size_type in_degree(this const auto& self, id_type vertex_id) { size_type in_deg = 0uz; for (const auto& out_edges : self._list) in_deg += static_cast( @@ -77,11 +77,12 @@ class directed_adjacency_list { return in_deg; } - [[nodiscard]] gl_attr_force_inline size_type out_degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { return self._list[to_idx(vertex_id)].size(); } - [[nodiscard]] std::vector degree_map(this auto&& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._list.size(), 0uz); for (auto idx = 0uz; idx < self._list.size(); ++idx) { @@ -93,7 +94,7 @@ class directed_adjacency_list { return degree_map; } - [[nodiscard]] std::vector in_degree_map(this auto&& self) { + [[nodiscard]] std::vector in_degree_map(this const auto& self) { std::vector in_degree_map(self._list.size(), 0uz); for (const auto& inc_edges : self._list) @@ -103,7 +104,8 @@ class directed_adjacency_list { return in_degree_map; } - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this auto&& self) { + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self + ) { return self._list | std::views::transform([](auto&& inc_edges) { return inc_edges.size(); }) | std::ranges::to>(); } @@ -111,13 +113,13 @@ class directed_adjacency_list { // --- edge modifiers --- gl_attr_force_inline void add_edge( - this auto&& self, id_type edge_id, id_type source_id, id_type target_id + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { self._list[to_idx(source_id)].emplace_back(target_id, edge_id); } void add_edges_from( - this auto&& self, + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -137,7 +139,7 @@ class directed_adjacency_list { protected: // --- vertex modifiers --- - std::vector _remove_vertex_impl(this auto&& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); auto removed_edges = @@ -167,14 +169,14 @@ class directed_adjacency_list { // --- edge modifiers --- - gl_attr_force_inline void _remove_edge_impl(this auto&& self, const auto& edge) { + gl_attr_force_inline void _remove_edge_impl(this auto& self, const auto& edge) { auto& inc_edges = self._list[to_idx(edge.source())]; inc_edges.erase(detail::strict_find(inc_edges, edge)); } // --- edge getters --- - [[nodiscard]] auto _in_edges_impl(this auto&& self, id_type vertex_id) { + [[nodiscard]] auto _in_edges_impl(this const auto& self, id_type vertex_id) { std::vector in_edges; for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { auto in_edges_view = @@ -200,22 +202,24 @@ class undirected_adjacency_list { // --- degree getters --- - [[nodiscard]] size_type degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] size_type degree(this const auto& self, id_type vertex_id) { size_type degree = 0uz; for (auto item : self._list[to_idx(vertex_id)]) degree += 1uz + static_cast(item.vertex_id == vertex_id); return degree; } - [[nodiscard]] gl_attr_force_inline size_type in_degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline size_type out_degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { return self.degree(vertex_id); } - [[nodiscard]] std::vector degree_map(this auto&& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map; degree_map.reserve(self._list.size()); for (id_type id = initial_id; id < self._list.size(); ++id) @@ -223,18 +227,19 @@ class undirected_adjacency_list { return degree_map; } - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this auto&& self) { + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this const auto& self) { return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this auto&& self) { + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self + ) { return self.degree_map(); } // --- edge modifiers --- gl_attr_force_inline void add_edge( - this auto&& self, id_type edge_id, id_type source_id, id_type target_id + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { self._list[to_idx(source_id)].emplace_back(target_id, edge_id); if (target_id != source_id) @@ -242,7 +247,7 @@ class undirected_adjacency_list { } void add_edges_from( - this auto&& self, + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -265,7 +270,7 @@ class undirected_adjacency_list { protected: // --- vertex modifiers --- - std::vector _remove_vertex_impl(this auto&& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); for (auto item : self._list[vertex_idx]) { @@ -288,7 +293,7 @@ class undirected_adjacency_list { // --- edge modifiers --- - void _remove_edge_impl(this auto&& self, const auto& edge) { + void _remove_edge_impl(this auto& self, const auto& edge) { auto& inc_edges_first = self._list[to_idx(edge.source())]; auto& inc_edges_second = self._list[to_idx(edge.target())]; @@ -299,7 +304,9 @@ class undirected_adjacency_list { // --- edge getters --- - [[nodiscard]] gl_attr_force_inline auto _in_edges_impl(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline auto _in_edges_impl( + this const auto& self, id_type vertex_id + ) { return std::views::all(self._list[to_idx(vertex_id)]); } }; diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/specialized/flat_adjacency_list.hpp index 270c4b83..c00b7bda 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/specialized/flat_adjacency_list.hpp @@ -27,21 +27,23 @@ class directed_flat_adjacency_list { // --- degree getters --- - [[nodiscard]] gl_attr_force_inline size_type degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { return self.in_degree(vertex_id) + self.out_degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline size_type in_degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { return static_cast( std::ranges::count(self._list.data_view(), vertex_id, &item_type::vertex_id) ); } - [[nodiscard]] gl_attr_force_inline size_type out_degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { return self._list.segment_size(to_idx(vertex_id)); } - [[nodiscard]] std::vector degree_map(this auto&& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._list.size(), 0uz); for (auto idx = 0uz; idx < self._list.size(); ++idx) { @@ -53,14 +55,15 @@ class directed_flat_adjacency_list { return degree_map; } - [[nodiscard]] std::vector in_degree_map(this auto&& self) { + [[nodiscard]] std::vector in_degree_map(this const auto& self) { std::vector in_degree_map(self._list.size(), 0uz); for (auto item : self._list.data_view()) ++in_degree_map[to_idx(item.vertex_id)]; return in_degree_map; } - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this auto&& self) { + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self + ) { std::vector out_degree; out_degree.reserve(self._list.size()); for (auto idx = 0uz; idx < self._list.size(); ++idx) @@ -71,13 +74,13 @@ class directed_flat_adjacency_list { // --- edge modifiers --- gl_attr_force_inline void add_edge( - this auto&& self, id_type edge_id, id_type source_id, id_type target_id + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { self._list.emplace_back(to_idx(source_id), target_id, edge_id); } void add_edges_from( - this auto&& self, + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -94,7 +97,7 @@ class directed_flat_adjacency_list { protected: // --- vertex modifiers --- - std::vector _remove_vertex_impl(this auto&& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); // extract out-edges @@ -128,7 +131,7 @@ class directed_flat_adjacency_list { // --- edge modifiers --- - gl_attr_force_inline void _remove_edge_impl(this auto&& self, const auto& edge) { + gl_attr_force_inline void _remove_edge_impl(this auto& self, const auto& edge) { const auto edge_src = to_idx(edge.source()); auto segment = self._list[edge_src]; const auto it = detail::strict_find(segment, edge); @@ -138,7 +141,7 @@ class directed_flat_adjacency_list { // --- edge getters --- - [[nodiscard]] auto _in_edges_impl(this auto&& self, id_type vertex_id) { + [[nodiscard]] auto _in_edges_impl(this const auto& self, id_type vertex_id) { std::vector in_edges; for (id_type src_id = initial_id; src_id < self._list.size(); ++src_id) { auto in_edges_view = @@ -164,22 +167,24 @@ class undirected_flat_adjacency_list { // --- degree getters --- - [[nodiscard]] size_type degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] size_type degree(this const auto& self, id_type vertex_id) { size_type degree = 0uz; for (auto item : self._list[to_idx(vertex_id)]) degree += 1uz + static_cast(item.vertex_id == vertex_id); return degree; } - [[nodiscard]] gl_attr_force_inline size_type in_degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline size_type out_degree(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { return self.degree(vertex_id); } - [[nodiscard]] std::vector degree_map(this auto&& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map; degree_map.reserve(self._list.size()); for (id_type id = initial_id; id < self._list.size(); ++id) @@ -187,24 +192,25 @@ class undirected_flat_adjacency_list { return degree_map; } - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this auto&& self) { + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this const auto& self) { return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this auto&& self) { + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self + ) { return self.degree_map(); } // --- edge modifiers --- - void add_edge(this auto&& self, id_type edge_id, id_type source_id, id_type target_id) { + void add_edge(this auto& self, id_type edge_id, id_type source_id, id_type target_id) { self._list.push_back(to_idx(source_id), {target_id, edge_id}); if (target_id != source_id) self._list.push_back(to_idx(target_id), {source_id, edge_id}); } void add_edges_from( - this auto&& self, + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -225,7 +231,7 @@ class undirected_flat_adjacency_list { protected: // --- vertex modifiers --- - std::vector _remove_vertex_impl(this auto&& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); // all removed edges are stored in the vertex's list segment @@ -257,7 +263,7 @@ class undirected_flat_adjacency_list { // --- edge modifiers --- - void _remove_edge_impl(this auto&& self, const auto& edge) { + void _remove_edge_impl(this auto& self, const auto& edge) { const auto src_idx = to_idx(edge.source()); const auto tgt_idx = to_idx(edge.target()); @@ -279,7 +285,9 @@ class undirected_flat_adjacency_list { // --- edge getters --- - [[nodiscard]] gl_attr_force_inline auto _in_edges_impl(this auto&& self, id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline auto _in_edges_impl( + this const auto& self, id_type vertex_id + ) { return std::views::all(self._list[to_idx(vertex_id)]); } }; From 751354df6d0c10ad0f620b88cc47ed20349ab2b9 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 27 Jul 2026 11:02:07 +0200 Subject: [PATCH 10/13] adjacency matrix impl alignment --- include/gl/impl/adjacency_matrix.hpp | 201 +++++----- .../gl/impl/specialized/adjacency_matrix.hpp | 341 ++++++++--------- .../specialized/flat_adjacency_matrix.hpp | 345 ++++++++---------- 3 files changed, 386 insertions(+), 501 deletions(-) diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index 2c3204ff..bac8af96 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -28,21 +28,22 @@ struct to_impl; namespace impl { template -class adjacency_matrix final { +class adjacency_matrix final : public specialized::adjacency_matrix_base_t { public: - using representation_tag = typename GraphTraits::representation_tag; - using id_type = typename GraphTraits::id_type; + using traits_type = GraphTraits; + using base_type = specialized::adjacency_matrix_base_t; + using representation_tag = typename traits_type::representation_tag; + using id_type = typename traits_type::id_type; - using vertex_type = typename GraphTraits::vertex_type; - using edge_type = typename GraphTraits::edge_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; - using adjacency_storage_type = typename specialized::adjacency_matrix_impl_traits< - adjacency_matrix>::template storage_type; + using storage_type = typename base_type::storage_type; adjacency_matrix() = default; explicit adjacency_matrix(size_type n_vertices) { - specialized_impl::init(*this, n_vertices); + this->_init(n_vertices); } adjacency_matrix(const adjacency_matrix&) = default; @@ -56,84 +57,32 @@ class adjacency_matrix final { // --- vertex modifiers --- gl_attr_force_inline void add_vertex() { - specialized_impl::add_vertex(*this); + this->_add_vertex_impl(); } gl_attr_force_inline void add_vertices(size_type n) { - specialized_impl::add_vertices(*this, n); + this->_add_vertices_impl(n); } std::vector remove_vertex(id_type vertex_id) { - auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); + auto removed_edge_ids = this->_remove_vertex_impl(vertex_id); this->_remap_element_ids(removed_edge_ids); return removed_edge_ids; } - // --- vertex getters --- - - [[nodiscard]] gl_attr_force_inline auto neighbor_ids(id_type vertex_id) const { - return specialized_impl::neighbor_ids(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline auto predecessor_ids(id_type vertex_id) const { - return specialized_impl::predecessor_ids(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline auto successor_ids(id_type vertex_id) const { - return specialized_impl::successor_ids(*this, vertex_id); - } - - // --- degree getters --- - - [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { - return specialized_impl::degree_map(*this); - } - - [[nodiscard]] gl_attr_force_inline size_type degree(id_type vertex_id) const { - return specialized_impl::degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type in_degree(id_type vertex_id) const { - return specialized_impl::in_degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline size_type out_degree(id_type vertex_id) const { - return specialized_impl::out_degree(*this, vertex_id); - } - - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { - return specialized_impl::in_degree_map(*this); - } - - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { - return specialized_impl::out_degree_map(*this); - } - // --- edge modifiers --- - gl_attr_force_inline void add_edge(id_type id, id_type source_id, id_type target_id) { - specialized_impl::add_edge(*this, id, source_id, target_id); - } - - gl_attr_force_inline void add_edges_from( - const traits::c_forward_range_of auto& edge_ids, - id_type source_id, - const traits::c_forward_range_of auto& target_ids - ) { - specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); - } - - void remove_edge(const edge_type& edge) { - specialized_impl::remove_edge(*this, edge); + void remove_edge(const auto& edge) { + this->_remove_edge_impl(edge); for (auto&& row : this->_matrix) for (auto& edge_id : row) if (edge_id != invalid_id and edge_id > edge.id()) edge_id--; } - std::vector remove_edges(const traits::c_range_of auto& edges) { + std::vector remove_edges(const traits::c_range auto& edges) { for (const auto& edge : edges) - specialized_impl::remove_edge(*this, edge); + this->_remove_edge_impl(edge); auto removed_edge_ids = edges | std::views::transform([](const auto& edge) { return edge.id(); }) | std::ranges::to(); @@ -144,107 +93,130 @@ class adjacency_matrix final { // --- edge getters --- [[nodiscard]] gl_attr_force_inline bool has_edge(id_type source_id, id_type target_id) const { - return specialized_impl::get_entry(*this, source_id, target_id) != invalid_id; + return this->_get_entry_impl(source_id, target_id) != invalid_id; } - [[nodiscard]] bool has_edge(const edge_type& edge) const { - return specialized_impl::get_entry(*this, edge.source(), edge.target()) == edge.id(); + [[nodiscard]] bool has_edge(const auto& edge) const { + return this->_get_entry_impl(edge.source(), edge.target()) == edge.id(); } - [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::optional edge(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); + const auto edge_id = this->_get_entry_impl(source_id, target_id); if (edge_id == invalid_id) return std::nullopt; - return std::make_optional(edge_id, source_id, target_id); + return std::make_optional(edge_id, source_id, target_id); } - [[nodiscard]] std::optional edge( + template + [[nodiscard]] std::optional edge( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); + const auto edge_id = this->_get_entry_impl(source_id, target_id); if (edge_id == invalid_id) return std::nullopt; - return std::make_optional( + return std::make_optional( edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)] ); } - [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const - requires(traits::c_has_empty_properties) + template + [[nodiscard]] std::vector edges(id_type source_id, id_type target_id) const + requires(traits::c_has_empty_properties) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); + const auto edge_id = this->_get_entry_impl(source_id, target_id); if (edge_id == invalid_id) - return std::vector(); - return std::vector{ - edge_type{edge_id, source_id, target_id} + return std::vector(); + return std::vector{ + EdgeType{edge_id, source_id, target_id} }; } - [[nodiscard]] std::vector edges( + template + [[nodiscard]] std::vector edges( id_type source_id, id_type target_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, target_id); + const auto edge_id = this->_get_entry_impl(source_id, target_id); if (edge_id == invalid_id) - return std::vector(); - return std::vector{ - edge_type{edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)]} + return std::vector(); + return std::vector{ + EdgeType{edge_id, source_id, target_id, edge_properties_map[to_idx(edge_id)]} }; } + template [[nodiscard]] gl_attr_force_inline auto incident_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { - return specialized_impl::incident_edges(*this, vertex_id); + if constexpr (traits::c_directed_graph_traits) { + return util::concat( + this->template in_edges(vertex_id), + this->template out_edges(vertex_id) + ); + } + else { + return this->template out_edges(vertex_id); + } } + template [[nodiscard]] gl_attr_force_inline auto incident_edges( id_type vertex_id, auto& edge_properties_map ) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - return specialized_impl::incident_edges(*this, vertex_id, edge_properties_map); + if constexpr (traits::c_directed_graph_traits) { + return util::concat( + this->template in_edges(vertex_id, edge_properties_map), + this->template out_edges(vertex_id, edge_properties_map) + ); + } + else { + return this->template out_edges(vertex_id, edge_properties_map); + } } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { - return std::views::iota(initial_id_v, this->_matrix.size()) + return std::views::iota(initial_id_v, static_cast(this->_matrix.size())) | std::views::filter([this, vertex_id](auto source_id) { - return specialized_impl::get_entry(*this, source_id, vertex_id) != invalid_id; + return this->_get_entry_impl(source_id, vertex_id) != invalid_id; }) | std::views::transform([this, vertex_id](auto source_id) { - return edge_type{ - specialized_impl::get_entry(*this, source_id, vertex_id), - source_id, - vertex_id + return EdgeType{ + this->_get_entry_impl(source_id, vertex_id), source_id, vertex_id }; }); } + template [[nodiscard]] gl_attr_force_inline auto in_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { - return std::views::iota(initial_id_v, this->_matrix.size()) + return std::views::iota(initial_id_v, static_cast(this->_matrix.size())) | std::views::filter([this, vertex_id](auto source_id) { - return specialized_impl::get_entry(*this, source_id, vertex_id) != invalid_id; + return this->_get_entry_impl(source_id, vertex_id) != invalid_id; }) | std::views::transform([this, vertex_id, &edge_properties_map](auto source_id) { - const auto edge_id = specialized_impl::get_entry(*this, source_id, vertex_id); - return edge_type{ + const auto edge_id = this->_get_entry_impl(source_id, vertex_id); + return EdgeType{ edge_id, source_id, vertex_id, edge_properties_map[to_idx(edge_id)] }; }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id) const - requires(traits::c_has_empty_properties) + requires(traits::c_has_empty_properties) { return this->_matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { @@ -253,13 +225,14 @@ class adjacency_matrix final { }) | std::views::transform([vertex_id](auto entry) { auto [target_id, edge_id] = entry; - return edge_type{edge_id, vertex_id, static_cast(target_id)}; + return EdgeType{edge_id, vertex_id, static_cast(target_id)}; }); } + template [[nodiscard]] gl_attr_force_inline auto out_edges(id_type vertex_id, auto& edge_properties_map) const - requires(traits::c_has_non_empty_properties) + requires(traits::c_has_non_empty_properties) { return this->_matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { @@ -268,7 +241,7 @@ class adjacency_matrix final { }) | std::views::transform([vertex_id, &edge_properties_map](auto entry) { const auto [target_id, edge_id] = entry; - return edge_type{ + return EdgeType{ edge_id, vertex_id, static_cast(target_id), @@ -284,6 +257,8 @@ class adjacency_matrix final { // --- friend declarations --- + friend base_type; + template friend struct gl::detail::to_impl; @@ -292,10 +267,6 @@ class adjacency_matrix final { #endif private: - using specialized_impl = - typename specialized::adjacency_matrix_impl_traits::type; - friend specialized_impl; - void _remap_element_ids(std::vector& removed_edge_ids) { std::ranges::sort(removed_edge_ids); removed_edge_ids.erase( @@ -318,7 +289,7 @@ class adjacency_matrix final { } } - adjacency_storage_type _matrix{}; + storage_type _matrix{}; }; } // namespace impl diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index 5e21672f..74313c9f 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -9,11 +9,15 @@ #include "gl/constants.hpp" #include "gl/decl/repr_tags.hpp" #include "gl/graph_traits.hpp" +#include "gl/traits.hpp" #include "gl/types/core.hpp" #include "gl/util/ranges.hpp" #include #include +#include +#include +#include #include namespace gl::impl { @@ -52,66 +56,18 @@ inline void check_edge_override( } // namespace detail -template AdjacencyMatrix> -requires(traits::c_directed_edge) -struct directed_adjacency_matrix { - using impl_type = AdjacencyMatrix; - using id_type = typename impl_type::id_type; - using vertex_type = typename impl_type::vertex_type; - using edge_type = typename impl_type::edge_type; - - // --- general --- - - static void init(impl_type& self, size_type n_vertices) { - self._matrix.resize(n_vertices); - for (auto& row : self._matrix) - row.resize(n_vertices, invalid_id); - } - - gl_attr_force_inline static id_type get_entry( - const impl_type& self, id_type source_id, id_type target_id - ) { - return self._matrix[to_idx(source_id)][to_idx(target_id)]; - } - - // --- vertex modifiers --- - - static void add_vertex(impl_type& self) { - for (auto& row : self._matrix) - row.emplace_back(invalid_id); - self._matrix.emplace_back(self._matrix.size() + 1uz, invalid_id); - } - - static void add_vertices(impl_type& self, size_type n) { - const auto new_n_vertices = self._matrix.size() + n; - for (auto& row : self._matrix) - row.resize(new_n_vertices, invalid_id); - for (auto _ = 0uz; _ < n; ++_) - self._matrix.emplace_back(new_n_vertices, invalid_id); - } - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - - auto removed_edges = - self._matrix[vertex_idx] - | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) - | std::ranges::to(); - - const auto vertex_pos = to_diff(vertex_id); - self._matrix.erase(self._matrix.begin() + vertex_pos); - for (auto& row : self._matrix) { - if (const auto edge_id = row[vertex_idx]; edge_id != invalid_id) - removed_edges.push_back(edge_id); - row.erase(row.begin() + vertex_pos); - } - - return removed_edges; - } +template +class directed_adjacency_matrix { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; + using storage_type = std::vector>; // --- vertex getters --- - [[nodiscard]] static auto successor_ids(const impl_type& self, id_type vertex_id) { + [[nodiscard]] auto successor_ids(this const auto& self, id_type vertex_id) { return self._matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { auto [target_id, edge_id] = entry; @@ -123,22 +79,20 @@ struct directed_adjacency_matrix { }); } - [[nodiscard]] static auto predecessor_ids(const impl_type& self, id_type vertex_id) { + [[nodiscard]] auto predecessor_ids(this const auto& self, id_type vertex_id) { return std::views::iota(initial_id_v, static_cast(self._matrix.size())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto src_id) { return self._matrix[to_idx(src_id)][v_idx] != invalid_id; }); } - [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { - return util::concat(successor_ids(self, vertex_id), predecessor_ids(self, vertex_id)); + [[nodiscard]] auto neighbor_ids(this const auto& self, id_type vertex_id) { + return util::concat(self.successor_ids(vertex_id), self.predecessor_ids(vertex_id)); } // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { size_type deg = 0uz; const auto vertex_idx = to_idx(vertex_id); for (auto v_idx = 0uz; v_idx < self._matrix.size(); ++v_idx) @@ -150,11 +104,8 @@ struct directed_adjacency_matrix { GL_SUPPRESS_WARNING_BEGIN("-Wsign-conversion") - // NOTE: Indexing into a row which might be a vector (requires size type) or a span/subrange (requires difference type) - - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { return static_cast(std::ranges::count_if( self._matrix, [vertex_id](const auto& row) { return row[to_idx(vertex_id)] != invalid_id; } @@ -163,16 +114,15 @@ struct directed_adjacency_matrix { GL_SUPPRESS_WARNING_END - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return self._matrix[vertex_id].size() + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self._matrix[to_idx(vertex_id)].size() - static_cast( - std::ranges::count(self._matrix[vertex_id], invalid_id_v) + std::ranges::count(self._matrix[to_idx(vertex_id)], invalid_id_v) ); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._matrix.size(), 0uz); for (auto src_idx = 0uz; src_idx < self._matrix.size(); ++src_idx) { @@ -187,7 +137,7 @@ struct directed_adjacency_matrix { return degree_map; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + [[nodiscard]] std::vector in_degree_map(this const auto& self) { std::vector in_degree_map(self._matrix.size(), 0uz); for (const auto& row : self._matrix) @@ -198,23 +148,24 @@ struct directed_adjacency_matrix { return in_degree_map; } - [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { - return std::views::iota(initial_id_v, self._matrix.size()) - | std::views::transform([&](id_type id) { return out_degree(self, id); }) + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self + ) { + return std::views::iota(initial_id_v, static_cast(self._matrix.size())) + | std::views::transform([&self](id_type id) { return self.out_degree(id); }) | std::ranges::to(); } // --- edge modifiers --- - static inline void add_edge( - impl_type& self, id_type edge_id, id_type source_id, id_type target_id + gl_attr_force_inline void add_edge( + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { detail::check_edge_override(self._matrix, source_id, target_id); self._matrix[to_idx(source_id)][to_idx(target_id)] = edge_id; } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -227,59 +178,27 @@ struct directed_adjacency_matrix { matrix_source_row[to_idx(target_id)] = edge_id; } - gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { - detail::strict_get(self._matrix, edge) = invalid_id; - } + // --- comparison --- - // --- edge getters --- + [[nodiscard]] friend bool + operator==(const directed_adjacency_matrix&, const directed_adjacency_matrix&) = default; - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return util::concat( - self.in_edges(vertex_id, edge_properties_map), - self.out_edges(vertex_id, edge_properties_map) - ); - } -}; - -template AdjacencyMatrix> -requires(traits::c_undirected_edge) -struct undirected_adjacency_matrix { - using impl_type = AdjacencyMatrix; - using id_type = typename impl_type::id_type; - using vertex_type = typename impl_type::vertex_type; - using edge_type = typename impl_type::edge_type; - - // --- general --- - - static void init(impl_type& self, size_type n_vertices) { +protected: + void _init(this auto& self, size_type n_vertices) { self._matrix.resize(n_vertices); for (auto& row : self._matrix) row.resize(n_vertices, invalid_id); } - gl_attr_force_inline static id_type get_entry( - const impl_type& self, id_type source_id, id_type target_id - ) { - return self._matrix[to_idx(source_id)][to_idx(target_id)]; - } - // --- vertex modifiers --- - static void add_vertex(impl_type& self) { + void _add_vertex_impl(this auto& self) { for (auto& row : self._matrix) row.emplace_back(invalid_id); self._matrix.emplace_back(self._matrix.size() + 1uz, invalid_id); } - static void add_vertices(impl_type& self, size_type n) { + void _add_vertices_impl(this auto& self, size_type n) { const auto new_n_vertices = self._matrix.size() + n; for (auto& row : self._matrix) row.resize(new_n_vertices, invalid_id); @@ -287,25 +206,51 @@ struct undirected_adjacency_matrix { self._matrix.emplace_back(new_n_vertices, invalid_id); } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); - const auto removed_edges = + auto removed_edges = self._matrix[vertex_idx] | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) | std::ranges::to(); const auto vertex_pos = to_diff(vertex_id); self._matrix.erase(self._matrix.begin() + vertex_pos); - for (auto& row : self._matrix) + for (auto& row : self._matrix) { + if (const auto edge_id = row[vertex_idx]; edge_id != invalid_id) + removed_edges.push_back(edge_id); row.erase(row.begin() + vertex_pos); + } return removed_edges; } + // --- edge modifiers --- + + gl_attr_force_inline void _remove_edge_impl(this auto& self, const auto& edge) { + detail::strict_get(self._matrix, edge) = invalid_id; + } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline id_type + _get_entry_impl(this const auto& self, id_type source_id, id_type target_id) { + return self._matrix[to_idx(source_id)][to_idx(target_id)]; + } +}; + +template +class undirected_adjacency_matrix { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; + using storage_type = std::vector>; + // --- vertex getters --- - [[nodiscard]] static auto neighbor_ids(const impl_type& self, id_type vertex_id) { + [[nodiscard]] auto neighbor_ids(this const auto& self, id_type vertex_id) { return self._matrix[to_idx(vertex_id)] | std::views::enumerate | std::views::filter([](auto entry) { auto [target_id, edge_id] = entry; @@ -317,23 +262,19 @@ struct undirected_adjacency_matrix { }); } - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto predecessor_ids( + this const auto& self, id_type vertex_id ) { - return neighbor_ids(self, vertex_id); + return self.neighbor_ids(vertex_id); } - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); + [[nodiscard]] gl_attr_force_inline auto successor_ids(this const auto& self, id_type vertex_id) { + return self.neighbor_ids(vertex_id); } // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); return self._matrix.size() - static_cast( @@ -342,19 +283,17 @@ struct undirected_adjacency_matrix { + static_cast(self._matrix[vertex_idx][vertex_idx] != invalid_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._matrix.size(), 0uz); for (auto src_idx = 0uz; src_idx < self._matrix.size(); ++src_idx) { @@ -369,21 +308,18 @@ struct undirected_adjacency_matrix { return degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this const auto& self) { + return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self ) { - return degree_map(self); + return self.degree_map(); } // --- edge modifiers --- - static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { + void add_edge(this auto& self, id_type edge_id, id_type source_id, id_type target_id) { detail::check_edge_override(self._matrix, source_id, target_id); const auto source_idx = to_idx(source_id); @@ -394,8 +330,8 @@ struct undirected_adjacency_matrix { self._matrix[target_idx][source_idx] = edge_id; } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -414,60 +350,85 @@ struct undirected_adjacency_matrix { } } - static void remove_edge(impl_type& self, const edge_type& edge) { + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const undirected_adjacency_matrix&, const undirected_adjacency_matrix&) = default; + +protected: + void _init(this auto& self, size_type n_vertices) { + self._matrix.resize(n_vertices); + for (auto& row : self._matrix) + row.resize(n_vertices, invalid_id); + } + + // --- vertex modifiers --- + + void _add_vertex_impl(this auto& self) { + for (auto& row : self._matrix) + row.emplace_back(invalid_id); + self._matrix.emplace_back(self._matrix.size() + 1uz, invalid_id); + } + + void _add_vertices_impl(this auto& self, size_type n) { + const auto new_n_vertices = self._matrix.size() + n; + for (auto& row : self._matrix) + row.resize(new_n_vertices, invalid_id); + for (auto _ = 0uz; _ < n; ++_) + self._matrix.emplace_back(new_n_vertices, invalid_id); + } + + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + const auto removed_edges = + self._matrix[vertex_idx] + | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) + | std::ranges::to(); + + const auto vertex_pos = to_diff(vertex_id); + self._matrix.erase(self._matrix.begin() + vertex_pos); + for (auto& row : self._matrix) + row.erase(row.begin() + vertex_pos); + + return removed_edges; + } + + // --- edge modifiers --- + + void _remove_edge_impl(this auto& self, const auto& edge) { if (edge.is_loop()) { detail::strict_get(self._matrix, edge) = invalid_id; } else { detail::strict_get(self._matrix, edge) = invalid_id; - // if the edge was found in the first matrix cell, it will also be present in the second matrix cell self._matrix[to_idx(edge.target())][to_idx(edge.source())] = invalid_id; } } // --- edge getters --- - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return self.out_edges(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return self.out_edges(vertex_id, edge_properties_map); + [[nodiscard]] gl_attr_force_inline id_type + _get_entry_impl(this const auto& self, id_type source_id, id_type target_id) { + return self._matrix[to_idx(source_id)][to_idx(target_id)]; } }; -template AdjacencyMatrix> -struct adjacency_matrix_impl_traits { +template +struct adjacency_matrix_base { using type = void; - - template - using storage_type = void; -}; - -template AdjacencyMatrix> -requires traits::c_directed_edge - and std::same_as -struct adjacency_matrix_impl_traits { - using type = directed_adjacency_matrix; - - template - using storage_type = std::vector>; }; -template AdjacencyMatrix> -requires traits::c_undirected_edge - and std::same_as -struct adjacency_matrix_impl_traits { - using type = undirected_adjacency_matrix; - - template - using storage_type = std::vector>; +template +using adjacency_matrix_base_t = typename adjacency_matrix_base::type; + +template +struct adjacency_matrix_base { + using type = std::conditional_t< + traits::c_directed_graph_traits, + directed_adjacency_matrix, + undirected_adjacency_matrix>; }; } // namespace specialized - } // namespace gl::impl diff --git a/include/gl/impl/specialized/flat_adjacency_matrix.hpp b/include/gl/impl/specialized/flat_adjacency_matrix.hpp index a87e78dc..14a67c42 100644 --- a/include/gl/impl/specialized/flat_adjacency_matrix.hpp +++ b/include/gl/impl/specialized/flat_adjacency_matrix.hpp @@ -51,78 +51,19 @@ inline void check_edge_override( } // namespace detail -template AdjacencyMatrix> -requires(traits::c_directed_edge) -struct directed_flat_adjacency_matrix { - using impl_type = AdjacencyMatrix; - using id_type = typename impl_type::id_type; - using storage_type = typename impl_type::adjacency_storage_type; - - using vertex_type = typename impl_type::vertex_type; - using edge_type = typename impl_type::edge_type; - - // --- general --- - - gl_attr_force_inline static void init(impl_type& self, size_type n_vertices) { - self._matrix = storage_type(n_vertices, n_vertices, invalid_id); - } - - gl_attr_force_inline static id_type get_entry( - const impl_type& self, id_type source_id, id_type target_id - ) { - return self._matrix[to_idx(source_id), to_idx(target_id)]; - } - - // --- vertex modifiers --- - - static void add_vertex(impl_type& self) { - const auto new_size = self._matrix.n_rows() + 1uz; - self._matrix.resize(new_size, new_size, invalid_id); - } - - static void add_vertices(impl_type& self, size_type n) { - const auto new_size = self._matrix.n_rows() + n; - self._matrix.resize(new_size, new_size, invalid_id); - } - - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { - const auto vertex_idx = to_idx(vertex_id); - std::vector removed_edges; - removed_edges.reserve(self._matrix.size() * 2uz); - - // extract out-edges - for (auto edge_id : self._matrix[vertex_idx]) - if (edge_id != invalid_id) - removed_edges.push_back(edge_id); - - // extract in-edges - const auto col = self._matrix.col(vertex_idx); - for (auto r_idx = 0uz; r_idx < self._matrix.n_rows(); ++r_idx) { - if (r_idx == vertex_idx) - continue; - - const auto edge_id = col[to_diff(r_idx)]; - if (edge_id != invalid_id) - removed_edges.push_back(edge_id); - } - - // elegantly remove from the 2D grid - self._matrix.erase_row(vertex_idx); - self._matrix.erase_col(vertex_idx); - - return removed_edges; - } +template +class directed_flat_adjacency_matrix { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; + using storage_type = flat_matrix; // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( - const impl_type& self, id_type vertex_id - ) { - return util::concat(predecessor_ids(self, vertex_id), successor_ids(self, vertex_id)); - } - - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto predecessor_ids( + this const auto& self, id_type vertex_id ) { return std::views::iota(initial_id_v, static_cast(self._matrix.n_rows())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto r_id) { @@ -130,20 +71,20 @@ struct directed_flat_adjacency_matrix { }); } - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline auto successor_ids(this const auto& self, id_type vertex_id) { return std::views::iota(initial_id_v, static_cast(self._matrix.n_cols())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto c_id) { return self._matrix[v_idx, to_idx(c_id)] != invalid_id; }); } + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(this const auto& self, id_type vertex_id) { + return util::concat(self.predecessor_ids(vertex_id), self.successor_ids(vertex_id)); + } + // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { size_type deg = 0uz; const auto vertex_idx = to_idx(vertex_id); const auto row = self._matrix[vertex_idx]; @@ -157,22 +98,20 @@ struct directed_flat_adjacency_matrix { return deg; } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { return static_cast(std::ranges::count_if( self._matrix.col(to_idx(vertex_id)), [](auto edge_id) { return edge_id != invalid_id; } )); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { const auto row = self._matrix[to_idx(vertex_id)]; return row.size() - static_cast(std::ranges::count(row, invalid_id_v)); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._matrix.n_rows(), 0uz); for (auto src_idx = 0uz; src_idx < self._matrix.n_rows(); ++src_idx) { @@ -187,7 +126,7 @@ struct directed_flat_adjacency_matrix { return degree_map; } - [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { + [[nodiscard]] std::vector in_degree_map(this const auto& self) { std::vector in_degree_map(self._matrix.n_rows(), 0uz); for (const auto row : self._matrix.rows()) @@ -198,23 +137,23 @@ struct directed_flat_adjacency_matrix { return in_degree_map; } - [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { + [[nodiscard]] std::vector out_degree_map(this const auto& self) { return std::views::iota(initial_id_v, static_cast(self._matrix.n_rows())) - | std::views::transform([&](id_type id) { return out_degree(self, id); }) + | std::views::transform([&self](id_type id) { return self.out_degree(id); }) | std::ranges::to(); } // --- edge modifiers --- - static inline void add_edge( - impl_type& self, id_type edge_id, id_type source_id, id_type target_id + gl_attr_force_inline void add_edge( + this auto& self, id_type edge_id, id_type source_id, id_type target_id ) { detail::check_edge_override(self._matrix, source_id, target_id); self._matrix[to_idx(source_id), to_idx(target_id)] = edge_id; } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -227,104 +166,102 @@ struct directed_flat_adjacency_matrix { matrix_source_row[to_diff(target_id)] = edge_id; } - gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { - detail::strict_get(self._matrix, edge) = invalid_id; - } - - // --- edge getters --- - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return util::concat(self.in_edges(vertex_id), self.out_edges(vertex_id)); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return util::concat( - self.in_edges(vertex_id, edge_properties_map), - self.out_edges(vertex_id, edge_properties_map) - ); - } -}; - -template AdjacencyMatrix> -requires(traits::c_undirected_edge) -struct undirected_flat_adjacency_matrix { - using impl_type = AdjacencyMatrix; - using id_type = typename impl_type::id_type; - using storage_type = typename impl_type::adjacency_storage_type; - - using vertex_type = typename impl_type::vertex_type; - using edge_type = typename impl_type::edge_type; + // --- comparison --- - // --- general --- + [[nodiscard]] friend bool + operator==(const directed_flat_adjacency_matrix&, const directed_flat_adjacency_matrix&) = + default; - gl_attr_force_inline static void init(impl_type& self, size_type n_vertices) { +protected: + gl_attr_force_inline void _init(this auto& self, size_type n_vertices) { self._matrix = storage_type(n_vertices, n_vertices, invalid_id); } - gl_attr_force_inline static id_type get_entry( - const impl_type& self, id_type source_id, id_type target_id - ) { - return self._matrix[to_idx(source_id), to_idx(target_id)]; - } - // --- vertex modifiers --- - static void add_vertex(impl_type& self) { + void _add_vertex_impl(this auto& self) { const auto new_size = self._matrix.n_rows() + 1uz; self._matrix.resize(new_size, new_size, invalid_id); } - static void add_vertices(impl_type& self, size_type n) { + void _add_vertices_impl(this auto& self, size_type n) { const auto new_size = self._matrix.n_rows() + n; self._matrix.resize(new_size, new_size, invalid_id); } - static std::vector remove_vertex(impl_type& self, id_type vertex_id) { + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); + std::vector removed_edges; + removed_edges.reserve(self._matrix.size() * 2uz); - const auto removed_edges = - self._matrix[vertex_idx] - | std::views::filter([](auto edge_id) { return edge_id != invalid_id; }) - | std::ranges::to(); + // extract out-edges + for (auto edge_id : self._matrix[vertex_idx]) + if (edge_id != invalid_id) + removed_edges.push_back(edge_id); + + // extract in-edges + const auto col = self._matrix.col(vertex_idx); + for (auto r_idx = 0uz; r_idx < self._matrix.n_rows(); ++r_idx) { + if (r_idx == vertex_idx) + continue; + const auto edge_id = col[to_diff(r_idx)]; + if (edge_id != invalid_id) + removed_edges.push_back(edge_id); + } + + // elegantly remove from the 2D grid self._matrix.erase_row(vertex_idx); self._matrix.erase_col(vertex_idx); return removed_edges; } + // --- edge modifiers --- + + gl_attr_force_inline void _remove_edge_impl(this auto& self, const auto& edge) { + detail::strict_get(self._matrix, edge) = invalid_id; + } + + // --- edge getters --- + + [[nodiscard]] gl_attr_force_inline id_type + _get_entry_impl(this const auto& self, id_type source_id, id_type target_id) { + return self._matrix[to_idx(source_id), to_idx(target_id)]; + } +}; + +template +class undirected_flat_adjacency_matrix { +public: + using traits_type = GraphTraits; + using id_type = typename traits_type::id_type; + using vertex_type = typename traits_type::vertex_type; + using edge_type = typename traits_type::edge_type; + using storage_type = flat_matrix; + // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline static auto neighbor_ids( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline auto neighbor_ids(this const auto& self, id_type vertex_id) { return std::views::iota(initial_id_v, static_cast(self._matrix.n_cols())) | std::views::filter([&self, v_idx = to_idx(vertex_id)](const auto c_id) { return self._matrix[v_idx, to_idx(c_id)] != invalid_id; }); } - [[nodiscard]] gl_attr_force_inline static auto predecessor_ids( - const impl_type& self, id_type vertex_id + [[nodiscard]] gl_attr_force_inline auto predecessor_ids( + this const auto& self, id_type vertex_id ) { - return neighbor_ids(self, vertex_id); + return self.neighbor_ids(vertex_id); } - [[nodiscard]] gl_attr_force_inline static auto successor_ids( - const impl_type& self, id_type vertex_id - ) { - return neighbor_ids(self, vertex_id); + [[nodiscard]] gl_attr_force_inline auto successor_ids(this const auto& self, id_type vertex_id) { + return self.neighbor_ids(vertex_id); } // --- degree getters --- - [[nodiscard]] gl_attr_force_inline static size_type degree( - const impl_type& self, id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline size_type degree(this const auto& self, id_type vertex_id) { const auto vertex_idx = to_idx(vertex_id); const auto row = self._matrix[vertex_idx]; return self._matrix.n_cols() @@ -332,19 +269,17 @@ struct undirected_flat_adjacency_matrix { + static_cast(self._matrix[vertex_idx, vertex_idx] != invalid_id); } - [[nodiscard]] gl_attr_force_inline static size_type in_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + in_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline static size_type out_degree( - const impl_type& self, id_type vertex_id - ) { - return degree(self, vertex_id); + [[nodiscard]] gl_attr_force_inline size_type + out_degree(this const auto& self, id_type vertex_id) { + return self.degree(vertex_id); } - [[nodiscard]] static std::vector degree_map(const impl_type& self) { + [[nodiscard]] std::vector degree_map(this const auto& self) { std::vector degree_map(self._matrix.n_rows(), 0uz); for (auto src_idx = 0uz; src_idx < self._matrix.n_rows(); ++src_idx) { @@ -359,21 +294,18 @@ struct undirected_flat_adjacency_matrix { return degree_map; } - [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( - const impl_type& self - ) { - return degree_map(self); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map(this const auto& self) { + return self.degree_map(); } - [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( - const impl_type& self + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map(this const auto& self ) { - return degree_map(self); + return self.degree_map(); } // --- edge modifiers --- - static void add_edge(impl_type& self, id_type edge_id, id_type source_id, id_type target_id) { + void add_edge(this auto& self, id_type edge_id, id_type source_id, id_type target_id) { detail::check_edge_override(self._matrix, source_id, target_id); const auto source_idx = to_idx(source_id); @@ -384,8 +316,8 @@ struct undirected_flat_adjacency_matrix { self._matrix[target_idx, source_idx] = edge_id; } - static void add_edges_from( - impl_type& self, + void add_edges_from( + this auto& self, const traits::c_forward_range_of auto& edge_ids, id_type source_id, const traits::c_forward_range_of auto& target_ids @@ -404,7 +336,47 @@ struct undirected_flat_adjacency_matrix { } } - static void remove_edge(impl_type& self, const edge_type& edge) { + // --- comparison --- + + [[nodiscard]] friend bool + operator==(const undirected_flat_adjacency_matrix&, const undirected_flat_adjacency_matrix&) = + default; + +protected: + gl_attr_force_inline void _init(this auto& self, size_type n_vertices) { + self._matrix = storage_type(n_vertices, n_vertices, invalid_id); + } + + // --- vertex modifiers --- + + void _add_vertex_impl(this auto& self) { + const auto new_size = self._matrix.n_rows() + 1uz; + self._matrix.resize(new_size, new_size, invalid_id); + } + + void _add_vertices_impl(this auto& self, size_type n) { + const auto new_size = self._matrix.n_rows() + n; + self._matrix.resize(new_size, new_size, invalid_id); + } + + std::vector _remove_vertex_impl(this auto& self, id_type vertex_id) { + const auto vertex_idx = to_idx(vertex_id); + + std::vector removed_edges; + for (auto edge_id : self._matrix[vertex_idx]) { + if (edge_id != invalid_id) + removed_edges.push_back(edge_id); + } + + self._matrix.erase_row(vertex_idx); + self._matrix.erase_col(vertex_idx); + + return removed_edges; + } + + // --- edge modifiers --- + + void _remove_edge_impl(this auto& self, const auto& edge) { if (edge.is_loop()) { detail::strict_get(self._matrix, edge) = invalid_id; } @@ -416,37 +388,18 @@ struct undirected_flat_adjacency_matrix { // --- edge getters --- - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id - ) { - return self.out_edges(vertex_id); - } - - [[nodiscard]] gl_attr_force_inline static auto incident_edges( - const impl_type& self, id_type vertex_id, auto& edge_properties_map - ) { - return self.out_edges(vertex_id, edge_properties_map); + [[nodiscard]] gl_attr_force_inline id_type + _get_entry_impl(this const auto& self, id_type source_id, id_type target_id) { + return self._matrix[to_idx(source_id), to_idx(target_id)]; } }; -template AdjacencyMatrix> -requires traits::c_directed_edge - and std::same_as -struct adjacency_matrix_impl_traits { - using type = directed_flat_adjacency_matrix; - - template - using storage_type = flat_matrix; -}; - -template AdjacencyMatrix> -requires traits::c_undirected_edge - and std::same_as -struct adjacency_matrix_impl_traits { - using type = undirected_flat_adjacency_matrix; - - template - using storage_type = flat_matrix; +template +struct adjacency_matrix_base { + using type = std::conditional_t< + traits::c_directed_graph_traits, + directed_flat_adjacency_matrix, + undirected_flat_adjacency_matrix>; }; } // namespace gl::impl::specialized From 064fb9d0fbca1225f80db0b183e50becda0cc58a Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 27 Jul 2026 15:18:23 +0200 Subject: [PATCH 11/13] algs alignment --- include/gl/algorithm/core.hpp | 8 +- include/gl/algorithm/pathfinding/dijkstra.hpp | 23 +- .../gl/algorithm/spanning_tree/prim_mst.hpp | 22 +- include/gl/algorithm/templates/bfs.hpp | 16 +- include/gl/algorithm/templates/dfs.hpp | 30 +- include/gl/algorithm/templates/pfs.hpp | 19 +- include/gl/algorithm/topology/coloring.hpp | 18 +- .../algorithm/topology/topological_sort.hpp | 12 +- .../traversal/breadth_first_search.hpp | 10 +- .../traversal/depth_first_search.hpp | 20 +- include/gl/algorithm/util.hpp | 7 +- include/gl/conversion.hpp | 5 +- include/gl/graph.hpp | 288 ++++++++++-------- tests/source/gl/test_alg_bfs.cpp | 3 +- tests/source/gl/test_alg_dfs.cpp | 5 +- tests/source/gl/test_alg_dijkstra.cpp | 2 +- 16 files changed, 260 insertions(+), 228 deletions(-) diff --git a/include/gl/algorithm/core.hpp b/include/gl/algorithm/core.hpp index 731505fc..8fc434ec 100644 --- a/include/gl/algorithm/core.hpp +++ b/include/gl/algorithm/core.hpp @@ -122,15 +122,15 @@ using non_void_result_type = /// @ingroup GL-Algorithm /// @brief Maps a vertex ID to its predecessor's ID in a traversal tree. /// @tparam GraphType The type of the graph being traversed. -template -using predecessors_map = std::vector; +template +using predecessors_map = std::vector>; /// @ingroup GL-Algorithm /// @brief Represents an active node in a search container (e.g., a BFS queue or DFS stack). /// @tparam GraphType The type of the graph being searched. -template +template struct search_node { - using id_type = typename GraphType::id_type; + using id_type = id_t; /// @brief Constructs a search node acting as a root (predecessor is itself). /// @param vertex_id The ID of the vertex. diff --git a/include/gl/algorithm/pathfinding/dijkstra.hpp b/include/gl/algorithm/pathfinding/dijkstra.hpp index f811aacb..7917ca0d 100644 --- a/include/gl/algorithm/pathfinding/dijkstra.hpp +++ b/include/gl/algorithm/pathfinding/dijkstra.hpp @@ -25,7 +25,7 @@ namespace gl::algorithm { /// | VertexDistanceType | The numeric type used to represent accumulated path weights/distances. | Must satisfy the [**c_arithmetic**](gl_concepts.md#gl-traits-c-arithmetic) concept. | template struct paths_descriptor { - using id_type = typename G::id_type; + using id_type = id_t; using distance_type = VertexDistanceType; /// @brief Constructs a descriptor sized for the given number of vertices. @@ -43,7 +43,7 @@ struct paths_descriptor { /// @brief An alias for @ref gl::algorithm::paths_descriptor "paths_descriptor" that automatically deduces the appropriate distance type for the graph. /// @tparam G The type of the graph. template -using paths_descriptor_type = paths_descriptor>; +using paths_descriptor_type = paths_descriptor>; /// @ingroup GL-Algorithm /// @brief Factory function to create an initialized paths descriptor sized for the given graph. @@ -66,11 +66,11 @@ template template struct dijkstra_search_node { /// @brief The type of the vertex ID. - using id_type = typename G::id_type; + using id_type = id_t; id_type vertex_id; ///< @brief The ID of the vertex represented by this node. id_type pred_id; ///< The ID of the predecessor vertex used to reach this node. - vertex_distance_type + vertex_distance_t distance; ///< The accumulated distance from the source to this vertex at the time of enqueueing. }; @@ -126,17 +126,14 @@ struct dijkstra_search_node { /// @hideparams template < traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> [[nodiscard]] paths_descriptor_type dijkstra_shortest_paths( - const G& graph, - typename G::id_type source_id, - PreVisitCallback pre_visit = {}, - PostVisitCallback post_visit = {} + G&& graph, id_t source_id, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { - using id_type = typename G::id_type; - using edge_type = typename G::edge_type; - using distance_type = vertex_distance_type; + using id_type = id_t; + using edge_type = edge_t; + using distance_type = vertex_distance_t; auto paths = make_paths_descriptor(graph); diff --git a/include/gl/algorithm/spanning_tree/prim_mst.hpp b/include/gl/algorithm/spanning_tree/prim_mst.hpp index c234c98a..44c9276f 100644 --- a/include/gl/algorithm/spanning_tree/prim_mst.hpp +++ b/include/gl/algorithm/spanning_tree/prim_mst.hpp @@ -22,11 +22,11 @@ namespace gl::algorithm { template struct mst_descriptor { /// @brief The type of the graph. - using graph_type = G; + using graph_type = traits::graph_val_t; /// @brief The type of the edges stored in the graph. - using edge_type = typename graph_type::edge_type; + using edge_type = edge_t; /// @brief The numeric type used to represent accumulated tree weights. - using weight_type = vertex_distance_type; + using weight_type = vertex_distance_t; /// @brief Constructs a descriptor sized to hold the resulting tree edges. /// @param n_vertices The total number of vertices in the graph. @@ -77,9 +77,9 @@ struct mst_descriptor { /// - @ref gl::algorithm::vertex_heap_prim_mst "vertex_heap_prim_mst" For the vertex-heap variant of the Prim's MST finding algorithm. /// @hideparams template -[[nodiscard]] mst_descriptor edge_heap_prim_mst(const G& graph, typename G::id_type root_id) { +[[nodiscard]] mst_descriptor edge_heap_prim_mst(G&& graph, id_t root_id) { // type definitions - using edge_type = typename G::edge_type; + using edge_type = edge_t; struct edge_comparator { [[nodiscard]] gl_attr_force_inline bool operator()( @@ -160,7 +160,7 @@ template /// ### Template Parameters /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | -/// | G | The type of the undirected graph being traversed. | Must satisfy the [**c_undirected_graph**](gl_concepts.md#gl-traits-c-undirected-graph) concept and its @ref gl::vertex_distance_type "distance type" must satisfy [**c_has_numeric_limits_max**](gl_concepts.md#gl-traits-c-has-numeric-limits-max). +/// | G | The type of the undirected graph being traversed. | Must satisfy the [**c_undirected_graph**](gl_concepts.md#gl-traits-c-undirected-graph) concept and its @ref gl::vertex_distance_t "distance type" must satisfy [**c_has_numeric_limits_max**](gl_concepts.md#gl-traits-c-has-numeric-limits-max). /// /// @param graph The undirected graph to evaluate. /// @param root_id The starting vertex ID for the MST calculation. Defaults to the graph's `initial_id` if `invalid_id` is passed. @@ -169,12 +169,12 @@ template /// - @ref gl::algorithm::edge_heap_prim_mst "edge_heap_prim_mst" For the vertex-heap variant of the Prim's MST finding algorithm. /// @hideparams template -requires(traits::c_has_numeric_limits_max>) -[[nodiscard]] mst_descriptor vertex_heap_prim_mst(const G& graph, typename G::id_type root_id) { +requires(traits::c_has_numeric_limits_max>) +[[nodiscard]] mst_descriptor vertex_heap_prim_mst(G&& graph, id_t root_id) { // type definitions - using id_type = typename G::id_type; - using edge_type = typename G::edge_type; - using distance_type = vertex_distance_type; + using id_type = id_t; + using edge_type = edge_t; + using distance_type = vertex_distance_t; // Prepare the necessary utility const auto n_vertices = graph.n_vertices(); diff --git a/include/gl/algorithm/templates/bfs.hpp b/include/gl/algorithm/templates/bfs.hpp index 9e91c4fb..61a79be1 100644 --- a/include/gl/algorithm/templates/bfs.hpp +++ b/include/gl/algorithm/templates/bfs.hpp @@ -75,15 +75,13 @@ namespace gl::algorithm { template < traits::c_graph G, traits::c_forward_range_of> InitQueueRangeType = std::vector>, - traits::c_optional_predicate VisitVertexPredicate = empty_callback, - traits::c_optional_predicate VisitCallback = - empty_callback, - traits::c_decision_predicate - EnqueueNodePred = empty_callback, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_predicate> VisitVertexPredicate = empty_callback, + traits::c_optional_predicate, id_t> VisitCallback = empty_callback, + traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> bool bfs( - const G& graph, + G&& graph, const InitQueueRangeType& initial_queue_content, VisitVertexPredicate visit_vertex_pred = {}, VisitCallback visit = {}, @@ -101,7 +99,7 @@ bool bfs( // search the graph while (not q.empty()) { - const search_node node = q.front(); + const auto node = q.front(); q.pop(); if constexpr (not traits::c_empty_callback) diff --git a/include/gl/algorithm/templates/dfs.hpp b/include/gl/algorithm/templates/dfs.hpp index 3a659157..12779f75 100644 --- a/include/gl/algorithm/templates/dfs.hpp +++ b/include/gl/algorithm/templates/dfs.hpp @@ -74,15 +74,13 @@ namespace gl::algorithm { template < traits::c_graph G, traits::c_forward_range_of> InitStackRangeType = std::vector>, - traits::c_optional_predicate VisitVertexPredicate = empty_callback, - traits::c_optional_predicate VisitCallback = - empty_callback, - traits::c_decision_predicate - EnqueueNodePred = empty_callback, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_predicate> VisitVertexPredicate = empty_callback, + traits::c_optional_predicate, id_t> VisitCallback = empty_callback, + traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> bool dfs( - const G& graph, + G&& graph, const InitStackRangeType& initial_stack_content, VisitVertexPredicate visit_vertex_pred = {}, VisitCallback visit = {}, @@ -187,15 +185,15 @@ bool dfs( /// @hideparams template < traits::c_graph G, - traits::c_optional_predicate VisitVertexPredicate, - traits::c_optional_predicate VisitCallback, - traits::c_decision_predicate EnqueueNodePred, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_predicate> VisitVertexPredicate, + traits::c_optional_predicate, id_t> VisitCallback, + traits::c_decision_predicate, const edge_t&> EnqueueNodePred, + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> void r_dfs( - const G& graph, - const typename G::id_type vertex_id, - const typename G::id_type pred_id, + G&& graph, + const id_t vertex_id, + const id_t pred_id, VisitVertexPredicate visit_vertex_pred, VisitCallback visit, EnqueueNodePred enqueue_node_pred, diff --git a/include/gl/algorithm/templates/pfs.hpp b/include/gl/algorithm/templates/pfs.hpp index b44b5956..8ab3ad76 100644 --- a/include/gl/algorithm/templates/pfs.hpp +++ b/include/gl/algorithm/templates/pfs.hpp @@ -90,20 +90,15 @@ template < typename InitQueueRangeType = std::vector>, typename NodeType = std::ranges::range_value_t, traits::c_optional_predicate VisitVertexPredicate = empty_callback, - traits::c_optional_predicate VisitCallback = + traits::c_optional_predicate, id_t> VisitCallback = empty_callback, + traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, + traits::c_optional_callback, id_t, const edge_t&> MakeNodeCallback = empty_callback, - traits::c_decision_predicate - EnqueueNodePred = empty_callback, - traits::c_optional_callback< - NodeType, - typename G::id_type, - typename G::id_type, - const typename G::edge_type&> MakeNodeCallback = empty_callback, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> requires traits::c_predicate bool pfs( - const G& graph, + G&& graph, const PQCmp& pq_cmp, const InitQueueRangeType& initial_queue_content, VisitVertexPredicate visit_vertex_pred = {}, @@ -152,7 +147,7 @@ bool pfs( } else { static_assert( - std::constructible_from, + std::constructible_from, id_t>, "[gl::algorithm::pfs] Custom NodeType provided without a MakeNodeCallback. " "The NodeType must be constructible from (target_id, pred_id), or you must " "provide a MakeNodeCallback!" diff --git a/include/gl/algorithm/topology/coloring.hpp b/include/gl/algorithm/topology/coloring.hpp index 644c5592..bc8f5af7 100644 --- a/include/gl/algorithm/topology/coloring.hpp +++ b/include/gl/algorithm/topology/coloring.hpp @@ -62,13 +62,11 @@ using bicoloring_type = std::vector; /// @hideparams template < traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> [[nodiscard]] std::optional bipartite_coloring( - const G& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} + G&& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { - using edge_type = typename G::edge_type; - bicoloring_type coloring(graph.n_vertices(), binary_color::value::unset); for (const auto root_id : graph.vertex_ids()) { if (coloring[root_id].is_set()) @@ -82,7 +80,7 @@ template < init_node_range(root_id), empty_callback{}, // visit predicate empty_callback{}, // visit callback - [&coloring](typename G::id_type vertex_id, const edge_type& in_edge) + [&coloring](id_t vertex_id, const edge_t& in_edge) -> decision { // enqueue predicate if (in_edge.is_loop()) return decision::abort; // graph is not bipartite @@ -119,7 +117,7 @@ template < /// @return `true` if the graph is bipartite (2-colorable), `false` otherwise. /// ### See Also /// - @ref gl::algorithm::apply_coloring "apply_coloring" -[[nodiscard]] gl_attr_force_inline bool is_bipartite(const traits::c_graph auto& graph) { +[[nodiscard]] gl_attr_force_inline bool is_bipartite(traits::c_graph auto&& graph) { return bipartite_coloring(graph).has_value(); } @@ -129,7 +127,7 @@ template < /// ### Template Parameters /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | -/// | G | The type of the graph to modify. Must have compatible color properties. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept, and its properties must satisfy [**c_binary_color_properties_type**](gl_concepts.md#gl-traits-c-binary-color-properties-type). | +/// | G | The type of the graph to modify. Must have compatible color properties. | Must satisfy the [**c_mut_graph**](gl_concepts.md#gl-traits-c-mut-graph) concept, and its properties must satisfy [**c_binary_color_properties_type**](gl_concepts.md#gl-traits-c-binary-color-properties-type). | /// | ColorRange | The type of the range containing the computed colors. | Must satisfy the [**c_sized_range_of**](gl_concepts.md#gl-traits-c-sized-range-of) concept for `binary_color`. | /// /// @param graph The mutable graph instance whose properties will be updated. @@ -138,8 +136,8 @@ template < /// ### See Also /// - @ref gl::algorithm::bipartite_coloring "bipartite_coloring" /// - @ref gl::algorithm::is_bipartite "is_bipartite" -template ColorRange> -requires(traits::c_binary_color_properties_type) +template ColorRange> +requires(traits::c_binary_color_properties_type>) bool apply_coloring(G& graph, const ColorRange& color_range) { if (std::ranges::size(color_range) != graph.n_vertices()) return false; diff --git a/include/gl/algorithm/topology/topological_sort.hpp b/include/gl/algorithm/topology/topological_sort.hpp index 1134631d..554f8a73 100644 --- a/include/gl/algorithm/topology/topological_sort.hpp +++ b/include/gl/algorithm/topology/topological_sort.hpp @@ -61,13 +61,13 @@ namespace gl::algorithm { /// @hideparams template < traits::c_directed_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> -[[nodiscard]] std::optional> topological_sort( - const G& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> +[[nodiscard]] std::optional>> topological_sort( + G&& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { - using id_type = typename G::id_type; - using edge_type = typename G::edge_type; + using id_type = id_t; + using edge_type = edge_t; // prepare the vertex in degree map std::vector in_degree_map = graph.in_degree_map(); diff --git a/include/gl/algorithm/traversal/breadth_first_search.hpp b/include/gl/algorithm/traversal/breadth_first_search.hpp index 6ab6fc2d..2e30d72b 100644 --- a/include/gl/algorithm/traversal/breadth_first_search.hpp +++ b/include/gl/algorithm/traversal/breadth_first_search.hpp @@ -70,16 +70,16 @@ namespace gl::algorithm { template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> result_type> breadth_first_search( - const G& graph, - const typename G::id_type root_vertex_id = no_root, + G&& graph, + const id_t root_vertex_id = no_root, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { std::vector visited(graph.n_vertices(), false); - std::vector sources(graph.n_vertices()); + std::vector> sources(graph.n_vertices()); auto pred_map = init_predecessors_map(graph); diff --git a/include/gl/algorithm/traversal/depth_first_search.hpp b/include/gl/algorithm/traversal/depth_first_search.hpp index cd05c937..d181b725 100644 --- a/include/gl/algorithm/traversal/depth_first_search.hpp +++ b/include/gl/algorithm/traversal/depth_first_search.hpp @@ -78,16 +78,16 @@ namespace gl::algorithm { template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> result_type> depth_first_search( - const G& graph, - const typename G::id_type root_vertex_id = no_root, + G&& graph, + const id_t root_vertex_id = no_root, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { std::vector visited(graph.n_vertices(), false); - std::vector sources(graph.n_vertices()); + std::vector> sources(graph.n_vertices()); auto pred_map = init_predecessors_map(graph); @@ -178,16 +178,16 @@ result_type> depth_first_search( template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback PreVisitCallback = empty_callback, - traits::c_optional_callback PostVisitCallback = empty_callback> + traits::c_optional_callback> PreVisitCallback = empty_callback, + traits::c_optional_callback> PostVisitCallback = empty_callback> result_type> recursive_depth_first_search( - const G& graph, - const typename G::id_type root_vertex_id = no_root, + G&& graph, + const id_t root_vertex_id = no_root, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { std::vector visited(graph.n_vertices(), false); - std::vector sources(graph.n_vertices()); + std::vector> sources(graph.n_vertices()); auto pred_map = init_predecessors_map(graph); diff --git a/include/gl/algorithm/util.hpp b/include/gl/algorithm/util.hpp index b7bfde13..76c7b417 100644 --- a/include/gl/algorithm/util.hpp +++ b/include/gl/algorithm/util.hpp @@ -53,8 +53,7 @@ template template < traits::c_graph G, traits::c_forward_range_of> InitRangeType = std::vector>> -[[nodiscard]] gl_attr_force_inline InitRangeType init_node_range(typename G::id_type root_vertex_id -) { +[[nodiscard]] gl_attr_force_inline InitRangeType init_node_range(id_t root_vertex_id) { return InitRangeType{search_node{root_vertex_id}}; } @@ -78,7 +77,7 @@ template [[nodiscard]] gl_attr_force_inline auto default_visit_callback( std::vector& visited, non_void_result_type>& pred_map ) { - using id_type = typename G::id_type; + using id_type = id_t; return [&](id_type vertex_id, id_type pred_id) { const auto vertex_idx = to_idx(vertex_id); visited[vertex_idx] = true; @@ -97,7 +96,7 @@ template template [[nodiscard]] gl_attr_force_inline auto default_enqueue_node_predicate(std::vector& visited) { using return_t = std::conditional_t; - return [&](typename G::id_type vertex_id, const typename G::edge_type&) -> return_t { + return [&](id_t vertex_id, const edge_t&) -> return_t { return not visited[to_idx(vertex_id)]; }; } diff --git a/include/gl/conversion.hpp b/include/gl/conversion.hpp index c27fdeb8..0caa5c6a 100644 --- a/include/gl/conversion.hpp +++ b/include/gl/conversion.hpp @@ -169,7 +169,7 @@ struct to_impl { /// | Parameter | Description | Constraints | /// | :------------ | :---------- | :---------- | /// | TargetImplTag | The representation tag of the desired target representation (e.g., `gl::repr::flat_list_t`) | [**c_graph_repr_tag**](gl_concepts.md#gl-traits-c-graph-repr-tag) | -/// | Graph | The type of the source graph, which will be automatically deduced from the function argument. | [**c_graph**](gl_concepts.md#gl-traits-c-graph) | +/// | Graph | The type of the source graph, which will be automatically deduced from the function argument. | [**c_graph**](gl_concepts.md#gl-traits-c-graph) and must **NOT** be an Lvalue reference | /// /// @param source The graph to convert. After the operation it will be left in a valid, empty state. /// @return A new graph containing the moved data, structured according to `TargetImplTag`. @@ -177,6 +177,7 @@ struct to_impl { /// ### See Also /// - @ref gl::traits::swap_repr_tag "swap_repr_tag" : For the trait used to resolve the target graph type with the swapped representation tag. template +requires(not std::is_lvalue_reference_v) [[nodiscard]] auto to(Graph&& source) { using source_traits = typename Graph::traits_type; using source_impl_tag = typename source_traits::representation_tag; @@ -192,7 +193,7 @@ template target._n_edges = std::exchange(source._n_edges, 0uz); target._vertex_properties = std::move(source._vertex_properties); target._edge_properties = std::move(source._edge_properties); - source._impl = typename Graph::implementation_type(); + source._impl = typename Graph::implementation_type{}; return target; } diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index f4922125..7a5a8cd6 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -33,32 +33,43 @@ namespace traits { /// @ingroup GL-Traits /// @brief Concept checking if a type is an instantiation of the generic @ref "gl::graph" graph class. template -concept c_graph = c_instantiation_of; +concept c_graph = c_instantiation_of, graph>; + +/// @ingroup GL-Traits +/// @brief Concept checking if a type is a mutable instantiation of the generic @ref "gl::graph" graph class. +template +concept c_mut_graph = c_graph and not std::is_const_v>; + +template +using graph_val_t = std::remove_cvref_t; /// @ingroup GL-Traits /// @brief Concept checking if a graph is directed. /// @see gl::directed_t "directed_t" : For the directional tag used to specify directed graph configuration. template -concept c_directed_graph = c_graph and std::same_as; +concept c_directed_graph = + c_graph and std::same_as::directional_tag, directed_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph is undirected. /// @see gl::undirected_t "undirected_t" : For the directional tag used to specify undirected graph configuration. template -concept c_undirected_graph = c_graph and std::same_as; +concept c_undirected_graph = + c_graph and std::same_as::directional_tag, undirected_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes the standard adjacency list representation. /// @see gl::repr::list_t "list_t" : For the representation tag used to specify the standard adjacency list representation. template -concept c_list_graph = c_graph and std::same_as; +concept c_list_graph = + c_graph and std::same_as::representation_tag, repr::list_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes the flattened adjacency list representation. /// @see gl::repr::flat_list_t "flat_list_t" : For the representation tag used to specify the flattened adjacency list representation. template concept c_flat_list_graph = - c_graph and std::same_as; + c_graph and std::same_as::representation_tag, repr::flat_list_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes any list-based adjacency representation. @@ -73,14 +84,14 @@ concept c_adjacency_list_graph = c_list_graph or c_flat_list_graph; /// @see gl::repr::matrix_t "matrix_t" : For the representation tag used to specify the standard adjacency matrix representation. template concept c_matrix_graph = - c_graph and std::same_as; + c_graph and std::same_as::representation_tag, repr::matrix_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes the flattened adjacency matrix representation. /// @see gl::repr::flat_matrix_t "flat_matrix_t" : For the representation tag used to specify the flattened adjacency matrix representation. template concept c_flat_matrix_graph = - c_graph and std::same_as; + c_graph and std::same_as::representation_tag, repr::flat_matrix_t>; /// @ingroup GL-Traits /// @brief Concept checking if a graph utilizes any matrix-based adjacency representation. @@ -97,7 +108,10 @@ concept c_adjacency_matrix_graph = c_matrix_graph or c_flat_matrix_graph; template concept c_graph_vertex = c_graph - and c_one_of, typename G::vertex_type, typename G::const_vertex_type>; + and c_one_of< + std::remove_cvref_t, + typename graph_val_t::vertex_type, + typename graph_val_t::const_vertex_type>; /// @ingroup GL-Traits /// @brief Concept checking if a type is a mutable or immutable edge descriptor associated with the given graph. @@ -106,14 +120,45 @@ concept c_graph_vertex = template concept c_graph_edge = c_graph - and c_one_of, typename G::edge_type, typename G::const_edge_type>; + and c_one_of< + std::remove_cvref_t, + typename graph_val_t::edge_type, + typename graph_val_t::const_edge_type>; } // namespace traits +template +using id_t = typename traits::graph_val_t::id_type; + +template +using vertex_t = std::conditional_t< + std::is_const_v>, + typename traits::graph_val_t::const_vertex_type, + typename traits::graph_val_t::vertex_type>; + +template +using vertex_properties_t = std::conditional_t< + std::is_const_v>, + const typename traits::graph_val_t::vertex_properties_type, + typename traits::graph_val_t::vertex_properties_type>; + +template +using edge_t = std::conditional_t< + std::is_const_v>, + typename traits::graph_val_t::const_edge_type, + typename traits::graph_val_t::edge_type>; + +template +using edge_properties_t = std::conditional_t< + std::is_const_v>, + const typename traits::graph_val_t::edge_properties_type, + typename traits::graph_val_t::edge_properties_type>; + template [[nodiscard]] Graph clone(const Graph& source); template +requires(not std::is_lvalue_reference_v) [[nodiscard]] auto to(Graph&& source); namespace detail { @@ -239,10 +284,6 @@ class graph final { /// @brief Type tag indicating the underlying representation model. using representation_tag = typename traits_type::representation_tag; - /// @brief The underlying representation type matching the directional tag. - using implementation_type = typename representation_tag::template type; - friend implementation_type; - /// @brief Integral type used to identify vertices and edges. using id_type = typename traits_type::id_type; @@ -261,33 +302,14 @@ class graph final { using edge_properties_type = typename traits_type::edge_properties_type; private: - template - using deduced_vertex_type = std::conditional_t< - std::is_const_v>, - const_vertex_type, - vertex_type>; - - template - using deduced_vertex_properties_type = std::conditional_t< - std::is_const_v>, - const vertex_properties_type, - vertex_properties_type>; + using implementation_type = typename representation_tag::template type; + friend implementation_type; using vertex_properties_map_type = std::conditional_t< traits::c_empty_properties, empty_properties_map, std::vector>; - template - using deduced_edge_type = std:: - conditional_t>, const_edge_type, edge_type>; - - template - using deduced_edge_properties_type = std::conditional_t< - std::is_const_v>, - const edge_properties_type, - edge_properties_type>; - using edge_properties_map_type = std::conditional_t< traits::c_empty_properties, empty_properties_map, @@ -459,7 +481,7 @@ class graph final { /// @return The corresponding vertex descriptor (const or mutable). /// @throws std::invalid_argument If the ID is invalid. template - [[nodiscard]] deduced_vertex_type vertex(this Self& self, const id_type vertex_id) { + [[nodiscard]] vertex_t vertex(this Self& self, const id_type vertex_id) { self._verify_vertex_id(vertex_id); return self.vertex_unchecked(vertex_id); } @@ -469,9 +491,7 @@ class graph final { /// @return The corresponding vertex descriptor. /// @throws std::invalid_argument If the ID is invalid. template - [[nodiscard]] gl_attr_force_inline deduced_vertex_type at( - this Self& self, const id_type vertex_id - ) { + [[nodiscard]] gl_attr_force_inline vertex_t at(this Self& self, const id_type vertex_id) { return self.vertex(vertex_id); } @@ -483,13 +503,13 @@ class graph final { /// > /// > No bounds checking is performed. Passing an invalid ID results in Undefined Behavior. template - [[nodiscard]] gl_attr_force_inline deduced_vertex_type vertex_unchecked( + [[nodiscard]] gl_attr_force_inline vertex_t vertex_unchecked( this Self& self, const id_type vertex_id ) { if constexpr (traits::c_non_empty_properties) - return deduced_vertex_type{vertex_id, self._vertex_properties[vertex_id]}; + return vertex_t{vertex_id, self._vertex_properties[vertex_id]}; else - return deduced_vertex_type{vertex_id}; + return vertex_t{vertex_id}; } /// @brief Returns a vertex descriptor without bounds checking (array access style). @@ -500,7 +520,7 @@ class graph final { /// > /// > No bounds checking is performed. Passing an invalid ID results in Undefined Behavior. template - [[nodiscard]] gl_attr_force_inline deduced_vertex_type operator[]( + [[nodiscard]] gl_attr_force_inline vertex_t operator[]( this Self& self, const id_type vertex_id ) noexcept { return self.vertex_unchecked(vertex_id); @@ -508,7 +528,7 @@ class graph final { /// @brief Returns a lazily evaluated view of all vertex descriptors in the graph. /// @return A view yielding descriptors for every vertex. - [[nodiscard]] gl_attr_force_inline auto vertices(this auto&& self) noexcept { + [[nodiscard]] gl_attr_force_inline auto vertices(this auto& self) noexcept { return self.vertex_ids() | std::views::transform(self._create_vertex_descriptor()); } @@ -523,7 +543,7 @@ class graph final { /// @param vertex_id The ID of the source vertex. /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto neighbors(this auto&& self, const id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline auto neighbors(this auto& self, const id_type vertex_id) { return self.neighbor_ids(vertex_id) | std::views::transform(self._create_vertex_descriptor()); } @@ -534,7 +554,7 @@ class graph final { /// @return A view of all adjacent vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. [[nodiscard]] gl_attr_force_inline auto neighbors( - this auto&& self, traits::c_graph_vertex auto vertex + this auto& self, traits::c_graph_vertex auto vertex ) { return self.neighbors(vertex.id()); } @@ -564,7 +584,7 @@ class graph final { /// @param vertex_id The ID of the target vertex. /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto predecessors(this auto&& self, const id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline auto predecessors(this auto& self, const id_type vertex_id) { return self.predecessor_ids(vertex_id) | std::views::transform(self._create_vertex_descriptor()); } @@ -575,7 +595,7 @@ class graph final { /// @return A view of all predecessor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. [[nodiscard]] gl_attr_force_inline auto predecessors( - this auto&& self, traits::c_graph_vertex auto vertex + this auto& self, traits::c_graph_vertex auto vertex ) { return self.predecessors(vertex.id()); } @@ -606,7 +626,7 @@ class graph final { /// @param vertex_id The ID of the source vertex. /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] gl_attr_force_inline auto successors(this auto&& self, const id_type vertex_id) { + [[nodiscard]] gl_attr_force_inline auto successors(this auto& self, const id_type vertex_id) { return self.successor_ids(vertex_id) | std::views::transform(self._create_vertex_descriptor()); } @@ -617,7 +637,7 @@ class graph final { /// @return A view of all successor vertex descriptors. /// @throws std::invalid_argument If the vertex descriptor is invalid. [[nodiscard]] gl_attr_force_inline auto successors( - this auto&& self, traits::c_graph_vertex auto vertex + this auto& self, traits::c_graph_vertex auto vertex ) { return self.successors(vertex.id()); } @@ -647,7 +667,7 @@ class graph final { /// @return A reference to the properties attached to the vertex. /// @throws std::invalid_argument If the vertex ID is invalid. template - [[nodiscard]] gl_attr_force_inline deduced_vertex_properties_type& vertex_properties( + [[nodiscard]] gl_attr_force_inline vertex_properties_t& vertex_properties( this Self& self, const id_type id ) requires(traits::c_non_empty_properties) @@ -658,7 +678,7 @@ class graph final { /// @brief Retrieves a random-access view over all vertex properties in the graph. /// @return A view mapping each active vertex index to its property. - [[nodiscard]] gl_attr_force_inline auto vertex_properties_map(this auto&& self) noexcept + [[nodiscard]] gl_attr_force_inline auto vertex_properties_map(this auto& self) noexcept requires(traits::c_non_empty_properties) { return std::views::all(self._vertex_properties); @@ -949,13 +969,17 @@ class graph final { /// @param target_id The target vertex ID. /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex ID is invalid. - [[nodiscard]] std::optional edge(const id_type source_id, const id_type target_id) - const { - this->_verify_vertex_ids(source_id, target_id); + template + [[nodiscard]] std::optional> edge( + this Self& self, const id_type source_id, const id_type target_id + ) { + self._verify_vertex_ids(source_id, target_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.edge(source_id, target_id, this->_edge_properties); + return self._impl.template edge>( + source_id, target_id, self._edge_properties + ); else - return this->_impl.edge(source_id, target_id); + return self._impl.template edge>(source_id, target_id); } /// @brief Retrieves an edge (if it exists) connecting the source to the target. @@ -963,10 +987,13 @@ class graph final { /// @param target The target vertex descriptor. /// @return An optional containing the edge descriptor if it exists, or std::nullopt otherwise. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline std::optional edge( - traits::c_graph_vertex auto source, traits::c_graph_vertex auto target - ) const { - return this->edge(source.id(), target.id()); + template + [[nodiscard]] gl_attr_force_inline std::optional> edge( + this Self& self, + traits::c_graph_vertex auto source, + traits::c_graph_vertex auto target + ) { + return self.edge(source.id(), target.id()); } /// @brief Retrieves all parallel edges connecting the source to the target. @@ -974,14 +1001,17 @@ class graph final { /// @param target_id The target vertex ID. /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex ID is invalid. - [[nodiscard]] inline std::vector edges( - const id_type source_id, const id_type target_id - ) const { - this->_verify_vertex_ids(source_id, target_id); + template + [[nodiscard]] inline std::vector> edges( + this Self& self, const id_type source_id, const id_type target_id + ) { + self._verify_vertex_ids(source_id, target_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.edges(source_id, target_id, this->_edge_properties); + return self._impl.template edges>( + source_id, target_id, self._edge_properties + ); else - return this->_impl.edges(source_id, target_id); + return self._impl.template edges>(source_id, target_id); } /// @brief Retrieves all parallel edges connecting the source to the target. @@ -989,94 +1019,110 @@ class graph final { /// @param target The target vertex descriptor. /// @return A vector populated with the descriptors of all edges linking the two vertices. /// @throws std::invalid_argument If either vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline std::vector edges( - traits::c_graph_vertex auto source, traits::c_graph_vertex auto target - ) const { - return this->edges(source.id(), target.id()); + template + [[nodiscard]] gl_attr_force_inline std::vector> edges( + this Self& self, + traits::c_graph_vertex auto source, + traits::c_graph_vertex auto target + ) { + return self.edges(source.id(), target.id()); } /// @brief Retrieves all edges incident with a vertex. /// @param vertex_id The vertex ID. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto incident_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] inline auto incident_edges(this Self& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.incident_edges(vertex_id, this->_edge_properties); + return self._impl.template incident_edges>( + vertex_id, self._edge_properties + ); else - return this->_impl.incident_edges(vertex_id); + return self._impl.template incident_edges>(vertex_id); } /// @brief Retrieves all edges incident with a vertex. /// @param vertex The vertex descriptor. /// @return A view representing the set of incident edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto incident_edges(traits::c_graph_vertex auto vertex - ) const { - return this->incident_edges(vertex.id()); + template + [[nodiscard]] gl_attr_force_inline auto incident_edges( + this Self& self, traits::c_graph_vertex auto vertex + ) { + return self.incident_edges(vertex.id()); } /// @brief Retrieves all incoming edges of a vertex (going into the vertex). /// @param vertex_id The vertex ID. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto in_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] inline auto in_edges(this Self& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.in_edges(vertex_id, this->_edge_properties); + return self._impl.template in_edges>(vertex_id, self._edge_properties); else - return this->_impl.in_edges(vertex_id); + return self._impl.template in_edges>(vertex_id); } /// @brief Retrieves all incoming edges of a vertex (going into the vertex). /// @param vertex The vertex descriptor. /// @return A view representing the set of incoming edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto in_edges(traits::c_graph_vertex auto vertex - ) const { - return this->in_edges(vertex.id()); + template + [[nodiscard]] gl_attr_force_inline auto in_edges( + this Self& self, traits::c_graph_vertex auto vertex + ) { + return self.in_edges(vertex.id()); } /// @brief Retrieves all outgoing edges of a vertex (going out of the vertex). /// @param vertex_id The vertex ID. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex ID is invalid. - [[nodiscard]] inline auto out_edges(const id_type vertex_id) const { - this->_verify_vertex_id(vertex_id); + template + [[nodiscard]] inline auto out_edges(this Self& self, const id_type vertex_id) { + self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) - return this->_impl.out_edges(vertex_id, this->_edge_properties); + return self._impl.template out_edges>(vertex_id, self._edge_properties); else - return this->_impl.out_edges(vertex_id); + return self._impl.template out_edges>(vertex_id); } /// @brief Retrieves all outgoing edges of a vertex (going out of the vertex). /// @param vertex The vertex descriptor. /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex descriptor is invalid. - [[nodiscard]] gl_attr_force_inline auto out_edges(traits::c_graph_vertex auto vertex - ) const { - return this->out_edges(vertex.id()); + [[nodiscard]] gl_attr_force_inline auto out_edges( + this auto& self, traits::c_graph_vertex auto vertex + ) { + return self.out_edges(vertex.id()); } /// @brief Retrieves a mutable reference to an edge's properties. /// @param id The ID of the edge. /// @return A reference to the properties attached to the edge. /// @throws std::invalid_argument If the edge ID is invalid. - [[nodiscard]] edge_properties_type& edge_properties(const id_type id) const + template + [[nodiscard]] gl_attr_force_inline edge_properties_t& edge_properties( + this Self& self, const id_type id + ) requires(traits::c_non_empty_properties) { - if (id >= this->_n_edges) + if (id >= self._n_edges) throw std::invalid_argument(std::format("Got invalid edge id [{}]", id)); - return this->_edge_properties[id]; + return self._edge_properties[id]; } /// @brief Retrieves a view over all edge properties in the graph. /// @return A view mapping each active edge index to its property. - [[nodiscard]] gl_attr_force_inline auto edge_properties_map() const noexcept + [[nodiscard]] gl_attr_force_inline auto edge_properties_map(this auto& self) noexcept requires(traits::c_non_empty_properties) { - return std::views::all(this->_edge_properties); + return std::views::all(self._edge_properties); } // --- adjacency and incidence methods --- @@ -1237,6 +1283,7 @@ class graph final { /// @brief Friend declaration providing access for graph target conversions. template + requires(not std::is_lvalue_reference_v) friend auto to(Graph&& source); /// @brief Internal friend structure for `to` conversion operations. @@ -1296,7 +1343,7 @@ class graph final { gl_attr_force_inline auto _create_vertex_descriptor(this Self&) noexcept requires(traits::c_empty_properties) { - return [](const id_type id) { return deduced_vertex_type{id}; }; + return [](const id_type id) { return vertex_t{id}; }; } template @@ -1304,7 +1351,7 @@ class graph final { requires(traits::c_non_empty_properties) { return [&pmap = self._vertex_properties](const id_type id) { - return deduced_vertex_type{id, pmap[to_idx(id)]}; + return vertex_t{id, pmap[to_idx(id)]}; }; } @@ -1312,9 +1359,10 @@ class graph final { using fmt_traits = io::detail::graph_fmt_traits; + template struct concise_target_formatter { - edge_type edge; - id_type src_id; + edge_t edge; + id_t src_id; bool with_props; friend std::ostream& operator<<(std::ostream& os, const concise_target_formatter& proxy) { @@ -1338,16 +1386,16 @@ class graph final { return os; } - std::ostream& _concise_write(std::ostream& os) const { + template + std::ostream& _concise_write(this Self&& self, std::ostream& os) { using enum io::detail::option_bit; - for (auto src : this->vertices()) { + for (auto src : self.vertices()) { auto tgts = std::views::transform( - this->out_edges(src.id()), - [src_id = src.id(), - with_props = io::is_option_set(os, with_connection_properties)](const auto& edge) { - return concise_target_formatter{edge, src_id, with_props}; - } + self.out_edges(src.id()), + [src_id = src.id(), with_props = io::is_option_set(os, with_connection_properties)]( + const edge_t& edge + ) { return concise_target_formatter{edge, src_id, with_props}; } ); os << src << " : " << io::range_formatter(tgts) << '\n'; } @@ -1485,7 +1533,7 @@ class graph final { implementation_type _impl{}; [[no_unique_address]] vertex_properties_map_type _vertex_properties{}; - [[no_unique_address]] mutable edge_properties_map_type _edge_properties{}; + [[no_unique_address]] edge_properties_map_type _edge_properties{}; }; // --- general graph utility --- @@ -1567,7 +1615,7 @@ using default_vertex_distance_type = std::int64_t; /// @ingroup GL-Core /// @brief Utility trait to resolve the underlying distance or weight numeric type for a graph. -template +template struct vertex_distance { /// @brief Resolves to the default distance type if no specific weight property is detected. using type = default_vertex_distance_type; @@ -1575,31 +1623,31 @@ struct vertex_distance { /// @ingroup GL-Core /// @brief Specialization resolving the specific weight type when edge properties contain weight attributes. -template -requires(traits::c_weight_properties_type) -struct vertex_distance { +template +requires(traits::c_weight_properties_type>) +struct vertex_distance { /// @brief Resolves to the `weight_type` inherently mapped to the graph's edges. - using type = typename GraphType::edge_properties_type::weight_type; + using type = typename edge_properties_t::weight_type; }; /// @ingroup GL-Core /// @brief Convenience alias to retrieve the appropriate distance numeric type from a graph structure. -template -using vertex_distance_type = typename vertex_distance::type; +template +using vertex_distance_t = typename vertex_distance::type; /// @ingroup GL-Core /// @brief Helper utility to safely retrieve the weight payload of an edge. /// /// Automatically returns a constant default if the graph representation carries no attached weight properties. /// -/// @tparam GraphType The underlying type of the graph. +/// @tparam G The underlying type of the graph. /// @param edge The edge descriptor to extract weight from. /// @return The specified edge weight or the unweighted graph default (1). -template -[[nodiscard]] gl_attr_force_inline vertex_distance_type get_weight( - const typename GraphType::edge_type& edge +template +[[nodiscard]] gl_attr_force_inline vertex_distance_t get_weight( + const traits::c_graph_edge auto& edge ) { - if constexpr (traits::c_weight_properties_type) + if constexpr (traits::c_weight_properties_type>) return edge.properties().weight; else return static_cast(1ll); diff --git a/tests/source/gl/test_alg_bfs.cpp b/tests/source/gl/test_alg_bfs.cpp index 15e6e314..e1913919 100644 --- a/tests/source/gl/test_alg_bfs.cpp +++ b/tests/source/gl/test_alg_bfs.cpp @@ -176,8 +176,7 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; const auto graph = gl::topology::regular_binary_tree(constants::depth); - const auto pred_map = - gl::algorithm::breadth_first_search(graph); + const auto pred_map = gl::algorithm::breadth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pred_map.size(), graph.n_vertices()); diff --git a/tests/source/gl/test_alg_dfs.cpp b/tests/source/gl/test_alg_dfs.cpp index e91d9069..1131912c 100644 --- a/tests/source/gl/test_alg_dfs.cpp +++ b/tests/source/gl/test_alg_dfs.cpp @@ -183,7 +183,7 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; const auto graph = gl::topology::regular_binary_tree(constants::depth); - const auto pred_map = gl::algorithm::depth_first_search(graph); + const auto pred_map = gl::algorithm::depth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pred_map.size(), graph.n_vertices()); @@ -386,8 +386,7 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; const auto graph = gl::topology::regular_binary_tree(constants::depth); - const auto pred_map = - gl::algorithm::recursive_depth_first_search(graph); + const auto pred_map = gl::algorithm::recursive_depth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pred_map.size(), graph.n_vertices()); diff --git a/tests/source/gl/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp index 510dd5fd..9e482b74 100644 --- a/tests/source/gl/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -27,7 +27,7 @@ TEST_CASE_TEMPLATE_DEFINE( static_assert(gl::traits::c_weight_properties_type); SUBCASE("should throw if there is an edge with a negative weight") { - const auto sut = gl::topology::clique(constants::n_elements_alg); + auto sut = gl::topology::clique(constants::n_elements_alg); sut.edge(constants::v1_id, constants::v2_id)->properties().weight = -static_cast(constants::n_elements_alg); From 8105036ec5685cca5b54ec6c2c2b08f069f835ad Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 27 Jul 2026 16:37:16 +0200 Subject: [PATCH 12/13] bench alignment --- CMakeLists.txt | 2 +- benchmarks/suites/hg_b_bfs.cpp | 8 +-- .../gl/algorithm/spanning_tree/prim_mst.hpp | 2 +- include/gl/graph.hpp | 58 +++++++++++++------ 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c358886..54b4e5da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,7 @@ if (CPP_GL_IS_TOP_LEVEL_PROJECT AND BUILD_TESTS) add_subdirectory(tests) endif() -# Include test directory if cpp-gl is a top level project +# Include benchmarks directory if cpp-gl is a top level project if (CPP_GL_IS_TOP_LEVEL_PROJECT AND BUILD_BENCHMARKS) add_subdirectory(benchmarks) endif() diff --git a/benchmarks/suites/hg_b_bfs.cpp b/benchmarks/suites/hg_b_bfs.cpp index fb4c969e..1d5a1909 100644 --- a/benchmarks/suites/hg_b_bfs.cpp +++ b/benchmarks/suites/hg_b_bfs.cpp @@ -87,11 +87,11 @@ void bm_hgl_backward_bfs(benchmark::State& state) { /// @brief Executes a Backward BFS equivalent on an Incidence Graph. template bool incidence_backward_bfs( - const IncidenceGraph& ig, - const std::vector& roots, - const typename IncidenceGraph::id_type original_n_vertices + IncidenceGraph&& ig, + const std::vector>& roots, + const gl::size_type original_n_vertices ) { - using id_type = typename IncidenceGraph::id_type; + using id_type = gl::id_t; std::vector visited_v(original_n_vertices, false); auto tail_unvisited = diff --git a/include/gl/algorithm/spanning_tree/prim_mst.hpp b/include/gl/algorithm/spanning_tree/prim_mst.hpp index 44c9276f..4dcd0d25 100644 --- a/include/gl/algorithm/spanning_tree/prim_mst.hpp +++ b/include/gl/algorithm/spanning_tree/prim_mst.hpp @@ -22,7 +22,7 @@ namespace gl::algorithm { template struct mst_descriptor { /// @brief The type of the graph. - using graph_type = traits::graph_val_t; + using graph_type = graph_val_t; /// @brief The type of the edges stored in the graph. using edge_type = edge_t; /// @brief The numeric type used to represent accumulated tree weights. diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 7a5a8cd6..5c9edb89 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -40,9 +40,15 @@ concept c_graph = c_instantiation_of, graph>; template concept c_mut_graph = c_graph and not std::is_const_v>; -template +} // namespace traits + +/// @ingroup GL-Core +/// @brief Extracts the underlying unqualified graph type by removing reference and cv-qualifiers. +template using graph_val_t = std::remove_cvref_t; +namespace traits { + /// @ingroup GL-Traits /// @brief Concept checking if a graph is directed. /// @see gl::directed_t "directed_t" : For the directional tag used to specify directed graph configuration. @@ -127,32 +133,42 @@ concept c_graph_edge = } // namespace traits +/// @ingroup GL-Core +/// @brief Resolves the identifier type associated with the given graph type. template -using id_t = typename traits::graph_val_t::id_type; +using id_t = typename graph_val_t::id_type; +/// @ingroup GL-Core +/// @brief Resolves the appropriate vertex descriptor type (mutable or const) based on the graph's constness. template using vertex_t = std::conditional_t< std::is_const_v>, - typename traits::graph_val_t::const_vertex_type, - typename traits::graph_val_t::vertex_type>; + typename graph_val_t::const_vertex_type, + typename graph_val_t::vertex_type>; +/// @ingroup GL-Core +/// @brief Resolves the appropriate vertex properties type (mutable or const) based on the graph's constness. template using vertex_properties_t = std::conditional_t< std::is_const_v>, - const typename traits::graph_val_t::vertex_properties_type, - typename traits::graph_val_t::vertex_properties_type>; + const typename graph_val_t::vertex_properties_type, + typename graph_val_t::vertex_properties_type>; +/// @ingroup GL-Core +/// @brief Resolves the appropriate edge descriptor type (mutable or const) based on the graph's constness. template using edge_t = std::conditional_t< std::is_const_v>, - typename traits::graph_val_t::const_edge_type, - typename traits::graph_val_t::edge_type>; + typename graph_val_t::const_edge_type, + typename graph_val_t::edge_type>; +/// @ingroup GL-Core +/// @brief Resolves the appropriate edge properties type (mutable or const) based on the graph's constness. template using edge_properties_t = std::conditional_t< std::is_const_v>, - const typename traits::graph_val_t::edge_properties_type, - typename traits::graph_val_t::edge_properties_type>; + const typename graph_val_t::edge_properties_type, + typename graph_val_t::edge_properties_type>; template [[nodiscard]] Graph clone(const Graph& source); @@ -434,15 +450,19 @@ class graph final { /// @throws std::invalid_argument If any vertex ID in the range is invalid. /// @copydetails detail::graph_doc_anchors::remove_vertex_wrn() void remove_vertices(const traits::c_forward_range_of auto& vertex_id_rng) { - // TODO: optimize - // sorts the ids in a descending order and removes duplicate ids - std::set> vertex_id_set( - std::ranges::begin(vertex_id_rng), std::ranges::end(vertex_id_rng) - ); - if (not vertex_id_set.empty()) - this->_verify_vertex_id(*vertex_id_set.begin()); - - for (auto vertex_id : vertex_id_set) + auto vertex_ids = vertex_id_rng | std::ranges::to(); + // Sort in descending order to prevent index shifting bugs during erasure + std::ranges::sort(vertex_ids, std::greater<>{}); + // Remove duplicate IDs + vertex_ids.erase(std::ranges::unique(vertex_ids).begin(), vertex_ids.end()); + + // Because the IDs are sorted descending, verifying the first (largest) ID + // implicitly guarantees all smaller IDs are also within bounds. + if (not vertex_ids.empty()) + this->_verify_vertex_id(vertex_ids.front()); + + // Remove vertices from largest ID to smallest ID + for (auto vertex_id : vertex_ids) this->_remove_vertex_impl(vertex_id); } From 610f4436da75cf3880cf4e32e099c5605655baa8 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 27 Jul 2026 21:43:36 +0200 Subject: [PATCH 13/13] resolved comments --- include/gl/edge_descriptor.hpp | 28 +++--- include/gl/graph.hpp | 95 +++++++++---------- include/gl/impl/adjacency_list.hpp | 8 +- include/gl/impl/adjacency_matrix.hpp | 8 +- .../{specialized => base}/adjacency_list.hpp | 4 - .../adjacency_matrix.hpp | 3 - .../flat_adjacency_list.hpp | 6 +- .../flat_adjacency_matrix.hpp | 6 +- include/gl/types/properties.hpp | 4 +- include/gl/vertex_descriptor.hpp | 2 + 10 files changed, 82 insertions(+), 82 deletions(-) rename include/gl/impl/{specialized => base}/adjacency_list.hpp (99%) rename include/gl/impl/{specialized => base}/adjacency_matrix.hpp (99%) rename include/gl/impl/{specialized => base}/flat_adjacency_list.hpp (98%) rename include/gl/impl/{specialized => base}/flat_adjacency_matrix.hpp (99%) diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index db6e84da..56fad0a4 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -147,23 +147,29 @@ class edge_descriptor final { /// @brief Destructor. ~edge_descriptor() = default; - /// @brief Equality comparison operator for directed edges. + /// @brief Cross-type equality comparison operator for directed edges. + /// @tparam OtherProperties The property type of the other descriptor. /// @param other The edge descriptor to compare against. /// @return `true` if IDs and exact endpoint pairs match, `false` otherwise. - [[nodiscard]] bool operator==(const edge_descriptor& other) const noexcept - requires(traits::c_directed_edge) - { - return this->_id == other._id and (this->_vertices == other._vertices); + template + requires(traits::c_directed_edge and std::same_as, std::remove_cv_t>) + [[nodiscard]] bool operator==( + const edge_descriptor& other + ) const noexcept { + return this->_id == other.id() and (this->_vertices == other.incident_vertices()); } - /// @brief Equality comparison operator for undirected edges. + /// @brief Cross-type equality comparison operator for undirected edges. + /// @tparam OtherProperties The property type of the other descriptor. /// @param other The edge descriptor to compare against. /// @return `true` if IDs and endpoint pairs match (order independent), `false` otherwise. - [[nodiscard]] bool operator==(const edge_descriptor& other) const noexcept - requires(traits::c_undirected_edge) - { - return this->_id == other._id - and (this->_vertices == other._vertices + template + requires(traits::c_undirected_edge and std::same_as, std::remove_cv_t>) + [[nodiscard]] bool operator==( + const edge_descriptor& other + ) const noexcept { + return this->_id == other.id() + and (this->_vertices == other.incident_vertices() or (this->_vertices == other.incident_vertices_r())); } diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 5c9edb89..bee8a7ca 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -36,7 +36,12 @@ template concept c_graph = c_instantiation_of, graph>; /// @ingroup GL-Traits -/// @brief Concept checking if a type is a mutable instantiation of the generic @ref "gl::graph" graph class. +/// @brief Concept checking if a type is a constant type instantiation of the generic @ref "gl::graph" graph class. +template +concept c_const_graph = c_graph and std::is_const_v>; + +/// @ingroup GL-Traits +/// @brief Concept checking if a type is a mutable type instantiation of the generic @ref "gl::graph" graph class. template concept c_mut_graph = c_graph and not std::is_const_v>; @@ -47,6 +52,43 @@ concept c_mut_graph = c_graph and not std::is_const_v using graph_val_t = std::remove_cvref_t; +/// @ingroup GL-Core +/// @brief Resolves the identifier type associated with the given graph type. +template +using id_t = typename graph_val_t::id_type; + +/// @ingroup GL-Core +/// @brief Resolves the appropriate vertex descriptor type (mutable or const) based on the graph's constness. +template +using vertex_t = std::conditional_t< + std::is_const_v>, + typename graph_val_t::const_vertex_type, + typename graph_val_t::vertex_type>; + +/// @ingroup GL-Core +/// @brief Resolves the appropriate vertex properties type (mutable or const) based on the graph's constness. +template +using vertex_properties_t = std::conditional_t< + std::is_const_v>, + const typename graph_val_t::vertex_properties_type, + typename graph_val_t::vertex_properties_type>; + +/// @ingroup GL-Core +/// @brief Resolves the appropriate edge descriptor type (mutable or const) based on the graph's constness. +template +using edge_t = std::conditional_t< + std::is_const_v>, + typename graph_val_t::const_edge_type, + typename graph_val_t::edge_type>; + +/// @ingroup GL-Core +/// @brief Resolves the appropriate edge properties type (mutable or const) based on the graph's constness. +template +using edge_properties_t = std::conditional_t< + std::is_const_v>, + const typename graph_val_t::edge_properties_type, + typename graph_val_t::edge_properties_type>; + namespace traits { /// @ingroup GL-Traits @@ -133,43 +175,6 @@ concept c_graph_edge = } // namespace traits -/// @ingroup GL-Core -/// @brief Resolves the identifier type associated with the given graph type. -template -using id_t = typename graph_val_t::id_type; - -/// @ingroup GL-Core -/// @brief Resolves the appropriate vertex descriptor type (mutable or const) based on the graph's constness. -template -using vertex_t = std::conditional_t< - std::is_const_v>, - typename graph_val_t::const_vertex_type, - typename graph_val_t::vertex_type>; - -/// @ingroup GL-Core -/// @brief Resolves the appropriate vertex properties type (mutable or const) based on the graph's constness. -template -using vertex_properties_t = std::conditional_t< - std::is_const_v>, - const typename graph_val_t::vertex_properties_type, - typename graph_val_t::vertex_properties_type>; - -/// @ingroup GL-Core -/// @brief Resolves the appropriate edge descriptor type (mutable or const) based on the graph's constness. -template -using edge_t = std::conditional_t< - std::is_const_v>, - typename graph_val_t::const_edge_type, - typename graph_val_t::edge_type>; - -/// @ingroup GL-Core -/// @brief Resolves the appropriate edge properties type (mutable or const) based on the graph's constness. -template -using edge_properties_t = std::conditional_t< - std::is_const_v>, - const typename graph_val_t::edge_properties_type, - typename graph_val_t::edge_properties_type>; - template [[nodiscard]] Graph clone(const Graph& source); @@ -451,17 +456,13 @@ class graph final { /// @copydetails detail::graph_doc_anchors::remove_vertex_wrn() void remove_vertices(const traits::c_forward_range_of auto& vertex_id_rng) { auto vertex_ids = vertex_id_rng | std::ranges::to(); - // Sort in descending order to prevent index shifting bugs during erasure + // Sort in descending order and remove duplicates to prevent index shifting bugs during erasure std::ranges::sort(vertex_ids, std::greater<>{}); - // Remove duplicate IDs vertex_ids.erase(std::ranges::unique(vertex_ids).begin(), vertex_ids.end()); - // Because the IDs are sorted descending, verifying the first (largest) ID - // implicitly guarantees all smaller IDs are also within bounds. if (not vertex_ids.empty()) this->_verify_vertex_id(vertex_ids.front()); - // Remove vertices from largest ID to smallest ID for (auto vertex_id : vertex_ids) this->_remove_vertex_impl(vertex_id); } @@ -498,7 +499,7 @@ class graph final { /// @brief Returns a vertex descriptor bounds-checked by ID. /// @param vertex_id The ID of the vertex. - /// @return The corresponding vertex descriptor (const or mutable). + /// @return The corresponding vertex descriptor. /// @throws std::invalid_argument If the ID is invalid. template [[nodiscard]] vertex_t vertex(this Self& self, const id_type vertex_id) { @@ -1103,7 +1104,7 @@ class graph final { /// @return A view representing the set of outgoing edges. /// @throws std::invalid_argument If the vertex ID is invalid. template - [[nodiscard]] inline auto out_edges(this Self& self, const id_type vertex_id) { + [[nodiscard]] auto out_edges(this Self& self, const id_type vertex_id) { self._verify_vertex_id(vertex_id); if constexpr (traits::c_non_empty_properties) return self._impl.template out_edges>(vertex_id, self._edge_properties); @@ -1126,9 +1127,7 @@ class graph final { /// @return A reference to the properties attached to the edge. /// @throws std::invalid_argument If the edge ID is invalid. template - [[nodiscard]] gl_attr_force_inline edge_properties_t& edge_properties( - this Self& self, const id_type id - ) + [[nodiscard]] edge_properties_t& edge_properties(this Self& self, const id_type id) requires(traits::c_non_empty_properties) { if (id >= self._n_edges) diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index 8e4955cf..28973f46 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -6,8 +6,8 @@ #include "gl/constants.hpp" #include "gl/graph_traits.hpp" -#include "gl/impl/specialized/adjacency_list.hpp" -#include "gl/impl/specialized/flat_adjacency_list.hpp" +#include "gl/impl/base/adjacency_list.hpp" +#include "gl/impl/base/flat_adjacency_list.hpp" #include "gl/types/core.hpp" #ifdef GL_TESTING @@ -28,10 +28,10 @@ struct to_impl; namespace impl { template -class adjacency_list final : public specialized::adjacency_list_base_t { +class adjacency_list final : public adjacency_list_base_t { public: using traits_type = GraphTraits; - using base_type = specialized::adjacency_list_base_t; + using base_type = adjacency_list_base_t; using id_type = typename traits_type::id_type; using item_type = typename base_type::item_type; using storage_type = typename base_type::storage_type; diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index bac8af96..a7637f88 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -6,8 +6,8 @@ #include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" -#include "gl/impl/specialized/adjacency_matrix.hpp" -#include "gl/impl/specialized/flat_adjacency_matrix.hpp" +#include "gl/impl/base/adjacency_matrix.hpp" +#include "gl/impl/base/flat_adjacency_matrix.hpp" #include "gl/types/core.hpp" #ifdef GL_TESTING @@ -28,10 +28,10 @@ struct to_impl; namespace impl { template -class adjacency_matrix final : public specialized::adjacency_matrix_base_t { +class adjacency_matrix final : public adjacency_matrix_base_t { public: using traits_type = GraphTraits; - using base_type = specialized::adjacency_matrix_base_t; + using base_type = adjacency_matrix_base_t; using representation_tag = typename traits_type::representation_tag; using id_type = typename traits_type::id_type; diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/base/adjacency_list.hpp similarity index 99% rename from include/gl/impl/specialized/adjacency_list.hpp rename to include/gl/impl/base/adjacency_list.hpp index 34b6366c..295ae941 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/base/adjacency_list.hpp @@ -23,8 +23,6 @@ namespace gl::impl { template class adjacency_list; -namespace specialized { - template struct incidence_item { using id_type = IdType; @@ -327,6 +325,4 @@ struct adjacency_list_base { undirected_adjacency_list>; }; -} // namespace specialized - } // namespace gl::impl diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/base/adjacency_matrix.hpp similarity index 99% rename from include/gl/impl/specialized/adjacency_matrix.hpp rename to include/gl/impl/base/adjacency_matrix.hpp index 74313c9f..206dcb2b 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/base/adjacency_matrix.hpp @@ -25,8 +25,6 @@ namespace gl::impl { template class adjacency_matrix; -namespace specialized { - namespace detail { [[nodiscard]] auto& strict_get(auto& id_matrix, const auto& edge) { @@ -430,5 +428,4 @@ struct adjacency_matrix_base { undirected_adjacency_matrix>; }; -} // namespace specialized } // namespace gl::impl diff --git a/include/gl/impl/specialized/flat_adjacency_list.hpp b/include/gl/impl/base/flat_adjacency_list.hpp similarity index 98% rename from include/gl/impl/specialized/flat_adjacency_list.hpp rename to include/gl/impl/base/flat_adjacency_list.hpp index c00b7bda..8e0232bc 100644 --- a/include/gl/impl/specialized/flat_adjacency_list.hpp +++ b/include/gl/impl/base/flat_adjacency_list.hpp @@ -7,7 +7,7 @@ #include "gl/constants.hpp" #include "gl/decl/repr_tags.hpp" #include "gl/graph_traits.hpp" -#include "gl/impl/specialized/adjacency_list.hpp" +#include "gl/impl/base/adjacency_list.hpp" #include "gl/types/flat_jagged_vector.hpp" #include @@ -15,7 +15,7 @@ #include #include -namespace gl::impl::specialized { +namespace gl::impl { template class directed_flat_adjacency_list { @@ -300,4 +300,4 @@ struct adjacency_list_base { undirected_flat_adjacency_list>; }; -} // namespace gl::impl::specialized +} // namespace gl::impl diff --git a/include/gl/impl/specialized/flat_adjacency_matrix.hpp b/include/gl/impl/base/flat_adjacency_matrix.hpp similarity index 99% rename from include/gl/impl/specialized/flat_adjacency_matrix.hpp rename to include/gl/impl/base/flat_adjacency_matrix.hpp index 14a67c42..afb04a4a 100644 --- a/include/gl/impl/specialized/flat_adjacency_matrix.hpp +++ b/include/gl/impl/base/flat_adjacency_matrix.hpp @@ -9,7 +9,7 @@ #include "gl/constants.hpp" #include "gl/decl/repr_tags.hpp" #include "gl/graph_traits.hpp" -#include "gl/impl/specialized/adjacency_matrix.hpp" +#include "gl/impl/base/adjacency_matrix.hpp" #include "gl/types/core.hpp" #include "gl/types/flat_matrix.hpp" @@ -19,7 +19,7 @@ #include #include -namespace gl::impl::specialized { +namespace gl::impl { namespace detail { @@ -402,4 +402,4 @@ struct adjacency_matrix_base { undirected_flat_adjacency_matrix>; }; -} // namespace gl::impl::specialized +} // namespace gl::impl diff --git a/include/gl/types/properties.hpp b/include/gl/types/properties.hpp index 203727f2..1652abce 100644 --- a/include/gl/types/properties.hpp +++ b/include/gl/types/properties.hpp @@ -257,7 +257,7 @@ namespace traits { /// /// @tparam T The type to evaluate against the concept. template -concept c_properties = std::semiregular>; +concept c_properties = std::semiregular>; /// @ingroup GL-Traits /// @brief Validates if a type is specifically the @ref gl::empty_properties tag. @@ -268,7 +268,7 @@ concept c_properties = std::semiregular>; /// @tparam T The type to evaluate against the concept. template concept c_empty_properties = - c_properties and std::same_as, gl::empty_properties>; + c_properties and std::same_as, gl::empty_properties>; /// @ingroup GL-Traits /// @brief Validates if a property type contains actual user-defined data. diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 52f8e7a8..eac6dad1 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -138,6 +138,7 @@ class vertex_descriptor final { /// @param other The vertex descriptor to compare against. /// @return `true` if both descriptors hold the same ID, `false` otherwise. template + requires(std::same_as, std::remove_cv_t>) [[nodiscard]] gl_attr_force_inline bool operator==( const vertex_descriptor& other ) const noexcept { @@ -149,6 +150,7 @@ class vertex_descriptor final { /// @param other The vertex descriptor to compare against. /// @return The strong ordering result based on the underlying IDs. template + requires(std::same_as, std::remove_cv_t>) [[nodiscard]] gl_attr_force_inline std::strong_ordering operator<=>( const vertex_descriptor& other ) const noexcept {