From 7707876d5df0c346910797abbb0a6b9730c4fd5b Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Mon, 27 May 2024 10:20:45 -0400 Subject: [PATCH 1/5] Implement target_id(e) and source_id(e) for edgelist Support use of tuple Support use of edge_descriptor --- include/graph/detail/graph_cpo.hpp | 109 +-------- include/graph/edgelist.hpp | 249 ++++++++++++++++++- tests/CMakeLists.txt | 3 +- tests/edgelist_tests.cpp | 379 +++++------------------------ tests/edgelist_view_tests.cpp | 321 ++++++++++++++++++++++++ 5 files changed, 631 insertions(+), 430 deletions(-) create mode 100644 tests/edgelist_view_tests.cpp diff --git a/include/graph/detail/graph_cpo.hpp b/include/graph/detail/graph_cpo.hpp index 12d5383..8d81208 100644 --- a/include/graph/detail/graph_cpo.hpp +++ b/include/graph/detail/graph_cpo.hpp @@ -1,6 +1,7 @@ #pragma once // (included from graph.hpp) +#include "graph/graph_descriptors.hpp" #include "tag_invoke.hpp" #ifndef GRAPH_CPO_HPP @@ -964,15 +965,6 @@ namespace _Target_id { template concept _Is_tuple_id_adj = integral>>; // vertex>> - template - concept _Has_edgl_ref_member = requires(_E&& e) { - { _Fake_copy_init(e.target_id()) }; - }; - template - concept _Has_edgl_ref_ADL = requires(_E&& __e) { - { _Fake_copy_init(target_id(__e)) }; // intentional ADL - }; - class _Cpo { private: enum class _St_ref { _None, _Member, _Non_member, _Basic_id, _Tuple_id, _Auto_eval }; @@ -1002,22 +994,6 @@ namespace _Target_id { template static constexpr _Choice_t<_St_ref> _Choice_adjl_ref = _Choose_adjl_ref<_G>(); - template - [[nodiscard]] static consteval _Choice_t<_St_ref> _Choose_edgl_ref() noexcept { - static_assert(is_lvalue_reference_v<_E>); - - if constexpr (_Has_edgl_ref_member<_E>) { - return {_St_ref::_Member, noexcept(_Fake_copy_init(declval<_E&>().target_id()))}; - } else if constexpr (_Has_edgl_ref_ADL<_E>) { - return {_St_ref::_Non_member, noexcept(_Fake_copy_init(target_id(declval<_E>())))}; // intentional ADL - } else { - return {_St_ref::_None}; - } - } - - template - static constexpr _Choice_t<_St_ref> _Choice_edgl_ref = _Choose_edgl_ref<_E>(); - public: /** * @brief The target_id of an adjancy list edge @@ -1051,32 +1027,6 @@ namespace _Target_id { static_assert(_Always_false<_G>, "target_id(g,uv) or g.target_id(uv) is not defined"); } } - - /** - * @brief The target_id of an edgelist edge. - * - * Complexity: O(1) - * - * Default implementation: - * - * @tparam G The graph type. - * @param g A graph instance. - * @param uv An edge instance. - * @return The target_id on an edge for an ajacency_list - */ - template - //requires(_Choice_edgl_ref<_G&>._Strategy != _St_ref::_None) - [[nodiscard]] constexpr auto operator()(_E&& __e) const noexcept(_Choice_edgl_ref<_E&>._No_throw) { - constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E&>._Strategy; - - if constexpr (_Strat_ref == _St_ref::_Member) { - return __e.target_id(); - } else if constexpr (_Strat_ref == _St_ref::_Non_member) { - return target_id(__e); // intentional ADL - } else { - static_assert(_Always_false<_E>, "target_id(e) or e.target_id() is not defined"); - } - } }; } // namespace _Target_id @@ -1088,7 +1038,7 @@ inline namespace _Cpos { // // source_id(g,uv) -> vertex_id_t (optional; only when a source_id exists on an edge) // -namespace _EL_Source_id { +namespace _Source_id { # if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 void source_id() = delete; // Block unqualified name lookup # else // ^^^ no workaround / workaround vvv @@ -1105,16 +1055,6 @@ namespace _EL_Source_id { { _Fake_copy_init(source_id(__g, uv)) }; // intentional ADL }; - template - concept _Has_edgl_ref_member = requires(_E&& __e) { - { _Fake_copy_init(__e.source_id()) }; - }; - template - concept _Has_edgl_ref_ADL = _Has_class_or_enum_type<_E> // - && requires(_E&& __e) { - { _Fake_copy_init(source_id(__e)) }; // intentional ADL - }; - class _Cpo { private: enum class _St_ref { _None, _Member, _Non_member, _Auto_eval }; @@ -1136,21 +1076,6 @@ namespace _EL_Source_id { template static constexpr _Choice_t<_St_ref> _Choice_adjl_ref = _Choose_adjl_ref<_E>(); - template - [[nodiscard]] static consteval _Choice_t<_St_ref> _Choose_edgl_ref() noexcept { - static_assert(is_lvalue_reference_v<_E>); - if constexpr (_Has_edgl_ref_member<_E>) { - return {_St_ref::_Member, noexcept(_Fake_copy_init(declval<_E>().source_id()))}; - } else if constexpr (_Has_edgl_ref_ADL<_E>) { - return {_St_ref::_Non_member, noexcept(_Fake_copy_init(source_id(declval<_E>())))}; // intentional ADL - } else { - return {_St_ref::_None}; - } - } - - template - static constexpr _Choice_t<_St_ref> _Choice_edgl_ref = _Choose_edgl_ref<_E>(); - public: /** * @brief The source_id of an adjacency list edge @@ -1180,37 +1105,11 @@ namespace _EL_Source_id { static_assert(_Always_false<_G>, "source_id(g,uv) or g.source_id(uv) is not defined"); } } - - /** - * @brief The source_id of an edgelist edge - * - * Complexity: O(1) - * - * Default implementation: - * - * @tparam E The edgelist value_type. - * @param e A edgelist edge instance. - * @return The source_id of the edge. - */ - template - requires(_Choice_edgl_ref<_E&>._Strategy != _St_ref::_None) - [[nodiscard]] constexpr auto operator()(_E&& __e, edge_reference_t<_E> uv) const - noexcept(_Choice_edgl_ref<_E&>._No_throw) { - constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E&>._Strategy; - - if constexpr (_Strat_ref == _St_ref::_Member) { - return __e.source_id(); - } else if constexpr (_Strat_ref == _St_ref::_Non_member) { - return source_id(__e); // intentional ADL - } else { - static_assert(_Always_false<_E>, "source_id(e) or e.source_id() is not defined"); - } - } }; -} // namespace _EL_Source_id +} // namespace _Source_id inline namespace _Cpos { - inline constexpr _EL_Source_id::_Cpo source_id; + inline constexpr _Source_id::_Cpo source_id; } diff --git a/include/graph/edgelist.hpp b/include/graph/edgelist.hpp index f439257..a3e8768 100644 --- a/include/graph/edgelist.hpp +++ b/include/graph/edgelist.hpp @@ -1,6 +1,9 @@ #pragma once #include "detail/graph_cpo.hpp" +#include "graph.hpp" +#include +#include #ifndef EDGELIST_HPP # define EDGELIST_HPP @@ -31,6 +34,239 @@ namespace std::graph::edgelist { // move implementations for source_id(e), target_id(e), edge_value(e) into std::graph::edgelist? // other functions to support: num_edges(el), contains_edge(uid,vid), edge_id(e) +// Support the use of std containers for adj list definitions +template +concept _el_value = !ranges::forward_range<_E>; // avoid conflict with adjacency list + +template +concept _el_tuple_edge = _el_value<_E> && // + same_as, tuple_element_t<1, _E>>; + +template +concept _el_index_tuple_edge = _el_tuple_edge<_E> && // + integral>; + +template +concept _el_basic_sourced_edge_desc = + same_as && requires(_E elv) { + { elv.source_id }; + { elv.target_id }; + }; + +template +concept _el_basic_sourced_index_edge_desc = _el_basic_sourced_edge_desc<_E> && requires(_E elv) { + { elv.source_id } -> integral; + { elv.target_id } -> same_as; +}; + +template +concept _el_sourced_edge_desc = _el_basic_sourced_edge_desc<_E> && requires(_E elv) { + { elv.value }; +}; + +template +concept _el_sourced_index_edge_desc = _el_basic_sourced_index_edge_desc<_E> && requires(_E elv) { + { elv.value }; +}; + + +// +// target_id(g,uv) -> vertex_id_t +// +// For E=tuple, returns get<0>(e) +// For E=edge_descriptor, returns e.target_id +// Caller may override +// +namespace _Target_id { +# if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 + void target_id() = delete; // Block unqualified name lookup +# else // ^^^ no workaround / workaround vvv + void target_id(); +# endif // ^^^ workaround ^^^ + + template + concept _Has_edgl_ref_member = requires(_E&& e) { + { _Fake_copy_init(e.target_id()) }; + }; + template + concept _Has_edgl_ref_ADL = requires(_E&& __e) { + { _Fake_copy_init(target_id(__e)) }; // intentional ADL + }; + + template + concept _is_tuple_edge = _el_tuple_edge<_E>; + + template + concept _is_edge_desc = _el_basic_sourced_edge_desc<_E>; + + //template + //concept _has_value = _el_sourced_edge_desc<_E>; + + class _Cpo { + private: + enum class _St_ref { _None, _Member, _Non_member, _Tuple_id, _EDesc_id }; + + template + [[nodiscard]] static consteval _Choice_t<_St_ref> _Choose_edgl_ref() noexcept { + //static_assert(is_lvalue_reference_v<_E>); + + if constexpr (_Has_edgl_ref_member<_E>) { + return {_St_ref::_Member, noexcept(_Fake_copy_init(declval<_E&>().target_id()))}; + } else if constexpr (_Has_edgl_ref_ADL<_E>) { + return {_St_ref::_Non_member, noexcept(_Fake_copy_init(target_id(declval<_E>())))}; // intentional ADL + } else if constexpr (_is_tuple_edge<_E>) { + return {_St_ref::_Tuple_id, + noexcept(_Fake_copy_init(declval>()))}; // first element of tuple/pair + } else if constexpr (_is_edge_desc<_E>) { + return {_St_ref::_EDesc_id, + noexcept(_Fake_copy_init(declval()))}; // target_id of edge_descriptor + } else { + return {_St_ref::_None}; + } + } + + template + static constexpr _Choice_t<_St_ref> _Choice_edgl_ref = _Choose_edgl_ref>(); + + public: + /** + * @brief The target_id of an edgelist edge. + * + * Complexity: O(1) + * + * Default implementation: + * + * @tparam G The graph type. + * @param g A graph instance. + * @param uv An edge instance. + * @return The target_id on an edge for an ajacency_list + */ + template + requires(_Choice_edgl_ref<_E>._Strategy != _St_ref::_None) + [[nodiscard]] constexpr auto operator()(_E&& __e) const noexcept(_Choice_edgl_ref<_E>._No_throw) { + constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E>._Strategy; + //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); + //static_assert(same_as, int>); + //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); + + if constexpr (_Strat_ref == _St_ref::_Member) { + return __e.target_id(); + } else if constexpr (_Strat_ref == _St_ref::_Non_member) { + return target_id(__e); // intentional ADL + } else if constexpr (_Strat_ref == _St_ref::_Tuple_id) { + //static_assert(same_as, int>); + return get<0>(__e); // first element of tuple/pair + } else if constexpr (_Strat_ref == _St_ref::_EDesc_id) { + return __e.target_id; + } else { + static_assert(_Always_false<_E>, "target_id(e) or e.target_id() is not defined"); + } + } + }; +} // namespace _Target_id + +inline namespace _Cpos { + inline constexpr _Target_id::_Cpo target_id; +} + +// +// source_id(e) -> vertex_id_t +// +// For E=tuple, returns get<1>(e) +// For E=edge_descriptor, returns e.source_id +// Caller may override +// +namespace _Source_id { +# if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 + void source_id() = delete; // Block unqualified name lookup +# else // ^^^ no workaround / workaround vvv + void source_id(); +# endif // ^^^ workaround ^^^ + + template + concept _Has_edgl_ref_member = requires(_E&& __e) { + { _Fake_copy_init(__e.source_id()) }; + }; + template + concept _Has_edgl_ref_ADL = _Has_class_or_enum_type<_E> // + && requires(_E&& __e) { + { _Fake_copy_init(source_id(__e)) }; // intentional ADL + }; + + template + concept _is_tuple_edge = _el_tuple_edge<_E>; + + template + concept _is_edge_desc = _el_basic_sourced_edge_desc<_E>; + + //template + //concept _has_value = _el_sourced_edge_desc<_E>; + + class _Cpo { + private: + enum class _St_ref { _None, _Member, _Non_member, _Tuple_id, _EDesc_id }; + + template + [[nodiscard]] static consteval _Choice_t<_St_ref> _Choose_edgl_ref() noexcept { + //static_assert(is_lvalue_reference_v<_E>); + if constexpr (_Has_edgl_ref_member<_E>) { + return {_St_ref::_Member, noexcept(_Fake_copy_init(declval<_E>().source_id()))}; + } else if constexpr (_Has_edgl_ref_ADL<_E>) { + return {_St_ref::_Non_member, noexcept(_Fake_copy_init(source_id(declval<_E>())))}; // intentional ADL + } else if constexpr (_is_tuple_edge<_E>) { + return {_St_ref::_Tuple_id, + noexcept(_Fake_copy_init(declval>()))}; // first element of tuple/pair + } else if constexpr (_is_edge_desc<_E>) { + return {_St_ref::_EDesc_id, + noexcept(_Fake_copy_init(declval()))}; // source_id of edge_descriptor + } else { + return {_St_ref::_None}; + } + } + + template + static constexpr _Choice_t<_St_ref> _Choice_edgl_ref = _Choose_edgl_ref>(); + + public: + /** + * @brief The source_id of an edgelist edge + * + * Complexity: O(1) + * + * Default implementation: + * + * @tparam E The edgelist value_type. + * @param e A edgelist edge instance. + * @return The source_id of the edge. + */ + template + requires(_Choice_edgl_ref<_E>._Strategy != _St_ref::_None) + [[nodiscard]] constexpr auto operator()(_E&& __e) const noexcept(_Choice_edgl_ref<_E>._No_throw) { + constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E>._Strategy; + //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); + //static_assert(same_as, int>); + //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); + + if constexpr (_Strat_ref == _St_ref::_Member) { + return __e.source_id(); + } else if constexpr (_Strat_ref == _St_ref::_Non_member) { + return source_id(__e); // intentional ADL + } else if constexpr (_Strat_ref == _St_ref::_Tuple_id) { + return get<1>(__e); // first element of tuple/pair + } else if constexpr (_Strat_ref == _St_ref::_EDesc_id) { + return __e.source_id; + } else { + static_assert(_Always_false<_E>, "source_id(e) or e.source_id() is not defined"); + } + } + }; +} // namespace _Source_id + +inline namespace _Cpos { + inline constexpr _Source_id::_Cpo source_id; +} + + // // edgelist ranges // @@ -45,7 +281,7 @@ concept _index_source_target_id = requires(E e) { { target_id(e) } -> same_as; }; template // For exposition only -concept _has_edge_value = requires(E e) { +concept has_edge_value = requires(E e) { { edge_value(e) }; }; @@ -55,11 +291,8 @@ concept basic_sourced_edgelist = ranges::forward_range && _source_target_id< template // For exposition only concept basic_sourced_index_edgelist = ranges::forward_range && _index_source_target_id>; -template // For exposition only -concept sourced_edgelist = basic_sourced_edgelist && _has_edge_value>; +// non-basic concepts imply edge reference which doesn't make much sense -template // For exposition only -concept sourced_index_edgelist = basic_sourced_index_edgelist && _has_edge_value>; // // edgelist types (note that concepts don't really do anything except document expectations) @@ -76,11 +309,11 @@ using edge_reference_t = ranges::range_reference_t; template // For exposition only using vertex_id_t = decltype(source_id(declval>())); -template // For exposition only +template // For exposition only using edge_value_t = decltype(edge_value(declval>())); -template // For exposition only -using edge_id_t = edge_descriptor, true, void, void>; +template // For exposition only +using edge_id_t = decltype(edge_id(declval>())); // template aliases can't be distinguished with concepts :( // diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f09edd4..81d9807 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,8 +42,9 @@ set(UNITTEST_SOURCES "csv_routes_csr_tests.cpp" "vertexlist_tests.cpp" "incidence_tests.cpp" - "neighbors_tests.cpp" + "neighbors_tests.cpp" "edgelist_tests.cpp" + "edgelist_view_tests.cpp" "examples_tests.cpp" "shortest_paths_tests.cpp" "transitive_closure_tests.cpp" diff --git a/tests/edgelist_tests.cpp b/tests/edgelist_tests.cpp index 5659887..6a54a87 100644 --- a/tests/edgelist_tests.cpp +++ b/tests/edgelist_tests.cpp @@ -1,321 +1,68 @@ #include #include -#include "csv_routes.hpp" -#include "graph/graph.hpp" -#include "graph/views/edgelist.hpp" -#include "graph/container/compressed_graph.hpp" - -using std::ranges::forward_range; -using std::remove_reference_t; -using std::is_const_v; -using std::is_lvalue_reference_v; -using std::forward_iterator; -using std::input_iterator; - -using std::graph::vertex_t; -using std::graph::vertex_id_t; -using std::graph::vertex_value_t; -using std::graph::vertex_edge_range_t; -using std::graph::vertex_reference_t; -using std::graph::edge_t; -using std::graph::edge_value_t; -using std::graph::edge_reference_t; - -using std::graph::graph_value; -using std::graph::vertices; -using std::graph::edges; -using std::graph::vertex_id; -using std::graph::vertex_value; -using std::graph::target_id; -using std::graph::target; -using std::graph::edge_value; -using std::graph::degree; -using std::graph::find_vertex; -using std::graph::find_vertex_edge; - -using std::graph::views::edgelist; - -using routes_compressed_graph_type = std::graph::container::compressed_graph; - -template -constexpr auto find_frankfurt_id(const G& g) { - return find_city_id(g, "Frankf\xC3\xBCrt"); -} - -template -auto find_frankfurt(G&& g) { - return find_city(g, "Frankf\xC3\xBCrt"); +#include "graph/edgelist.hpp" +#include "graph/graph_descriptors.hpp" +#include +#include + +using std::declval; +using std::vector; +using std::tuple; +using std::is_same_v; +using std::same_as; +using std::graph::edge_descriptor; +using namespace std::graph::edgelist; + +TEST_CASE("edgelist tuple test", "[edgelist][tuple]") { + using EL = vector>; + using E = std::ranges::range_value_t; + _Target_id::_Cpo cpo; + E e; + static_assert(same_as>); + + static_assert(!std::ranges::forward_range); + //static_assert(_el_value); + static_assert(_el_tuple_edge); + static_assert(_Target_id::_is_tuple_edge); + //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + + static_assert(!std::ranges::forward_range); + //static_assert(_el_value); + static_assert(_el_tuple_edge); + static_assert(_Source_id::_is_tuple_edge); + //static_assert(_Source_id::_Cpo::_Choice_edgl_ref._Strategy == _Source_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + + static_assert(_source_target_id); + static_assert(basic_sourced_edgelist); } -// Things to test -// compressed_graph with VV=void (does it compile?) -// push_back and emplace_back work correctly when adding city names (applies to compressed_graph & dynamic_graph) - -TEST_CASE("edgelist test", "[csr][edgelist]") { - - init_console(); - - using G = routes_compressed_graph_type; - auto&& g = load_ordered_graph(TEST_DATA_ROOT_DIR "germany_routes.csv", name_order_policy::source_order_found); - // name_order_policy::source_order_found gives best output with least overlap for germany routes - - const auto frankfurt = find_frankfurt(g); - const auto frankfurt_id = find_frankfurt_id(g); - - SECTION("non-const edgelist_iterator") { - static_assert(!std::is_const_v>); - static_assert(!std::is_const_v); - - REQUIRE(frankfurt); - vertex_t& u = **frankfurt; - - std::graph::edgelist_iterator i0; // default construction - std::graph::edgelist_iterator i1(g); - static_assert(std::forward_iterator, "edgelist_iterator must be a forward_iterator"); - static_assert(std::is_move_assignable_v, "edgelist_iterator must be move_assignable"); - static_assert(std::is_copy_assignable_v, "edgelist_iterator must be copy_assignable"); - { - auto&& [uid, vid, uv] = *i1; - static_assert(is_const_v, "vertex id must be const"); - static_assert(!is_const_v>, "edge must be non-const"); - REQUIRE(uid == 0); - REQUIRE(vid == 1); - } - { - auto&& [uid, vid, uv] = *++i1; - REQUIRE(uid == 0); - REQUIRE(vid == 4); - auto i1b = i1; - REQUIRE(i1b == i1); - } - - std::graph::edgelist_iterator i2(g); - { - auto&& [uid, vid, uv] = *i2; - static_assert(is_const_v, "vertex id must be const"); - static_assert(is_lvalue_reference_v, "edge must be lvalue reference"); - static_assert(!is_const_v>, "edge must be non-const"); - REQUIRE(uid == 0); - REQUIRE(vid == 1); - } - { - auto&& [uid, vid, uv] = *++i2; - REQUIRE(uid == 0); - REQUIRE(vid == 4); - auto i2b = i2; - REQUIRE(i2b == i2); - } - - static_assert(std::input_or_output_iterator); - using _It = std::graph::edgelist_iterator; - using _Se = std::graph::vertex_iterator_t; - bool yy = std::sentinel_for<_Se, _It>; - bool xx = std::sized_sentinel_for<_Se, _It>; - static_assert(std::sized_sentinel_for<_Se, _It> == false); - auto _Ki = - std::sized_sentinel_for<_Se, _It> ? std::ranges::subrange_kind::sized : std::ranges::subrange_kind::unsized; - - auto evf = [&g](edge_t& uv) -> double& { return edge_value(g, uv); }; - using EVF = decltype(evf); - - std::graph::edgelist_iterator i3(g, evf); - { - // The following asserts are used to isolate problem with failing input_or_output_iterator concept for edgelist_iterator - static_assert(std::movable, "edgelist_iterator is NOT movable"); - static_assert(std::default_initializable, "edgelist_iterator is NOT default_initializable"); - //static_assert(std::__detail::__is_signed_integer_like>, "edgelist_iterator is NOT __is_signed_integer_like"); - static_assert(std::weakly_incrementable, "edgelist_iterator is NOT weakly_incrementable"); - static_assert(std::input_or_output_iterator, - "edgelist_iterator is NOT an input_or_output_iterator"); - - auto&& [uid, vid, uv, km] = *i3; - REQUIRE(uid == 0); - REQUIRE(vid == 1); - REQUIRE(km == 85.0); - } - { - auto&& [uid, vid, uv, km] = *++i3; - REQUIRE(uid == 0); - REQUIRE(vid == 4); - REQUIRE(km == 217.0); - } - - //std::graph::edgelist_iterator j0; - //j0 = i0; - //i0 == j0; - } - - SECTION("const edgelist_iterator") { - using G2 = const G; - G2& g2 = g; - static_assert(std::is_const_v>, "graph must be const"); - - vertex_reference_t u = **frankfurt; - - //std::graph::edgelist_iterator i0; // default construction - std::graph::edgelist_iterator i1(g2); - static_assert(std::forward_iterator, "edgelist_iterator must be a forward_iterator"); - { - auto&& [uid, vid, uv] = *i1; - - edge_reference_t uv2 = uv; - static_assert(is_const_v>, "edge must be const"); - - static_assert(is_const_v, "id must be const"); - static_assert(is_lvalue_reference_v, "edge must be lvalue reference"); - static_assert(is_const_v>, "edge must be const"); - REQUIRE(vid == 1); - } - { - auto&& [uid, vid, uv] = *++i1; - REQUIRE(vid == 4); - auto i1b = i1; - REQUIRE(i1b == i1); - } - - std::graph::edgelist_iterator i2(g2); - { - auto&& [uid, vid, uv] = *i2; - static_assert(is_const_v, "id must be const"); - static_assert(is_const_v>, "edge must be const"); - REQUIRE(vid == 1); - } - { - auto&& [uid, vid, uv] = *++i2; - REQUIRE(vid == 4); - auto i2b = i2; - REQUIRE(i2b == i2); - } - - auto evf = [&g2](edge_reference_t uv) -> const double& { return edge_value(g2, uv); }; - using EVF = decltype(evf); - std::graph::edgelist_iterator i3(g2, evf); - { - auto&& [uid, vid, uv, km] = *i3; - REQUIRE(vid == 1); - REQUIRE(km == 85.0); - } - { - auto&& [uid, vid, uv, km] = *++i3; - REQUIRE(vid == 4); - REQUIRE(km == 217); - } - } - - SECTION("non-const edgelist") { - vertex_t& u = **frankfurt; - using view_t = decltype(std::graph::views::edgelist(g)); - static_assert(forward_range, "edgelist(g) is not a forward_range"); - { - size_t cnt = 0; - for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g)) { - ++cnt; - } - REQUIRE(cnt == 11); - } - { - size_t cnt = 0; - for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g, 3, 5)) { - ++cnt; - } - REQUIRE(cnt == 3); - } - { - // vertices [7..10) have no edges - size_t cnt = 0; - for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g, 7, 10)) { - ++cnt; - } - REQUIRE(cnt == 0); - } - } - - SECTION("const edgelist") { - using G2 = const G; - G2& g2 = g; - vertex_reference_t u = **frankfurt; - - using view_t = decltype(std::graph::views::edgelist(g2)); - static_assert(forward_range, "edgelist(g) is not a forward_range"); - { - size_t cnt = 0; - for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g2)) { - ++cnt; - } - REQUIRE(cnt == 11); - } - { - size_t cnt = 0; - for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g, 3, 5)) { - ++cnt; - } - REQUIRE(cnt == 3); - } - { - // vertices [7..10) have no edges - size_t cnt = 0; - for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g, 7, 10)) { - ++cnt; - } - REQUIRE(cnt == 0); - } - } - - SECTION("non-const edgelist with edge_fn") { - // Note: must include trailing return type on lambda - vertex_reference_t u = **frankfurt; - size_t cnt = 0; - auto evf = [&g](edge_reference_t uv) -> double& { return edge_value(g, uv); }; - for (auto&& [uid, vid, uv, val] : std::graph::views::edgelist(g, evf)) { - ++cnt; - } - REQUIRE(cnt == 11); - } - SECTION("const edgelist with edge_fn") { - // Note: must include trailing return type on lambda - using G2 = const G; - G2& g2 = g; - vertex_reference_t u = **frankfurt; - auto evf = [&g2](edge_reference_t uv) -> const double& { return edge_value(g2, uv); }; - size_t cnt = 0; - for (auto&& [uid, vid, uv, val] : std::graph::views::edgelist(g2, evf)) { - ++cnt; - } - REQUIRE(cnt == 11); - } - - SECTION("edgelist is a forward view") { - auto elist = edgelist(g); - auto it1 = elist.begin(); - using I = decltype(it1); - - auto it2 = it1; // copyable - I it3(it1); // copy-constuctible - auto it4 = std::move(it2); // movable - I it5(std::move(it3)); // move-constuctible - I it6; // default-constructible - - using I2 = std::remove_cvref_t; - REQUIRE(std::move_constructible); - REQUIRE(std::copyable); - REQUIRE(std::movable); - REQUIRE(std::swappable); - CHECK(std::is_default_constructible_v); - - CHECK(std::input_or_output_iterator); - CHECK(std::indirectly_readable); - CHECK(std::input_iterator); - CHECK(std::forward_iterator); - - using Rng = decltype(elist); - CHECK(std::ranges::range); - CHECK(std::movable); - //CHECK(std::derived_from); - CHECK(std::ranges::enable_view); - CHECK(std::ranges::view); - - auto it8 = std::ranges::begin(elist); - auto it9 = std::ranges::end(elist); - auto empt = std::ranges::empty(elist); - } +TEST_CASE("edgelist edge_descriptor test", "[edgelist][edge_descriptor]") { + using EL = vector>; + using E = std::ranges::range_value_t; + _Target_id::_Cpo cpo; + E e; + static_assert(same_as>); + + static_assert(!std::ranges::forward_range); + //static_assert(_el_value); + static_assert(_el_basic_sourced_edge_desc); + static_assert(_Target_id::_is_edge_desc); + //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + + static_assert(!std::ranges::forward_range); + //static_assert(_el_value); + static_assert(_el_basic_sourced_edge_desc); + static_assert(_Source_id::_is_edge_desc); + //static_assert(_Source_id::_Cpo::_Choice_edgl_ref._Strategy == _Source_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + + static_assert(_source_target_id); + static_assert(basic_sourced_edgelist); } diff --git a/tests/edgelist_view_tests.cpp b/tests/edgelist_view_tests.cpp new file mode 100644 index 0000000..ba99b2a --- /dev/null +++ b/tests/edgelist_view_tests.cpp @@ -0,0 +1,321 @@ +#include +#include +#include "csv_routes.hpp" +#include "graph/graph.hpp" +#include "graph/views/edgelist.hpp" +#include "graph/container/compressed_graph.hpp" + +using std::ranges::forward_range; +using std::remove_reference_t; +using std::is_const_v; +using std::is_lvalue_reference_v; +using std::forward_iterator; +using std::input_iterator; + +using std::graph::vertex_t; +using std::graph::vertex_id_t; +using std::graph::vertex_value_t; +using std::graph::vertex_edge_range_t; +using std::graph::vertex_reference_t; +using std::graph::edge_t; +using std::graph::edge_value_t; +using std::graph::edge_reference_t; + +using std::graph::graph_value; +using std::graph::vertices; +using std::graph::edges; +using std::graph::vertex_id; +using std::graph::vertex_value; +using std::graph::target_id; +using std::graph::target; +using std::graph::edge_value; +using std::graph::degree; +using std::graph::find_vertex; +using std::graph::find_vertex_edge; + +using std::graph::views::edgelist; + +using routes_compressed_graph_type = std::graph::container::compressed_graph; + +template +constexpr auto find_frankfurt_id(const G& g) { + return find_city_id(g, "Frankf\xC3\xBCrt"); +} + +template +auto find_frankfurt(G&& g) { + return find_city(g, "Frankf\xC3\xBCrt"); +} + +// Things to test +// compressed_graph with VV=void (does it compile?) +// push_back and emplace_back work correctly when adding city names (applies to compressed_graph & dynamic_graph) + +TEST_CASE("edgelist view test", "[csr][edgelist]") { + + init_console(); + + using G = routes_compressed_graph_type; + auto&& g = load_ordered_graph(TEST_DATA_ROOT_DIR "germany_routes.csv", name_order_policy::source_order_found); + // name_order_policy::source_order_found gives best output with least overlap for germany routes + + const auto frankfurt = find_frankfurt(g); + const auto frankfurt_id = find_frankfurt_id(g); + + SECTION("non-const edgelist_iterator") { + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v); + + REQUIRE(frankfurt); + vertex_t& u = **frankfurt; + + std::graph::edgelist_iterator i0; // default construction + std::graph::edgelist_iterator i1(g); + static_assert(std::forward_iterator, "edgelist_iterator must be a forward_iterator"); + static_assert(std::is_move_assignable_v, "edgelist_iterator must be move_assignable"); + static_assert(std::is_copy_assignable_v, "edgelist_iterator must be copy_assignable"); + { + auto&& [uid, vid, uv] = *i1; + static_assert(is_const_v, "vertex id must be const"); + static_assert(!is_const_v>, "edge must be non-const"); + REQUIRE(uid == 0); + REQUIRE(vid == 1); + } + { + auto&& [uid, vid, uv] = *++i1; + REQUIRE(uid == 0); + REQUIRE(vid == 4); + auto i1b = i1; + REQUIRE(i1b == i1); + } + + std::graph::edgelist_iterator i2(g); + { + auto&& [uid, vid, uv] = *i2; + static_assert(is_const_v, "vertex id must be const"); + static_assert(is_lvalue_reference_v, "edge must be lvalue reference"); + static_assert(!is_const_v>, "edge must be non-const"); + REQUIRE(uid == 0); + REQUIRE(vid == 1); + } + { + auto&& [uid, vid, uv] = *++i2; + REQUIRE(uid == 0); + REQUIRE(vid == 4); + auto i2b = i2; + REQUIRE(i2b == i2); + } + + static_assert(std::input_or_output_iterator); + using _It = std::graph::edgelist_iterator; + using _Se = std::graph::vertex_iterator_t; + bool yy = std::sentinel_for<_Se, _It>; + bool xx = std::sized_sentinel_for<_Se, _It>; + static_assert(std::sized_sentinel_for<_Se, _It> == false); + auto _Ki = + std::sized_sentinel_for<_Se, _It> ? std::ranges::subrange_kind::sized : std::ranges::subrange_kind::unsized; + + auto evf = [&g](edge_t& uv) -> double& { return edge_value(g, uv); }; + using EVF = decltype(evf); + + std::graph::edgelist_iterator i3(g, evf); + { + // The following asserts are used to isolate problem with failing input_or_output_iterator concept for edgelist_iterator + static_assert(std::movable, "edgelist_iterator is NOT movable"); + static_assert(std::default_initializable, "edgelist_iterator is NOT default_initializable"); + //static_assert(std::__detail::__is_signed_integer_like>, "edgelist_iterator is NOT __is_signed_integer_like"); + static_assert(std::weakly_incrementable, "edgelist_iterator is NOT weakly_incrementable"); + static_assert(std::input_or_output_iterator, + "edgelist_iterator is NOT an input_or_output_iterator"); + + auto&& [uid, vid, uv, km] = *i3; + REQUIRE(uid == 0); + REQUIRE(vid == 1); + REQUIRE(km == 85.0); + } + { + auto&& [uid, vid, uv, km] = *++i3; + REQUIRE(uid == 0); + REQUIRE(vid == 4); + REQUIRE(km == 217.0); + } + + //std::graph::edgelist_iterator j0; + //j0 = i0; + //i0 == j0; + } + + SECTION("const edgelist_iterator") { + using G2 = const G; + G2& g2 = g; + static_assert(std::is_const_v>, "graph must be const"); + + vertex_reference_t u = **frankfurt; + + //std::graph::edgelist_iterator i0; // default construction + std::graph::edgelist_iterator i1(g2); + static_assert(std::forward_iterator, "edgelist_iterator must be a forward_iterator"); + { + auto&& [uid, vid, uv] = *i1; + + edge_reference_t uv2 = uv; + static_assert(is_const_v>, "edge must be const"); + + static_assert(is_const_v, "id must be const"); + static_assert(is_lvalue_reference_v, "edge must be lvalue reference"); + static_assert(is_const_v>, "edge must be const"); + REQUIRE(vid == 1); + } + { + auto&& [uid, vid, uv] = *++i1; + REQUIRE(vid == 4); + auto i1b = i1; + REQUIRE(i1b == i1); + } + + std::graph::edgelist_iterator i2(g2); + { + auto&& [uid, vid, uv] = *i2; + static_assert(is_const_v, "id must be const"); + static_assert(is_const_v>, "edge must be const"); + REQUIRE(vid == 1); + } + { + auto&& [uid, vid, uv] = *++i2; + REQUIRE(vid == 4); + auto i2b = i2; + REQUIRE(i2b == i2); + } + + auto evf = [&g2](edge_reference_t uv) -> const double& { return edge_value(g2, uv); }; + using EVF = decltype(evf); + std::graph::edgelist_iterator i3(g2, evf); + { + auto&& [uid, vid, uv, km] = *i3; + REQUIRE(vid == 1); + REQUIRE(km == 85.0); + } + { + auto&& [uid, vid, uv, km] = *++i3; + REQUIRE(vid == 4); + REQUIRE(km == 217); + } + } + + SECTION("non-const edgelist") { + vertex_t& u = **frankfurt; + using view_t = decltype(std::graph::views::edgelist(g)); + static_assert(forward_range, "edgelist(g) is not a forward_range"); + { + size_t cnt = 0; + for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g)) { + ++cnt; + } + REQUIRE(cnt == 11); + } + { + size_t cnt = 0; + for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g, 3, 5)) { + ++cnt; + } + REQUIRE(cnt == 3); + } + { + // vertices [7..10) have no edges + size_t cnt = 0; + for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g, 7, 10)) { + ++cnt; + } + REQUIRE(cnt == 0); + } + } + + SECTION("const edgelist view") { + using G2 = const G; + G2& g2 = g; + vertex_reference_t u = **frankfurt; + + using view_t = decltype(std::graph::views::edgelist(g2)); + static_assert(forward_range, "edgelist(g) is not a forward_range"); + { + size_t cnt = 0; + for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g2)) { + ++cnt; + } + REQUIRE(cnt == 11); + } + { + size_t cnt = 0; + for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g, 3, 5)) { + ++cnt; + } + REQUIRE(cnt == 3); + } + { + // vertices [7..10) have no edges + size_t cnt = 0; + for (auto&& [uid, vid, uv] : std::graph::views::edgelist(g, 7, 10)) { + ++cnt; + } + REQUIRE(cnt == 0); + } + } + + SECTION("non-const edgelist with edge_fn") { + // Note: must include trailing return type on lambda + vertex_reference_t u = **frankfurt; + size_t cnt = 0; + auto evf = [&g](edge_reference_t uv) -> double& { return edge_value(g, uv); }; + for (auto&& [uid, vid, uv, val] : std::graph::views::edgelist(g, evf)) { + ++cnt; + } + REQUIRE(cnt == 11); + } + SECTION("const edgelist with edge_fn") { + // Note: must include trailing return type on lambda + using G2 = const G; + G2& g2 = g; + vertex_reference_t u = **frankfurt; + auto evf = [&g2](edge_reference_t uv) -> const double& { return edge_value(g2, uv); }; + size_t cnt = 0; + for (auto&& [uid, vid, uv, val] : std::graph::views::edgelist(g2, evf)) { + ++cnt; + } + REQUIRE(cnt == 11); + } + + SECTION("edgelist is a forward view") { + auto elist = edgelist(g); + auto it1 = elist.begin(); + using I = decltype(it1); + + auto it2 = it1; // copyable + I it3(it1); // copy-constuctible + auto it4 = std::move(it2); // movable + I it5(std::move(it3)); // move-constuctible + I it6; // default-constructible + + using I2 = std::remove_cvref_t; + REQUIRE(std::move_constructible); + REQUIRE(std::copyable); + REQUIRE(std::movable); + REQUIRE(std::swappable); + CHECK(std::is_default_constructible_v); + + CHECK(std::input_or_output_iterator); + CHECK(std::indirectly_readable); + CHECK(std::input_iterator); + CHECK(std::forward_iterator); + + using Rng = decltype(elist); + CHECK(std::ranges::range); + CHECK(std::movable); + //CHECK(std::derived_from); + CHECK(std::ranges::enable_view); + CHECK(std::ranges::view); + + auto it8 = std::ranges::begin(elist); + auto it9 = std::ranges::end(elist); + auto empt = std::ranges::empty(elist); + } +} From 92267c66471352d359030aa14b1ad4836ebc887b Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Mon, 27 May 2024 11:03:23 -0400 Subject: [PATCH 2/5] Implement edge_value(e) for edgelist Support use of tuple Support use of edge_descriptor --- include/graph/edgelist.hpp | 129 +++++++++++++++++++++++++++++++------ tests/edgelist_tests.cpp | 54 ++++++++++++++++ 2 files changed, 165 insertions(+), 18 deletions(-) diff --git a/include/graph/edgelist.hpp b/include/graph/edgelist.hpp index a3e8768..81fbd59 100644 --- a/include/graph/edgelist.hpp +++ b/include/graph/edgelist.hpp @@ -59,15 +59,15 @@ concept _el_basic_sourced_index_edge_desc = _el_basic_sourced_edge_desc<_E> && r { elv.target_id } -> same_as; }; -template -concept _el_sourced_edge_desc = _el_basic_sourced_edge_desc<_E> && requires(_E elv) { - { elv.value }; -}; - -template -concept _el_sourced_index_edge_desc = _el_basic_sourced_index_edge_desc<_E> && requires(_E elv) { - { elv.value }; -}; +//template +//concept _el_sourced_edge_desc = _el_basic_sourced_edge_desc<_E> && requires(_E elv) { +// { elv.value }; +//}; +// +//template +//concept _el_sourced_index_edge_desc = _el_basic_sourced_index_edge_desc<_E> && requires(_E elv) { +// { elv.value }; +//}; // @@ -99,9 +99,6 @@ namespace _Target_id { template concept _is_edge_desc = _el_basic_sourced_edge_desc<_E>; - //template - //concept _has_value = _el_sourced_edge_desc<_E>; - class _Cpo { private: enum class _St_ref { _None, _Member, _Non_member, _Tuple_id, _EDesc_id }; @@ -199,9 +196,6 @@ namespace _Source_id { template concept _is_edge_desc = _el_basic_sourced_edge_desc<_E>; - //template - //concept _has_value = _el_sourced_edge_desc<_E>; - class _Cpo { private: enum class _St_ref { _None, _Member, _Non_member, _Tuple_id, _EDesc_id }; @@ -268,7 +262,105 @@ inline namespace _Cpos { // -// edgelist ranges +// edge_value(e) -> vertex_id_t +// +// For E=tuple, returns get<2>(e) +// For E=edge_descriptor, returns e.value +// Caller may override +// +namespace _Edge_value { +# if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-1681199 + void edge_value() = delete; // Block unqualified name lookup +# else // ^^^ no workaround / workaround vvv + void edge_value(); +# endif // ^^^ workaround ^^^ + + template + concept _Has_edgl_ref_member = requires(_E&& __e) { + { _Fake_copy_init(__e.edge_value()) }; + }; + template + concept _Has_edgl_ref_ADL = _Has_class_or_enum_type<_E> // + && requires(_E&& __e) { + { _Fake_copy_init(edge_value(__e)) }; // intentional ADL + }; + + template + concept _is_tuple_edge = _el_tuple_edge<_E> && (tuple_size_v<_E> >= 3); + + template + concept _is_edge_desc = _el_basic_sourced_edge_desc<_E> && requires(_E elv) { + true; + { elv.value }; //->same_as; + }; + + class _Cpo { + public: + enum class _St_ref { _None, _Member, _Non_member, _Tuple_id, _EDesc_id }; + + template + [[nodiscard]] static consteval _Choice_t<_St_ref> _Choose_edgl_ref() noexcept { + //static_assert(is_lvalue_reference_v<_E>); + if constexpr (_Has_edgl_ref_member<_E>) { + return {_St_ref::_Member, noexcept(_Fake_copy_init(declval<_E>().edge_value()))}; + } else if constexpr (_Has_edgl_ref_ADL<_E>) { + return {_St_ref::_Non_member, noexcept(_Fake_copy_init(edge_value(declval<_E>())))}; // intentional ADL + } else if constexpr (_is_tuple_edge<_E>) { + return {_St_ref::_Tuple_id, + noexcept(_Fake_copy_init(declval>()))}; // first element of tuple/pair + } else if constexpr (_is_edge_desc<_E>) { + return {_St_ref::_EDesc_id, + noexcept(_Fake_copy_init(declval()))}; // edge_value of edge_descriptor + } else { + return {_St_ref::_None}; + } + } + + template + static constexpr _Choice_t<_St_ref> _Choice_edgl_ref = _Choose_edgl_ref>(); + + public: + /** + * @brief The edge_value of an edgelist edge + * + * Complexity: O(1) + * + * Default implementation: + * + * @tparam E The edgelist value_type. + * @param e A edgelist edge instance. + * @return The edge_value of the edge. + */ + template + //requires(_Choice_edgl_ref<_E>._Strategy != _St_ref::_None) + [[nodiscard]] constexpr auto operator()(_E&& __e) const noexcept(_Choice_edgl_ref<_E>._No_throw) { + constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E>._Strategy; + //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); + //static_assert(same_as, int>); + //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); + + if constexpr (_Strat_ref == _St_ref::_Member) { + return __e.edge_value(); + } else if constexpr (_Strat_ref == _St_ref::_Non_member) { + return edge_value(__e); // intentional ADL + } else if constexpr (_Strat_ref == _St_ref::_Tuple_id) { + return get<2>(__e); // first element of tuple/pair + } else if constexpr (_Strat_ref == _St_ref::_EDesc_id) { + return __e.value; + } else { + static_assert(_Always_false<_E>, "edge_value(e) or e.edge_value() is not defined"); + } + } + }; +} // namespace _Edge_value + +inline namespace _Cpos { + inline constexpr _Edge_value::_Cpo edge_value; +} + + +// +// edgelist concepts // template // For exposition only concept _source_target_id = requires(E e) { @@ -280,8 +372,8 @@ concept _index_source_target_id = requires(E e) { { source_id(e) } -> integral; { target_id(e) } -> same_as; }; -template // For exposition only -concept has_edge_value = requires(E e) { +template // For exposition only; specialization of edge_value(g,uv) needed +concept _has_edge_value = requires(_E e) { { edge_value(e) }; }; @@ -291,6 +383,7 @@ concept basic_sourced_edgelist = ranges::forward_range && _source_target_id< template // For exposition only concept basic_sourced_index_edgelist = ranges::forward_range && _index_source_target_id>; + // non-basic concepts imply edge reference which doesn't make much sense diff --git a/tests/edgelist_tests.cpp b/tests/edgelist_tests.cpp index 6a54a87..08c5984 100644 --- a/tests/edgelist_tests.cpp +++ b/tests/edgelist_tests.cpp @@ -23,21 +23,49 @@ TEST_CASE("edgelist tuple test", "[edgelist][tuple]") { static_assert(!std::ranges::forward_range); //static_assert(_el_value); static_assert(_el_tuple_edge); + static_assert(_Target_id::_is_tuple_edge); //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); static_assert(same_as())), int>); static_assert(same_as); + static_assert(_Source_id::_is_tuple_edge); + //static_assert(_Source_id::_Cpo::_Choice_edgl_ref._Strategy == _Source_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + + static_assert(_source_target_id); + static_assert(basic_sourced_edgelist); +} + +TEST_CASE("edgelist tuple test with value", "[edgelist][tuple]") { + using EL = vector>; + using E = std::ranges::range_value_t; + _Target_id::_Cpo cpo; + E e; + static_assert(same_as>); + static_assert(!std::ranges::forward_range); //static_assert(_el_value); static_assert(_el_tuple_edge); + + static_assert(_Target_id::_is_tuple_edge); + //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + static_assert(_Source_id::_is_tuple_edge); //static_assert(_Source_id::_Cpo::_Choice_edgl_ref._Strategy == _Source_id::_Cpo::_St_ref::_Tuple_id); static_assert(same_as())), int>); static_assert(same_as); + static_assert(_Edge_value::_is_tuple_edge); + //static_assert(_Edge_value::_Cpo::_Choice_edgl_ref._Strategy == _Edge_value::_Cpo::_St_ref::_Tuple_id); + //static_assert(same_as())), double>); + static_assert(_source_target_id); static_assert(basic_sourced_edgelist); + static_assert(_has_edge_value); } TEST_CASE("edgelist edge_descriptor test", "[edgelist][edge_descriptor]") { @@ -50,19 +78,45 @@ TEST_CASE("edgelist edge_descriptor test", "[edgelist][edge_descriptor]") { static_assert(!std::ranges::forward_range); //static_assert(_el_value); static_assert(_el_basic_sourced_edge_desc); + static_assert(_Target_id::_is_edge_desc); //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); static_assert(same_as())), int>); static_assert(same_as); + static_assert(_Source_id::_is_edge_desc); + //static_assert(_Source_id::_Cpo::_Choice_edgl_ref._Strategy == _Source_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + + static_assert(_source_target_id); + static_assert(basic_sourced_edgelist); +} + +TEST_CASE("edgelist edge_descriptor test with value", "[edgelist][edge_descriptor]") { + using EL = vector>; + using E = std::ranges::range_value_t; + _Target_id::_Cpo cpo; + E e; + static_assert(same_as>); + static_assert(!std::ranges::forward_range); //static_assert(_el_value); static_assert(_el_basic_sourced_edge_desc); + + static_assert(_Target_id::_is_edge_desc); + //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + static_assert(_Source_id::_is_edge_desc); //static_assert(_Source_id::_Cpo::_Choice_edgl_ref._Strategy == _Source_id::_Cpo::_St_ref::_Tuple_id); static_assert(same_as())), int>); static_assert(same_as); + static_assert(_Edge_value::_is_edge_desc); + static_assert(_source_target_id); static_assert(basic_sourced_edgelist); + static_assert(_has_edge_value); } From 745224aedc2d7c4f442813f0e70619204bd80af2 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Mon, 27 May 2024 12:13:24 -0400 Subject: [PATCH 3/5] edgelist documentation and refinements --- include/graph/edgelist.hpp | 244 ++++++++++++++++++++++--------------- tests/edgelist_tests.cpp | 20 +-- 2 files changed, 156 insertions(+), 108 deletions(-) diff --git a/include/graph/edgelist.hpp b/include/graph/edgelist.hpp index 81fbd59..63c8ffa 100644 --- a/include/graph/edgelist.hpp +++ b/include/graph/edgelist.hpp @@ -1,6 +1,5 @@ #pragma once -#include "detail/graph_cpo.hpp" #include "graph.hpp" #include #include @@ -8,67 +7,116 @@ #ifndef EDGELIST_HPP # define EDGELIST_HPP -// function support -// source_id(el) -// target_id(el) -// edge_value(el) -// num_edges -// has_edge -// contains_edge(el,uid,vid) -// edge_id +// This file implements the interface for an edgelist (el). // -// type support -// vertex_id_t -// edge_t -// edge_value_t -// edge_range_t +// An edgelist is a range of edges where source_id(e) and target_id(e) are property +// functions that can be called on an edge (value type of the range). // -// edge_descriptor : {source_id, target_id} -// edge_descriptor : {source_id, target_id, EV} +// An optional edge_value(e) property can also be used if a value is defined for +// the edgelist. Use the has_edge_value concept to determine if it defined. // -// is_edge_descriptor_v +// The concepts, types and property functions mirror definitions for edges and +// and edge for an adjacency list. // -// Target concepts for edgelist -namespace std::graph::edgelist { -// move implementations for source_id(e), target_id(e), edge_value(e) into std::graph::edgelist? -// other functions to support: num_edges(el), contains_edge(uid,vid), edge_id(e) - -// Support the use of std containers for adj list definitions -template -concept _el_value = !ranges::forward_range<_E>; // avoid conflict with adjacency list - -template -concept _el_tuple_edge = _el_value<_E> && // - same_as, tuple_element_t<1, _E>>; - -template -concept _el_index_tuple_edge = _el_tuple_edge<_E> && // - integral>; - -template -concept _el_basic_sourced_edge_desc = - same_as && requires(_E elv) { - { elv.source_id }; - { elv.target_id }; - }; - -template -concept _el_basic_sourced_index_edge_desc = _el_basic_sourced_edge_desc<_E> && requires(_E elv) { - { elv.source_id } -> integral; - { elv.target_id } -> same_as; -}; - -//template -//concept _el_sourced_edge_desc = _el_basic_sourced_edge_desc<_E> && requires(_E elv) { -// { elv.value }; -//}; +// edgelist concepts +// ----------------------------------------------------------------------------- +// basic_sourced_edgelist +// basic_sourced_index_edgelist +// has_edge_value +// +// Type aliases +// ----------------------------------------------------------------------------- +// edge_range_t = EL +// edge_iterator_t = range_iterator_t +// edge_t = range_value_t +// edge_reference_t = range_value_t +// edge_value_t = decltype(edge_value(e)) (optional) +// vertex_id_t = decltype(source_id(e)) +// +// edgelist functions +// ----------------------------------------------------------------------------- +// num_edges(el) (todo) +// has_edge(el) (todo) +// contains_edge(el,uid,vid) (todo) +// +// edge functions +// ----------------------------------------------------------------------------- +// source_id(e) +// target_id(e) +// edge_value(e) +// edge_id(e) (todo) +// +// Edge definitions supported without overrides +// ----------------------------------------------------------------------------- +// The standard implementation supports two edge types with support for +// source_id(e) and target_id(e) to return their respective values, and an +// optional edge_value(e) if the edge has a value (shown following). The +// functions can be overridden for user-defined edge types. +// +// pair +// tuple +// tuple +// +// edge_descriptor : {source_id, target_id} +// edge_descriptor : {source_id, target_id, EV} +// +// edge_descriptor : {source_id, target_id, edge} +// edge_descriptor : {source_id, target_id, edge, EV} +// +// Naming conventions +// ----------------------------------------------------------------------------- +// Type Variable Description +// -------- ----------- -------------------------------------------------------- +// EL el EdgeList +// E e Edge on an edgelist +// EV val Edge Value // -//template -//concept _el_sourced_index_edge_desc = _el_basic_sourced_index_edge_desc<_E> && requires(_E elv) { -// { elv.value }; -//}; +// merge implementation into std::graph with single namespace? +// Issues: +// 1. name conflict with edgelist view? No: basic_sourced_edgelist vs. views::edgelist. +// 2. template aliases can't be distinguished by concepts +// 3. vertex_id_t definition for adjlist and edgelist have be done in separate locations + +namespace std::graph::edgelist { + +namespace _detail { + // + // An edge type cannot be a range, which distinguishes it from an adjacency list + // that is a range-of-ranges. + // + template // For exposition only + concept _el_edge = !ranges::range<_E>; + + // + // Support the use of std containers for edgelist edge definitions + // + template // For exposition only + concept _el_tuple_edge = _el_edge<_E> && // + same_as, tuple_element_t<1, _E>>; + + template // For exposition only + concept _el_index_tuple_edge = _el_tuple_edge<_E> && // + integral>; + + // + // Suport the use of edge_descriptor for edgelist edge definitions + // (Only types and values needed from edge_descriptor are used and there is no + // explicit use of edge_descriptor. This is deemed more flexible and no + // functionality is compromised for it.) + // + template // For exposition only + concept _el_basic_sourced_edge_desc = _el_edge<_E> && // + same_as().source_id)> && + same_as().target_id)> && + same_as; + + template // For exposition only + concept _el_basic_sourced_index_edge_desc = + _el_basic_sourced_edge_desc<_E> && integral && + integral; +} // namespace _detail // // target_id(g,uv) -> vertex_id_t @@ -94,10 +142,10 @@ namespace _Target_id { }; template - concept _is_tuple_edge = _el_tuple_edge<_E>; + concept _is_tuple_edge = _detail::_el_tuple_edge<_E>; template - concept _is_edge_desc = _el_basic_sourced_edge_desc<_E>; + concept _is_edge_desc = _detail::_el_basic_sourced_edge_desc<_E>; class _Cpo { private: @@ -131,7 +179,9 @@ namespace _Target_id { * * Complexity: O(1) * - * Default implementation: + * Default implementation: + * get<0>(e) for tuple or pair + * e.source_id for edge_descriptor * * @tparam G The graph type. * @param g A graph instance. @@ -142,9 +192,6 @@ namespace _Target_id { requires(_Choice_edgl_ref<_E>._Strategy != _St_ref::_None) [[nodiscard]] constexpr auto operator()(_E&& __e) const noexcept(_Choice_edgl_ref<_E>._No_throw) { constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E>._Strategy; - //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); - //static_assert(same_as, int>); - //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); if constexpr (_Strat_ref == _St_ref::_Member) { return __e.target_id(); @@ -191,10 +238,10 @@ namespace _Source_id { }; template - concept _is_tuple_edge = _el_tuple_edge<_E>; + concept _is_tuple_edge = _detail::_el_tuple_edge<_E>; template - concept _is_edge_desc = _el_basic_sourced_edge_desc<_E>; + concept _is_edge_desc = _detail::_el_basic_sourced_edge_desc<_E>; class _Cpo { private: @@ -228,6 +275,8 @@ namespace _Source_id { * Complexity: O(1) * * Default implementation: + * get<1>(e) for tuple or pair + * e.target_id for edge_descriptor * * @tparam E The edgelist value_type. * @param e A edgelist edge instance. @@ -237,9 +286,6 @@ namespace _Source_id { requires(_Choice_edgl_ref<_E>._Strategy != _St_ref::_None) [[nodiscard]] constexpr auto operator()(_E&& __e) const noexcept(_Choice_edgl_ref<_E>._No_throw) { constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E>._Strategy; - //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); - //static_assert(same_as, int>); - //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); if constexpr (_Strat_ref == _St_ref::_Member) { return __e.source_id(); @@ -286,10 +332,10 @@ namespace _Edge_value { }; template - concept _is_tuple_edge = _el_tuple_edge<_E> && (tuple_size_v<_E> >= 3); + concept _is_tuple_edge = _detail::_el_tuple_edge<_E> && (tuple_size_v<_E> >= 3); template - concept _is_edge_desc = _el_basic_sourced_edge_desc<_E> && requires(_E elv) { + concept _is_edge_desc = _detail::_el_basic_sourced_edge_desc<_E> && requires(_E elv) { true; { elv.value }; //->same_as; }; @@ -326,18 +372,17 @@ namespace _Edge_value { * Complexity: O(1) * * Default implementation: + * get<2>(e) for tuple + * e.value for edge_descriptor * * @tparam E The edgelist value_type. * @param e A edgelist edge instance. - * @return The edge_value of the edge. + * @return The edge_value of the edge, when supported. */ template //requires(_Choice_edgl_ref<_E>._Strategy != _St_ref::_None) [[nodiscard]] constexpr auto operator()(_E&& __e) const noexcept(_Choice_edgl_ref<_E>._No_throw) { constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E>._Strategy; - //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); - //static_assert(same_as, int>); - //static_assert(_Choice_edgl_ref<_E>._Strategy == _St_ref::_Tuple_id); if constexpr (_Strat_ref == _St_ref::_Member) { return __e.edge_value(); @@ -362,51 +407,54 @@ inline namespace _Cpos { // // edgelist concepts // -template // For exposition only -concept _source_target_id = requires(E e) { - { source_id(e) }; - { target_id(e) } -> same_as; -}; -template // For exposition only -concept _index_source_target_id = requires(E e) { - { source_id(e) } -> integral; - { target_id(e) } -> same_as; -}; -template // For exposition only; specialization of edge_value(g,uv) needed -concept _has_edge_value = requires(_E e) { - { edge_value(e) }; -}; - -template // For exposition only -concept basic_sourced_edgelist = ranges::forward_range && _source_target_id>; +template // For exposition only +concept basic_sourced_edgelist = ranges::forward_range && // + !ranges::range> && // distinguish from adjacency list + requires(ranges::range_value_t e) { + { source_id(e) }; + { target_id(e) } -> same_as; + }; -template // For exposition only -concept basic_sourced_index_edgelist = ranges::forward_range && _index_source_target_id>; +template // For exposition only +concept basic_sourced_index_edgelist = basic_sourced_edgelist && // + requires(ranges::range_value_t e) { + { source_id(e) } -> integral; + { target_id(e) } -> integral; + }; +template // For exposition only +concept has_edge_value = basic_sourced_edgelist && // + requires(ranges::range_value_t e) { + { edge_value(e) }; + }; -// non-basic concepts imply edge reference which doesn't make much sense +// (non-basic concepts imply inclusion of an edge reference which doesn't make much sense) // // edgelist types (note that concepts don't really do anything except document expectations) // template // For exposition only -using edge_iterator_t = ranges::iterator_t; +using edge_range_t = EL; + +template // For exposition only +using edge_iterator_t = ranges::iterator_t>; template // For exposition only -using edge_t = ranges::range_value_t; +using edge_t = ranges::range_value_t>; template // For exposition only -using edge_reference_t = ranges::range_reference_t; +using edge_reference_t = ranges::range_reference_t>; template // For exposition only -using vertex_id_t = decltype(source_id(declval>())); +using edge_value_t = decltype(edge_value(declval>>())); template // For exposition only -using edge_value_t = decltype(edge_value(declval>())); +using edge_id_t = decltype(edge_id(declval>>())); template // For exposition only -using edge_id_t = decltype(edge_id(declval>())); +using vertex_id_t = decltype(source_id(declval>>())); + // template aliases can't be distinguished with concepts :( // diff --git a/tests/edgelist_tests.cpp b/tests/edgelist_tests.cpp index 08c5984..4a5fcb4 100644 --- a/tests/edgelist_tests.cpp +++ b/tests/edgelist_tests.cpp @@ -22,7 +22,7 @@ TEST_CASE("edgelist tuple test", "[edgelist][tuple]") { static_assert(!std::ranges::forward_range); //static_assert(_el_value); - static_assert(_el_tuple_edge); + static_assert(_detail::_el_tuple_edge); static_assert(_Target_id::_is_tuple_edge); //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); @@ -34,7 +34,7 @@ TEST_CASE("edgelist tuple test", "[edgelist][tuple]") { static_assert(same_as())), int>); static_assert(same_as); - static_assert(_source_target_id); + //static_assert(_source_target_id); static_assert(basic_sourced_edgelist); } @@ -47,7 +47,7 @@ TEST_CASE("edgelist tuple test with value", "[edgelist][tuple]") { static_assert(!std::ranges::forward_range); //static_assert(_el_value); - static_assert(_el_tuple_edge); + static_assert(_detail::_el_tuple_edge); static_assert(_Target_id::_is_tuple_edge); //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); @@ -63,9 +63,9 @@ TEST_CASE("edgelist tuple test with value", "[edgelist][tuple]") { //static_assert(_Edge_value::_Cpo::_Choice_edgl_ref._Strategy == _Edge_value::_Cpo::_St_ref::_Tuple_id); //static_assert(same_as())), double>); - static_assert(_source_target_id); + //static_assert(_source_target_id); static_assert(basic_sourced_edgelist); - static_assert(_has_edge_value); + static_assert(has_edge_value); } TEST_CASE("edgelist edge_descriptor test", "[edgelist][edge_descriptor]") { @@ -77,7 +77,7 @@ TEST_CASE("edgelist edge_descriptor test", "[edgelist][edge_descriptor]") { static_assert(!std::ranges::forward_range); //static_assert(_el_value); - static_assert(_el_basic_sourced_edge_desc); + static_assert(_detail::_el_basic_sourced_edge_desc); static_assert(_Target_id::_is_edge_desc); //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); @@ -89,7 +89,7 @@ TEST_CASE("edgelist edge_descriptor test", "[edgelist][edge_descriptor]") { static_assert(same_as())), int>); static_assert(same_as); - static_assert(_source_target_id); + //static_assert(_source_target_id); static_assert(basic_sourced_edgelist); } @@ -102,7 +102,7 @@ TEST_CASE("edgelist edge_descriptor test with value", "[edgelist][edge_descripto static_assert(!std::ranges::forward_range); //static_assert(_el_value); - static_assert(_el_basic_sourced_edge_desc); + static_assert(_detail::_el_basic_sourced_edge_desc); static_assert(_Target_id::_is_edge_desc); //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); @@ -116,7 +116,7 @@ TEST_CASE("edgelist edge_descriptor test with value", "[edgelist][edge_descripto static_assert(_Edge_value::_is_edge_desc); - static_assert(_source_target_id); + //static_assert(_source_target_id); static_assert(basic_sourced_edgelist); - static_assert(_has_edge_value); + static_assert(has_edge_value); } From f724e52ed257a5b50d1b4e3ad0ba3e073d0b5755 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Mon, 27 May 2024 13:28:42 -0400 Subject: [PATCH 4/5] Add runtime code examples in edgelist tests --- include/graph/edgelist.hpp | 4 +-- tests/edgelist_tests.cpp | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/include/graph/edgelist.hpp b/include/graph/edgelist.hpp index 63c8ffa..326c9f3 100644 --- a/include/graph/edgelist.hpp +++ b/include/graph/edgelist.hpp @@ -380,7 +380,7 @@ namespace _Edge_value { * @return The edge_value of the edge, when supported. */ template - //requires(_Choice_edgl_ref<_E>._Strategy != _St_ref::_None) + requires(_Choice_edgl_ref<_E>._Strategy != _St_ref::_None) [[nodiscard]] constexpr auto operator()(_E&& __e) const noexcept(_Choice_edgl_ref<_E>._No_throw) { constexpr _St_ref _Strat_ref = _Choice_edgl_ref<_E>._Strategy; @@ -419,7 +419,7 @@ template // For expo concept basic_sourced_index_edgelist = basic_sourced_edgelist && // requires(ranges::range_value_t e) { { source_id(e) } -> integral; - { target_id(e) } -> integral; + { target_id(e) } -> integral; // this is redundant, but makes it clear }; template // For exposition only diff --git a/tests/edgelist_tests.cpp b/tests/edgelist_tests.cpp index 4a5fcb4..822349d 100644 --- a/tests/edgelist_tests.cpp +++ b/tests/edgelist_tests.cpp @@ -8,6 +8,7 @@ using std::declval; using std::vector; using std::tuple; +using std::pair; using std::is_same_v; using std::same_as; using std::graph::edge_descriptor; @@ -16,6 +17,14 @@ using namespace std::graph::edgelist; TEST_CASE("edgelist tuple test", "[edgelist][tuple]") { using EL = vector>; using E = std::ranges::range_value_t; + + EL el{{1, 2}, {1, 4}, {2, 3}, {2, 4}}; + for (auto&& e : el) { + int sid = source_id(e); + int tid = target_id(e); + static_assert(!has_edge_value); + } + _Target_id::_Cpo cpo; E e; static_assert(same_as>); @@ -41,6 +50,13 @@ TEST_CASE("edgelist tuple test", "[edgelist][tuple]") { TEST_CASE("edgelist tuple test with value", "[edgelist][tuple]") { using EL = vector>; using E = std::ranges::range_value_t; + EL el{{1, 2, 11.1}, {1, 4, 22.2}, {2, 3, 3.33}, {2, 4, 4.44}}; + for (auto&& e : el) { + int sid = source_id(e); + int tid = target_id(e); + double val = edge_value(e); + } + _Target_id::_Cpo cpo; E e; static_assert(same_as>); @@ -68,9 +84,50 @@ TEST_CASE("edgelist tuple test with value", "[edgelist][tuple]") { static_assert(has_edge_value); } +TEST_CASE("edgelist pair test", "[edgelist][tuple]") { + using EL = vector>; + using E = std::ranges::range_value_t; + + EL el{{1, 2}, {1, 4}, {2, 3}, {2, 4}}; + for (auto&& e : el) { + int sid = source_id(e); + int tid = target_id(e); + static_assert(!has_edge_value); + } + + _Target_id::_Cpo cpo; + E e; + static_assert(same_as>); + + static_assert(!std::ranges::forward_range); + //static_assert(_el_value); + static_assert(_detail::_el_tuple_edge); + + static_assert(_Target_id::_is_tuple_edge); + //static_assert(_Target_id::_Cpo::_Choice_edgl_ref._Strategy == _Target_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + + static_assert(_Source_id::_is_tuple_edge); + //static_assert(_Source_id::_Cpo::_Choice_edgl_ref._Strategy == _Source_id::_Cpo::_St_ref::_Tuple_id); + static_assert(same_as())), int>); + static_assert(same_as); + + //static_assert(_source_target_id); + static_assert(basic_sourced_edgelist); +} + TEST_CASE("edgelist edge_descriptor test", "[edgelist][edge_descriptor]") { using EL = vector>; using E = std::ranges::range_value_t; + + EL el{{1, 2}, {1, 4}, {2, 3}, {2, 4}}; + for (auto&& e : el) { + int sid = source_id(e); + int tid = target_id(e); + static_assert(!has_edge_value); + } + _Target_id::_Cpo cpo; E e; static_assert(same_as>); @@ -96,6 +153,14 @@ TEST_CASE("edgelist edge_descriptor test", "[edgelist][edge_descriptor]") { TEST_CASE("edgelist edge_descriptor test with value", "[edgelist][edge_descriptor]") { using EL = vector>; using E = std::ranges::range_value_t; + + EL el{{1, 2, 11.1}, {1, 4, 22.2}, {2, 3, 3.33}, {2, 4, 4.44}}; + for (auto&& e : el) { + int sid = source_id(e); + int tid = target_id(e); + double val = edge_value(e); + } + _Target_id::_Cpo cpo; E e; static_assert(same_as>); From 266f59afb437ec0acd43eda9b4861e0af23c3689 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Mon, 27 May 2024 15:40:10 -0400 Subject: [PATCH 5/5] Add has_directed_edge type trait Remove "true" from concept that was leftover from debugging. --- include/graph/edgelist.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/graph/edgelist.hpp b/include/graph/edgelist.hpp index 326c9f3..69eef7c 100644 --- a/include/graph/edgelist.hpp +++ b/include/graph/edgelist.hpp @@ -335,9 +335,8 @@ namespace _Edge_value { concept _is_tuple_edge = _detail::_el_tuple_edge<_E> && (tuple_size_v<_E> >= 3); template - concept _is_edge_desc = _detail::_el_basic_sourced_edge_desc<_E> && requires(_E elv) { - true; - { elv.value }; //->same_as; + concept _is_edge_desc = _detail::_el_basic_sourced_edge_desc<_E> && requires(_E e) { + { e.value }; //->same_as; }; class _Cpo { @@ -428,6 +427,9 @@ concept has_edge_value = basic_sourced_edgelist && // { edge_value(e) }; }; +//template +//struct has_directed_edge = ...; + // (non-basic concepts imply inclusion of an edge reference which doesn't make much sense)