diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp index faa2e976..a57608fa 100644 --- a/include/Simple-Utility/graph/View.hpp +++ b/include/Simple-Utility/graph/View.hpp @@ -13,17 +13,103 @@ #include "Simple-Utility/graph/Node.hpp" #include -#include +// ReSharper disable once CppUnusedIncludeDirective +#include // std::ranges::input_range, etc. namespace sl::graph::view { - template + template struct traits; + + template + using edge_t = typename traits::edge_type; + + template + requires requires { typename T::edge_type; } + && concepts::edge + struct traits + { + using edge_type = typename T::edge_type; + }; +} + +namespace sl::graph::customize +{ + template + struct edges_fn; +} + +namespace sl::graph::detail +{ + template + requires requires { customize::edges_fn{}; } + && std::ranges::input_range, const View&, const Node&>> + && std::convertible_to< + std::ranges::range_reference_t, const View&, const Node&>>, + view::edge_t> + constexpr decltype(auto) edges( + const View& view, + const Node& node, + const priority_tag<2> + ) noexcept(noexcept(customize::edges_fn{}(view, node))) + { + return customize::edges_fn{}(view, node); + } + + template + requires requires(const View& view, const Node& node) + { + { view.edges(node) } -> std::ranges::input_range; + requires std::convertible_to< + std::ranges::range_reference_t, + view::edge_t>; + } + constexpr decltype(auto) edges(const View& view, const Node& node, const priority_tag<1>) noexcept(noexcept(view.edges(node))) + { + return view.edges(node); + } + + template + requires requires(const View& view, const Node& node) + { + { edges(view, node) } -> std::ranges::input_range; + requires std::convertible_to< + std::ranges::range_reference_t, + view::edge_t>; + } + constexpr decltype(auto) edges(const View& view, const Node& node, const priority_tag<0>) noexcept(noexcept(edges(view, node))) + { + return edges(view, node); + } + + struct edges_fn + { + template + requires requires(const View& view, const Node& node, const priority_tag<2> tag) + { + { detail::edges(view, node, tag) } -> std::ranges::input_range; + requires std::convertible_to< + std::ranges::range_reference_t, + view::edge_t>; + } + constexpr decltype(auto) operator ()( + const View& view, + const Node& node + ) const noexcept(noexcept(detail::edges(view, node, priority_tag<2>{}))) + { + return detail::edges(view, node, priority_tag<2>{}); + } + }; +} + +namespace sl::graph::view +{ + inline constexpr detail::edges_fn edges{}; } namespace sl::graph::concepts { - template + template concept view_for = basic_node && sl::concepts::unqualified && requires(const T& view, const Node& node) @@ -31,28 +117,12 @@ namespace sl::graph::concepts // fixes compile error on msvc v142 // ReSharper disable once CppRedundantTemplateKeyword typename view::template traits::edge_type; - // ReSharper disable once CppRedundantTemplateKeyword - requires edge_for::edge_type, Node>; - { view.edges(node) } -> std::ranges::input_range; + requires edge_for, Node>; + { view::edges(view, node) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_value_t, - // ReSharper disable once CppRedundantTemplateKeyword - typename view::template traits::edge_type>; + std::ranges::range_value_t>, + view::edge_t>; }; } -namespace sl::graph::view -{ - template - using edge_t = typename traits::edge_type; - - template - requires requires { typename T::edge_type; } - && concepts::edge - struct traits - { - using edge_type = typename T::edge_type; - }; -} - #endif diff --git a/tests/graph/View.cpp b/tests/graph/View.cpp index 69a59452..06876834 100644 --- a/tests/graph/View.cpp +++ b/tests/graph/View.cpp @@ -11,6 +11,32 @@ namespace { + struct member_fun_get_edges + { + using edge_type = GenericBasicEdge; + + MAKE_CONST_MOCK1(edges, std::vector(const GenericBasicNode&)); + }; + + struct free_fun_get_edges + { + using edge_type = GenericBasicEdge; + + MAKE_CONST_MOCK1(get_edges, std::vector(const GenericBasicNode&)); + + friend std::vector edges(const free_fun_get_edges& obj, const GenericBasicNode& node) + { + return obj.get_edges(node); + } + }; + + struct customized_get_edges + { + using edge_type = GenericBasicEdge; + + MAKE_CONST_MOCK1(get_edges, std::vector(const GenericBasicNode&)); + }; + template struct GenericBasicView { @@ -19,9 +45,11 @@ namespace template requires sg::concepts::edge_for // ReSharper disable once CppFunctionIsNotImplemented - static std::vector edges(const Node&); + static std::vector edges(const Node&); // NOLINT(clang-diagnostic-undefined-internal) }; + static_assert(sg::concepts::view_for, GenericBasicNode>); + template struct GenericWeightedView { @@ -30,10 +58,56 @@ namespace template requires sg::concepts::edge_for // ReSharper disable once CppFunctionIsNotImplemented - static std::vector edges(const Node&); + static std::vector edges(const Node&); // NOLINT(clang-diagnostic-undefined-internal) }; } +template <> +struct sl::graph::customize::edges_fn +{ + [[nodiscard]] + auto operator ()(const customized_get_edges& e, const GenericBasicNode& node) const + { + return e.get_edges(node); + } +}; + +TEST_CASE( + "graph::view::edges serves as a customization point, returning the outgoing edges of the given node.", + "[graph][graph::view]") +{ + const GenericBasicNode node{"Hello, World!"}; + const std::vector> expected{ + {"Edge0"}, + {"Edge1"}, + {"Edge2"} + }; + + SECTION("Access via the member function.") + { + const member_fun_get_edges mock{}; + REQUIRE_CALL(mock, edges(node)) + .RETURN(expected); + REQUIRE(expected == sg::view::edges(mock, node)); + } + + SECTION("Access via the free function.") + { + const free_fun_get_edges mock{}; + REQUIRE_CALL(mock, get_edges(node)) + .RETURN(expected); + REQUIRE(expected == sg::view::edges(mock, node)); + } + + SECTION("Access via customized function.") + { + const customized_get_edges mock{}; + REQUIRE_CALL(mock, get_edges(node)) + .RETURN(expected); + REQUIRE(expected == sg::view::edges(mock, node)); + } +} + TEMPLATE_TEST_CASE_SIG( "view::traits extracts edge type.", "[graph][graph::view]",