Skip to content

Commit

Permalink
feat: view::edges customization point
Browse files Browse the repository at this point in the history
  • Loading branch information
DNKpp committed Oct 5, 2023
1 parent c6d4edd commit 1669886
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 25 deletions.
116 changes: 93 additions & 23 deletions include/Simple-Utility/graph/View.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,46 +13,116 @@
#include "Simple-Utility/graph/Node.hpp"

#include <concepts>
#include <ranges>
// ReSharper disable once CppUnusedIncludeDirective
#include <ranges> // std::ranges::input_range, etc.

namespace sl::graph::view
{
template <class>
template <typename>
struct traits;

template <typename T>
using edge_t = typename traits<T>::edge_type;

template <typename T>
requires requires { typename T::edge_type; }
&& concepts::edge<typename T::edge_type>
struct traits<T>
{
using edge_type = typename T::edge_type;
};
}

namespace sl::graph::customize
{
template <typename>
struct edges_fn;
}

namespace sl::graph::detail
{
template <typename View, typename Node>
requires requires { customize::edges_fn<View>{}; }
&& std::ranges::input_range<std::invoke_result_t<customize::edges_fn<View>, const View&, const Node&>>
&& std::convertible_to<
std::ranges::range_reference_t<std::invoke_result_t<customize::edges_fn<View>, const View&, const Node&>>,
view::edge_t<View>>
constexpr decltype(auto) edges(
const View& view,
const Node& node,
const priority_tag<2>
) noexcept(noexcept(customize::edges_fn<View>{}(view, node)))
{
return customize::edges_fn<View>{}(view, node);
}

template <typename View, typename Node>
requires requires(const View& view, const Node& node)
{
{ view.edges(node) } -> std::ranges::input_range;
requires std::convertible_to<
std::ranges::range_reference_t<decltype(view.edges(node))>,
view::edge_t<View>>;
}
constexpr decltype(auto) edges(const View& view, const Node& node, const priority_tag<1>) noexcept(noexcept(view.edges(node)))
{
return view.edges(node);
}

template <typename View, typename Node>
requires requires(const View& view, const Node& node)
{
{ edges(view, node) } -> std::ranges::input_range;
requires std::convertible_to<
std::ranges::range_reference_t<decltype(edges(view, node))>,
view::edge_t<View>>;
}
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 <typename View, typename Node>
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<decltype(detail::edges(view, node, tag))>,
view::edge_t<View>>;
}
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 <class T, class Node>
template <typename T, typename Node>
concept view_for = basic_node<Node>
&& sl::concepts::unqualified<T>
&& requires(const T& view, const Node& node)
{
// fixes compile error on msvc v142
// ReSharper disable once CppRedundantTemplateKeyword
typename view::template traits<T>::edge_type;
// ReSharper disable once CppRedundantTemplateKeyword
requires edge_for<typename view::template traits<T>::edge_type, Node>;
{ view.edges(node) } -> std::ranges::input_range;
requires edge_for<view::edge_t<T>, Node>;
{ view::edges(view, node) } -> std::ranges::input_range;
requires std::convertible_to<
std::ranges::range_value_t<decltype(view.edges(node))>,
// ReSharper disable once CppRedundantTemplateKeyword
typename view::template traits<T>::edge_type>;
std::ranges::range_value_t<std::invoke_result_t<detail::edges_fn, const T&, const Node&>>,
view::edge_t<T>>;
};
}

namespace sl::graph::view
{
template <class T>
using edge_t = typename traits<T>::edge_type;

template <class T>
requires requires { typename T::edge_type; }
&& concepts::edge<typename T::edge_type>
struct traits<T>
{
using edge_type = typename T::edge_type;
};
}

#endif
78 changes: 76 additions & 2 deletions tests/graph/View.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,32 @@

namespace
{
struct member_fun_get_edges
{
using edge_type = GenericBasicEdge<std::string>;

MAKE_CONST_MOCK1(edges, std::vector<edge_type>(const GenericBasicNode<std::string>&));
};

struct free_fun_get_edges
{
using edge_type = GenericBasicEdge<std::string>;

MAKE_CONST_MOCK1(get_edges, std::vector<edge_type>(const GenericBasicNode<std::string>&));

friend std::vector<edge_type> edges(const free_fun_get_edges& obj, const GenericBasicNode<std::string>& node)
{
return obj.get_edges(node);
}
};

struct customized_get_edges
{
using edge_type = GenericBasicEdge<std::string>;

MAKE_CONST_MOCK1(get_edges, std::vector<edge_type>(const GenericBasicNode<std::string>&));
};

template <sg::concepts::vertex Vertex>
struct GenericBasicView
{
Expand All @@ -19,9 +45,11 @@ namespace
template <sg::concepts::basic_node Node>
requires sg::concepts::edge_for<edge_type, Node>
// ReSharper disable once CppFunctionIsNotImplemented
static std::vector<edge_type> edges(const Node&);
static std::vector<edge_type> edges(const Node&); // NOLINT(clang-diagnostic-undefined-internal)
};

static_assert(sg::concepts::view_for<GenericBasicView<std::string>, GenericBasicNode<std::string>>);

template <sg::concepts::vertex Vertex, sg::concepts::weight Weight>
struct GenericWeightedView
{
Expand All @@ -30,10 +58,56 @@ namespace
template <sg::concepts::basic_node Node>
requires sg::concepts::edge_for<edge_type, Node>
// ReSharper disable once CppFunctionIsNotImplemented
static std::vector<edge_type> edges(const Node&);
static std::vector<edge_type> edges(const Node&); // NOLINT(clang-diagnostic-undefined-internal)
};
}

template <>
struct sl::graph::customize::edges_fn<customized_get_edges>
{
[[nodiscard]]
auto operator ()(const customized_get_edges& e, const GenericBasicNode<std::string>& 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<std::string> node{"Hello, World!"};
const std::vector<GenericBasicEdge<std::string>> 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]",
Expand Down

0 comments on commit 1669886

Please sign in to comment.