From b60e396fb16b536c4029cc2181d3fb0dbbfea4f7 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 11:23:03 +0200 Subject: [PATCH 001/256] env: add trompeloeil to dictionaryfeat: graph::concepts::vertex --- include/Simple-Utility/graph/Common.hpp | 22 ++++++++++++ tests/CMakeLists.txt | 1 + tests/graph/CMakeLists.txt | 5 +++ tests/graph/Common.cpp | 48 +++++++++++++++++++++++++ tests/graph/Defines.hpp | 12 +++++++ 5 files changed, 88 insertions(+) create mode 100644 include/Simple-Utility/graph/Common.hpp create mode 100644 tests/graph/CMakeLists.txt create mode 100644 tests/graph/Common.cpp create mode 100644 tests/graph/Defines.hpp diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp new file mode 100644 index 000000000..73f6e89e7 --- /dev/null +++ b/include/Simple-Utility/graph/Common.hpp @@ -0,0 +1,22 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_COMMON_HPP +#define SIMPLE_UTILITY_GRAPH_COMMON_HPP + +#pragma once + +#include +#include + +namespace sl::graph::concepts +{ + template + concept vertex = std::same_as> + && std::equality_comparable + && std::copyable; +} + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 936002559..23d0379c1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable( add_subdirectory("nullables") add_subdirectory("concepts") add_subdirectory("functional") +add_subdirectory("graph") target_link_libraries( Simple-Utility-Tests diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt new file mode 100644 index 000000000..1d1b8fa6b --- /dev/null +++ b/tests/graph/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources( + Simple-Utility-Tests + PRIVATE + "Common.cpp" +) diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp new file mode 100644 index 000000000..b53ddb0bb --- /dev/null +++ b/tests/graph/Common.cpp @@ -0,0 +1,48 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include + +#include "Defines.hpp" + +#include "Simple-Utility/graph/Common.hpp" + +namespace +{ + struct non_equality_comparable + { + bool operator ==(const non_equality_comparable&) const = delete; + auto operator <=>(const non_equality_comparable&) const = default; + }; + + struct non_copyable + { + non_copyable(const non_copyable&) = delete; + non_copyable& operator =(const non_copyable&) = delete; + auto operator <=>(const non_copyable&) const = default; + }; + + struct valid_vertex + { + friend bool operator==(const valid_vertex&, const valid_vertex&) = default; + }; +} + +TEMPLATE_TEST_CASE_SIG( + "graph::concepts::vertex determines whether the given type can be used as vertex type.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (true, int), + (true, float), + (true, valid_vertex), + (false, const int), + (false, int&), + (false, non_equality_comparable), + (false, non_copyable) +) +{ + STATIC_REQUIRE(expected == sg::concepts::vertex); +} + diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp new file mode 100644 index 000000000..b5b2648a0 --- /dev/null +++ b/tests/graph/Defines.hpp @@ -0,0 +1,12 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include + +#include + +namespace sg = sl::graph; From baa4acbee939520c24cd70b405e1c6145e53fff4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 11:23:45 +0200 Subject: [PATCH 002/256] env: add "trompeloeil" to dictionary --- Folder.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/Folder.DotSettings b/Folder.DotSettings index 90dda5ce4..6bd663a9b 100644 --- a/Folder.DotSettings +++ b/Folder.DotSettings @@ -98,4 +98,5 @@ Distributed under the Boost Software License, Version 1.0. True True True + True From 0fd3c4e82283a5d42bf8a4a6951ff030e4c12101 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 11:27:01 +0200 Subject: [PATCH 003/256] feat: graph::concepts::rank --- include/Simple-Utility/graph/Common.hpp | 11 +++++ tests/graph/Common.cpp | 65 ++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 73f6e89e7..e648e36ae 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -8,6 +8,8 @@ #pragma once +#include "Simple-Utility/concepts/operators.hpp" + #include #include @@ -17,6 +19,15 @@ namespace sl::graph::concepts concept vertex = std::same_as> && std::equality_comparable && std::copyable; + + template + concept rank = std::same_as> + && std::totally_ordered + && std::regular + && sl::concepts::plus + && sl::concepts::minus + && sl::concepts::plus_assign + && sl::concepts::minus_assign; } #endif diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index b53ddb0bb..ed029b447 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -9,6 +9,9 @@ #include "Simple-Utility/graph/Common.hpp" +// ReSharper disable CppClangTidyClangDiagnosticUnusedMemberFunction +// ReSharper disable CppClangTidyClangDiagnosticUnneededMemberFunction + namespace { struct non_equality_comparable @@ -26,7 +29,47 @@ namespace struct valid_vertex { - friend bool operator==(const valid_vertex&, const valid_vertex&) = default; + friend bool operator==(const valid_vertex&, const valid_vertex&) = default; // NOLINT(clang-diagnostic-unneeded-internal-declaration) + }; + + struct non_totally_ordered + { + bool operator ==(const non_totally_ordered&) const = default; + }; + + struct non_mutable_plus_rank + { + auto operator <=>(const non_mutable_plus_rank&) const = default; + non_mutable_plus_rank operator +([[maybe_unused]] const non_mutable_plus_rank&) const; + }; + + struct non_immutable_plus_rank + { + auto operator <=>(const non_immutable_plus_rank&) const = default; + + non_immutable_plus_rank& operator +=([[maybe_unused]] const non_immutable_plus_rank&); + }; + + struct non_mutable_minus_rank + { + auto operator <=>(const non_mutable_minus_rank&) const = default; + + non_mutable_minus_rank operator +([[maybe_unused]] const non_mutable_minus_rank&) const; + }; + + struct non_immutable_minus_rank + { + auto operator <=>(const non_immutable_minus_rank&) const = default; + non_immutable_minus_rank& operator +=([[maybe_unused]] const non_immutable_minus_rank&); + }; + + struct valid_rank + { + auto operator <=>(const valid_rank&) const = default; + valid_rank& operator +=([[maybe_unused]] const valid_rank&); + valid_rank operator +([[maybe_unused]] const valid_rank&) const; + valid_rank& operator -=([[maybe_unused]] const valid_rank&); + valid_rank operator -([[maybe_unused]] const valid_rank&) const; }; } @@ -46,3 +89,23 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == sg::concepts::vertex); } +TEMPLATE_TEST_CASE_SIG( + "graph::concepts::rank determines whether the given type can be used as rank type.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (true, int), + (true, float), + (true, valid_rank), + (false, const int), + (false, int&), + (false, non_equality_comparable), + (false, non_copyable), + (false, non_totally_ordered), + (false, non_mutable_plus_rank), + (false, non_immutable_plus_rank), + (false, non_mutable_minus_rank), + (false, non_immutable_minus_rank) +) +{ + STATIC_REQUIRE(expected == sg::concepts::rank); +} From 6924eb91904b769ee897ef8a75fbe3f10cecbbc4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 11:29:25 +0200 Subject: [PATCH 004/256] feat: add basic_feature_category, ranked_feature_category and common_feature_category trait --- include/Simple-Utility/graph/Common.hpp | 42 +++++++++++++++++++++++-- tests/graph/Common.cpp | 26 +++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index e648e36ae..325237545 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -8,13 +8,33 @@ #pragma once +#include "Simple-Utility/TypeList.hpp" #include "Simple-Utility/concepts/operators.hpp" +#include #include -#include + +namespace sl::graph +{ + struct basic_feature_category + { + }; + + struct ranked_feature_category + { + }; +} + +namespace sl::graph::detail +{ + using feature_category_list = type_list::TypeList; +} namespace sl::graph::concepts { + template + concept feature_category = type_list::contains_v; + template concept vertex = std::same_as> && std::equality_comparable @@ -29,5 +49,23 @@ namespace sl::graph::concepts && sl::concepts::plus_assign && sl::concepts::minus_assign; } - + +namespace sl::graph +{ + template + struct common_feature_category + { + using type = std::tuple_element_t< + std::ranges::min( + { + type_list::index_of_v, + type_list::index_of_v... + }), + detail::feature_category_list>; + }; + + template + using common_feature_category_t = typename common_feature_category::type; +} + #endif diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index ed029b447..2f679dc12 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -109,3 +109,29 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::rank); } + +TEMPLATE_TEST_CASE_SIG( + "graph::concepts::feature_category determines whether the given type denotes a feature category.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (false, int), + (true, sg::basic_feature_category), + (true, sg::ranked_feature_category) +) +{ + STATIC_REQUIRE(expected == sg::concepts::feature_category); +} + +TEMPLATE_TEST_CASE_SIG( + "graph::common_feature_category trait yields the strictest category.", + "[graph][graph::traits]", + ((bool dummy, class Expected, class... Ts), dummy, Expected, Ts...), + (true, sg::basic_feature_category, sg::basic_feature_category), + (true, sg::ranked_feature_category, sg::ranked_feature_category), + (true, sg::basic_feature_category, sg::ranked_feature_category, sg::basic_feature_category), + (true, sg::basic_feature_category, sg::ranked_feature_category, sg::basic_feature_category, sg::ranked_feature_category) +) +{ + STATIC_REQUIRE(std::same_as::type>); + STATIC_REQUIRE(std::same_as>); +} From b0c76fb0578067e6cead6eb7af738acfebbe0fc2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 12:27:10 +0200 Subject: [PATCH 005/256] fixfeat: conepts::readable_vertex_type --- include/Simple-Utility/graph/Common.hpp | 4 +++ tests/graph/Common.cpp | 36 +++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 325237545..5d2342345 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -48,6 +48,10 @@ namespace sl::graph::concepts && sl::concepts::minus && sl::concepts::plus_assign && sl::concepts::minus_assign; + + template + concept readable_vertex_type = requires { typename T::vertex_type; } + && vertex; } namespace sl::graph diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 2f679dc12..b58197350 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -9,6 +9,8 @@ #include "Simple-Utility/graph/Common.hpp" +// ReSharper disable CppDeclaratorNeverUsed +// ReSharper disable CppTypeAliasNeverUsed // ReSharper disable CppClangTidyClangDiagnosticUnusedMemberFunction // ReSharper disable CppClangTidyClangDiagnosticUnneededMemberFunction @@ -20,7 +22,7 @@ namespace auto operator <=>(const non_equality_comparable&) const = default; }; - struct non_copyable + struct non_copyable // NOLINT(cppcoreguidelines-special-member-functions) { non_copyable(const non_copyable&) = delete; non_copyable& operator =(const non_copyable&) = delete; @@ -29,7 +31,8 @@ namespace struct valid_vertex { - friend bool operator==(const valid_vertex&, const valid_vertex&) = default; // NOLINT(clang-diagnostic-unneeded-internal-declaration) + friend bool operator==(const valid_vertex&, const valid_vertex&) = default; + // NOLINT(clang-diagnostic-unneeded-internal-declaration) }; struct non_totally_ordered @@ -135,3 +138,32 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(std::same_as::type>); STATIC_REQUIRE(std::same_as>); } + +namespace +{ + struct non_readable_vertex_type + { + }; + + struct readable_but_unsatisfied_vertex_type + { + using vertex_type = non_equality_comparable; + }; + + struct readable_vertex_type + { + using vertex_type = valid_vertex; + }; +} + +TEMPLATE_TEST_CASE_SIG( + "graph::concepts::readable_vertex_type determines whether T contains a \"vertex_type\" member alias.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (false, non_readable_vertex_type), + (false, readable_but_unsatisfied_vertex_type), + (true, readable_vertex_type) +) +{ + STATIC_REQUIRE(expected == sg::concepts::readable_vertex_type); +} From f2116133e0cbd901146c4da1941a21078eaf9413 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 12:38:09 +0200 Subject: [PATCH 006/256] feat: add readable_rank_type --- include/Simple-Utility/graph/Common.hpp | 4 ++++ tests/graph/Common.cpp | 26 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 5d2342345..bd77a3a4b 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -52,6 +52,10 @@ namespace sl::graph::concepts template concept readable_vertex_type = requires { typename T::vertex_type; } && vertex; + + template + concept readable_rank_type = requires { typename T::rank_type; } + && vertex; } namespace sl::graph diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index b58197350..862250f1a 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -154,6 +154,20 @@ namespace { using vertex_type = valid_vertex; }; + + struct non_readable_rank_type + { + }; + + struct readable_but_unsatisfied_rank_type + { + using vertex_type = non_totally_ordered; + }; + + struct readable_rank_type + { + using rank_type = valid_rank; + }; } TEMPLATE_TEST_CASE_SIG( @@ -167,3 +181,15 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::readable_vertex_type); } + +TEMPLATE_TEST_CASE_SIG( + "graph::concepts::readable_rank_type determines whether T contains a \"rank_type\" member alias.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (false, non_readable_rank_type), + (false, readable_but_unsatisfied_rank_type), + (true, readable_rank_type) +) +{ + STATIC_REQUIRE(expected == sg::concepts::readable_rank_type); +} From 626813acfdfc3e0a22590f20acfb5af886b1da36 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 12:54:17 +0200 Subject: [PATCH 007/256] feat: feature_traits and convenient member aliases --- include/Simple-Utility/graph/Common.hpp | 30 +++++++++++ tests/graph/Common.cpp | 66 +++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index bd77a3a4b..69fe998a5 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -74,6 +74,36 @@ namespace sl::graph template using common_feature_category_t = typename common_feature_category::type; + + template + struct feature_traits; + + template + using feature_category_t = typename feature_traits::category_type; + + template + using feature_vertex_t = typename feature_traits::vertex_type; + + template + using feature_rank_t = typename feature_traits::rank_type; + + template + requires concepts::readable_vertex_type + struct feature_traits + { + using category_type = basic_feature_category; + using vertex_type = typename T::vertex_type; + }; + + template + requires concepts::readable_vertex_type + && concepts::readable_rank_type + struct feature_traits + { + using category_type = ranked_feature_category; + using vertex_type = typename T::vertex_type; + using rank_type = typename T::rank_type; + }; } #endif diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 862250f1a..76ff5de03 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -168,8 +168,26 @@ namespace { using rank_type = valid_rank; }; + + struct ranked_feature_category_type + { + using vertex_type = valid_vertex; + using rank_type = valid_rank; + }; + + struct type_with_custom_trait + { + }; } +template <> +struct sg::feature_traits +{ + using category_type = ranked_feature_category; + using vertex_type = int; + using rank_type = float; +}; + TEMPLATE_TEST_CASE_SIG( "graph::concepts::readable_vertex_type determines whether T contains a \"vertex_type\" member alias.", "[graph][graph::concepts]", @@ -193,3 +211,51 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::readable_rank_type); } + +TEST_CASE( + "graph::feature_traits categorizes T as basic_feature_category, if just vertex_type is readable.", + "[graph][graph::traits]" +) +{ + using TestType = readable_vertex_type; + + STATIC_REQUIRE(std::same_as::category_type>); + STATIC_REQUIRE(std::same_as>); + + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEST_CASE( + "graph::feature_traits categorizes T as ranked_feature_category, if vertex_type and rank_type are readable.", + "[graph][graph::traits]" +) +{ + using TestType = ranked_feature_category_type; + + STATIC_REQUIRE(std::same_as::category_type>); + STATIC_REQUIRE(std::same_as>); + + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); + + STATIC_REQUIRE(std::same_as::rank_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEST_CASE( + "graph::feature_traits can be specialized.", + "[graph][graph::traits]" +) +{ + using TestType = type_with_custom_trait; + + STATIC_REQUIRE(std::same_as::category_type>); + STATIC_REQUIRE(std::same_as>); + + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); + + STATIC_REQUIRE(std::same_as::rank_type>); + STATIC_REQUIRE(std::same_as>); +} From 7798be676f68dd99e3bd0be8502ca1034bc10c3f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 12:57:52 +0200 Subject: [PATCH 008/256] fix: please gcc and clang --- tests/graph/Common.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 76ff5de03..342af241e 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -128,15 +128,15 @@ TEMPLATE_TEST_CASE_SIG( TEMPLATE_TEST_CASE_SIG( "graph::common_feature_category trait yields the strictest category.", "[graph][graph::traits]", - ((bool dummy, class Expected, class... Ts), dummy, Expected, Ts...), + ((bool dummy, class Expected, class T, class... Others), dummy, Expected, T, Others...), (true, sg::basic_feature_category, sg::basic_feature_category), (true, sg::ranked_feature_category, sg::ranked_feature_category), (true, sg::basic_feature_category, sg::ranked_feature_category, sg::basic_feature_category), (true, sg::basic_feature_category, sg::ranked_feature_category, sg::basic_feature_category, sg::ranked_feature_category) ) { - STATIC_REQUIRE(std::same_as::type>); - STATIC_REQUIRE(std::same_as>); + STATIC_REQUIRE(std::same_as::type>); + STATIC_REQUIRE(std::same_as>); } namespace From 298dbab9fa22e21c2f510358aa2fd6ecb6d59ce3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 12:59:53 +0200 Subject: [PATCH 009/256] fix: disable CppClangTidyClangDiagnosticUnneededInternalDeclaration for graph/Common.cpp --- tests/graph/Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 342af241e..0177db5bb 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -13,6 +13,7 @@ // ReSharper disable CppTypeAliasNeverUsed // ReSharper disable CppClangTidyClangDiagnosticUnusedMemberFunction // ReSharper disable CppClangTidyClangDiagnosticUnneededMemberFunction +// ReSharper disable CppClangTidyClangDiagnosticUnneededInternalDeclaration namespace { @@ -32,7 +33,6 @@ namespace struct valid_vertex { friend bool operator==(const valid_vertex&, const valid_vertex&) = default; - // NOLINT(clang-diagnostic-unneeded-internal-declaration) }; struct non_totally_ordered From 749d84982ee1c0e2a81394f6f0175878ad0e35c7 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 19:59:45 +0200 Subject: [PATCH 010/256] fix: please clang --- CMakeSettings.json | 5 +++++ include/Simple-Utility/graph/Common.hpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index bc058be69..5cff49154 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -77,6 +77,11 @@ "name": "CATCH_BUILD_TESTING", "value": "False", "type": "BOOL" + }, + { + "name": "CMAKE_C_COMPILER_WORKS", + "value": "True", + "type": "BOOL" } ], "remoteMachineName": "${defaultRemoteMachineName}", diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 69fe998a5..c77757ef3 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -64,7 +64,7 @@ namespace sl::graph struct common_feature_category { using type = std::tuple_element_t< - std::ranges::min( + std::min( // clang seems to have issues with std::ranges::min { type_list::index_of_v, type_list::index_of_v... From 62cb508bb82c7845561bb9d8f0b4d92b58045699 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 21:53:21 +0200 Subject: [PATCH 011/256] feat: concepts::not_void --- include/Simple-Utility/concepts/stl_extensions.hpp | 9 +++++++++ tests/concepts/stl_extensions.cpp | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/Simple-Utility/concepts/stl_extensions.hpp b/include/Simple-Utility/concepts/stl_extensions.hpp index a542f3e7f..6c0e3ec11 100644 --- a/include/Simple-Utility/concepts/stl_extensions.hpp +++ b/include/Simple-Utility/concepts/stl_extensions.hpp @@ -68,6 +68,15 @@ namespace sl::concepts template concept not_same_as = !std::same_as; + /** + * \brief Checks whether T is not ``void``. + * \details This is the inverted counterpart of ``std::is_void_v`` trait. + * \see https://en.cppreference.com/w/cpp/types/is_void + * \tparam T Type to check. + */ + template + concept not_void = !std::is_void_v; + /** * \brief Checks whether the target type is constructible from the source type. * \details This is the symmetrical counterpart of ``std::constructible_from`` concept with a single constructor argument. diff --git a/tests/concepts/stl_extensions.cpp b/tests/concepts/stl_extensions.cpp index eba672518..ff193cd89 100644 --- a/tests/concepts/stl_extensions.cpp +++ b/tests/concepts/stl_extensions.cpp @@ -92,6 +92,19 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(not_same_as == VExpected); } +TEMPLATE_TEST_CASE_SIG( + "not_void should behave as the inverted counterpart of std::is_void(_v).", + "[concepts][stl_ext]", + ((bool expected, class T), expected, T), + (false, void), + (false, const void), + (true, int), + (true, int&) +) +{ + STATIC_REQUIRE(expected == not_void); +} + TEMPLATE_TEST_CASE_SIG( "weakly_equality_comparable checks whether the given type is comparable via operator == and !=.", "[concepts][stl_ext]", From 28ca5ec7bee2b22efb307a7d89d8c78c71cc5c5b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 22:34:08 +0200 Subject: [PATCH 012/256] feat: graph::node::vertex customization point --- include/Simple-Utility/graph/Node.hpp | 41 ++++++++++++++++++ tests/graph/CMakeLists.txt | 1 + tests/graph/Node.cpp | 61 +++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 include/Simple-Utility/graph/Node.hpp create mode 100644 tests/graph/Node.cpp diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp new file mode 100644 index 000000000..4b1b23190 --- /dev/null +++ b/include/Simple-Utility/graph/Node.hpp @@ -0,0 +1,41 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_NODE_HPP +#define SIMPLE_UTILITY_GRAPH_NODE_HPP + +#include "Simple-Utility/concepts/stl_extensions.hpp" +#include "Simple-Utility/graph/Common.hpp" + +namespace sl::graph::node::detail +{ + struct vertex_fn + { + constexpr auto& operator ()(const auto& node) const noexcept + requires concepts::vertex> + { + return node.vertex; + } + + constexpr decltype(auto) operator ()(const auto& node) const noexcept + requires concepts::vertex> + { + return node.vertex(); + } + + constexpr decltype(auto) operator ()(const auto& node) const noexcept + requires concepts::vertex> + { + return vertex(node); + } + }; +} + +namespace sl::graph::node +{ + inline constexpr detail::vertex_fn vertex{}; +} + +#endif diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 1d1b8fa6b..7edfad007 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -2,4 +2,5 @@ target_sources( Simple-Utility-Tests PRIVATE "Common.cpp" + "Node.cpp" ) diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp new file mode 100644 index 000000000..60eaf9e42 --- /dev/null +++ b/tests/graph/Node.cpp @@ -0,0 +1,61 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/Node.hpp" + +#include +#include +#include + +#include "Defines.hpp" + +namespace +{ + struct member_vertex + { + int vertex; + }; + + struct member_fun_vertex + { + MAKE_CONST_MOCK0(vertex, int()); + }; + + struct free_fun_vertex + { + MAKE_CONST_MOCK0(my_vertex, int()); + + friend int vertex(const free_fun_vertex& v) + { + return v.my_vertex(); + } + }; +} + +TEST_CASE("graph::node::vertex serves as a customization point accessing the node vertex.", "[graph][graph::node]") +{ + const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); + + SECTION("Access via the vertex member.") + { + REQUIRE(expected == sg::node::vertex(member_vertex{expected})); + } + + SECTION("Access via the vertex member function.") + { + member_fun_vertex mock{}; + REQUIRE_CALL(mock, vertex()) + .RETURN(expected); + REQUIRE(expected == sg::node::vertex(std::as_const(mock))); + } + + SECTION("Access via the vertex free function.") + { + free_fun_vertex mock{}; + REQUIRE_CALL(mock, my_vertex()) + .RETURN(expected); + REQUIRE(expected == sg::node::vertex(std::as_const(mock))); + } +} From 54105531915f24d54b3531f9007d48a7ff1a655e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 22:40:28 +0200 Subject: [PATCH 013/256] feat: concepts::node --- include/Simple-Utility/graph/Node.hpp | 13 +++++++++++++ tests/graph/Defines.hpp | 17 +++++++++++++++++ tests/graph/Node.cpp | 14 ++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 4b1b23190..635af53b4 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -38,4 +38,17 @@ namespace sl::graph::node inline constexpr detail::vertex_fn vertex{}; } +namespace sl::graph::concepts +{ + template + concept node = sl::concepts::unqualified + && std::copyable + && std::destructible + && feature_category::category_type> + && vertex::vertex_type> + && requires(const T& node) + { + requires concepts::vertex>; + }; +} #endif diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index b5b2648a0..7d53a98c0 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -5,8 +5,25 @@ #pragma once +#include #include #include namespace sg = sl::graph; + +template +struct BasicTestNode +{ + using vertex_type = Vertex; + + struct Mock + { + MAKE_CONST_MOCK1(vertex, vertex_type(const BasicTestNode&)); + } inline static mock{}; + + friend constexpr vertex_type vertex(const BasicTestNode& node) + { + return mock.vertex(node); + } +}; diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 60eaf9e42..535c72d35 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -5,6 +5,7 @@ #include "Simple-Utility/graph/Node.hpp" +#include #include #include #include @@ -59,3 +60,16 @@ TEST_CASE("graph::node::vertex serves as a customization point accessing the nod REQUIRE(expected == sg::node::vertex(std::as_const(mock))); } } + +TEMPLATE_TEST_CASE_SIG( + "concepts::node determines, whether the given type satisfies the node requirements.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (false, member_vertex), + (false, member_fun_vertex), + (false, free_fun_vertex), + (true, BasicTestNode) +) +{ + STATIC_REQUIRE(expected == sg::concepts::node); +} From 6f99df93dfa35004180b1b81a2afef652357a4e0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 22:55:25 +0200 Subject: [PATCH 014/256] feat: queue::empty --- include/Simple-Utility/graph/Queue.hpp | 37 ++++++++++++++++++ tests/graph/CMakeLists.txt | 1 + tests/graph/Queue.cpp | 52 ++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 include/Simple-Utility/graph/Queue.hpp create mode 100644 tests/graph/Queue.cpp diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp new file mode 100644 index 000000000..58ca26e95 --- /dev/null +++ b/include/Simple-Utility/graph/Queue.hpp @@ -0,0 +1,37 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_QUEUE_HPP +#define SIMPLE_UTILITY_GRAPH_QUEUE_HPP + +#include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Node.hpp" + +namespace sl::graph::queue::detail +{ + struct empty_fn + { + template + requires requires(const T& t) { { t.empty() } -> std::convertible_to; } + constexpr bool operator ()(const T& queue) const noexcept(noexcept(queue.empty())) + { + return queue.empty(); + } + + template + requires requires(const T& t) { { empty(t) } -> std::convertible_to; } + constexpr decltype(auto) operator ()(const T& queue) const noexcept(noexcept(empty(queue))) + { + return empty(queue); + } + }; +} + +namespace sl::graph::queue +{ + inline constexpr detail::empty_fn empty{}; +} + +#endif diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 7edfad007..9d49774c2 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -3,4 +3,5 @@ target_sources( PRIVATE "Common.cpp" "Node.cpp" + "Queue.cpp" ) diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp new file mode 100644 index 000000000..1fc4c35db --- /dev/null +++ b/tests/graph/Queue.cpp @@ -0,0 +1,52 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/Queue.hpp" + +#include +#include +#include +#include + +#include "Defines.hpp" + +namespace +{ + struct member_fun_empty + { + MAKE_CONST_MOCK0(empty, bool()); + }; + + struct free_fun_empty + { + MAKE_CONST_MOCK0(is_empty, bool()); + + friend bool empty(const free_fun_empty& obj) + { + return obj.is_empty(); + } + }; +} + +TEST_CASE("graph::queue::empty serves as a customization point, detmerining whether the queue is empty.", "[graph][graph::queue]") +{ + const bool expected = GENERATE(false, true); + + SECTION("Access via the member function.") + { + member_fun_empty mock{}; + REQUIRE_CALL(mock, empty()) + .RETURN(expected); + REQUIRE(expected == sg::queue::empty(std::as_const(mock))); + } + + SECTION("Access via the free function.") + { + free_fun_empty mock{}; + REQUIRE_CALL(mock, is_empty()) + .RETURN(expected); + REQUIRE(expected == sg::queue::empty(std::as_const(mock))); + } +} From 473549e8183168b8c06590ee76feab52ad6d8f29 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 23:49:30 +0200 Subject: [PATCH 015/256] feat: graph::queue::insert --- include/Simple-Utility/graph/Queue.hpp | 27 +++++++++++++++ tests/graph/Queue.cpp | 48 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 58ca26e95..b99c64795 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -27,11 +27,38 @@ namespace sl::graph::queue::detail return empty(queue); } }; + + struct insert_fn + { + template + requires concepts::node> + && requires(Queue& queue) { queue.insert(std::declval()); } + constexpr void operator ()( + Queue& container, + Range&& elements + ) const noexcept(noexcept(container.insert(std::forward(elements)))) + { + container.insert(std::forward(elements)); + } + + template + requires concepts::node> + && requires(Queue& queue) { insert(queue, std::declval()); } + constexpr void operator ()( + Queue& container, + Range&& elements + ) const noexcept(noexcept(insert(container, std::forward(elements)))) + { + insert(container, std::forward(elements)); + } + }; + } namespace sl::graph::queue { inline constexpr detail::empty_fn empty{}; + inline constexpr detail::insert_fn insert{}; } #endif diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 1fc4c35db..0249182da 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -28,6 +28,31 @@ namespace return obj.is_empty(); } }; + + struct TestNode + { + using vertex_type = int; + int vertex{}; + + friend bool operator==(const TestNode&, const TestNode&) = default; + }; + + struct member_fun_insert + { + MAKE_MOCK1(insert, void(std::vector)); + }; + + struct free_fun_insert + { + MAKE_MOCK1(do_insert, void(std::vector)); + + friend void insert(free_fun_insert& obj, std::vector input) + { + obj.do_insert(std::move(input)); +} + }; + +} } TEST_CASE("graph::queue::empty serves as a customization point, detmerining whether the queue is empty.", "[graph][graph::queue]") @@ -50,3 +75,26 @@ TEST_CASE("graph::queue::empty serves as a customization point, detmerining whet REQUIRE(expected == sg::queue::empty(std::as_const(mock))); } } + +TEST_CASE("graph::queue::insert serves as a customization point, inserting the range elements.", "[graph][graph::queue]") +{ + const auto& expected = GENERATE( + std::vector{}, + (std::vector{{42}, {1337} })); + + SECTION("Access via the member function.") + { + member_fun_insert mock{}; + REQUIRE_CALL(mock, insert(expected)); + + sg::queue::insert(mock, expected); + } + + SECTION("Access via the free function.") + { + free_fun_insert mock{}; + REQUIRE_CALL(mock, do_insert(expected)); + + sg::queue::insert(mock, expected); + } +} From 035633f2f1014424a1e7bf48ed7fba4336b8ada0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 23:50:11 +0200 Subject: [PATCH 016/256] feat: graph::queue::next --- include/Simple-Utility/graph/Queue.hpp | 19 ++++++++++++ tests/graph/Queue.cpp | 40 ++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index b99c64795..8c46c79b5 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -9,6 +9,8 @@ #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Node.hpp" +#include + namespace sl::graph::queue::detail { struct empty_fn @@ -53,12 +55,29 @@ namespace sl::graph::queue::detail } }; + struct next_fn + { + template + requires concepts::node().next())>> + constexpr decltype(auto) operator ()(Queue& queue) const noexcept(noexcept(queue.next())) + { + return queue.next(); + } + + template + requires concepts::node()))>> + constexpr decltype(auto) operator ()(Queue& queue) const noexcept(noexcept(next(queue))) + { + return next(queue); + } + }; } namespace sl::graph::queue { inline constexpr detail::empty_fn empty{}; inline constexpr detail::insert_fn insert{}; + inline constexpr detail::next_fn next{}; } #endif diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 0249182da..51aa04995 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -49,10 +49,23 @@ namespace friend void insert(free_fun_insert& obj, std::vector input) { obj.do_insert(std::move(input)); -} + } }; -} + struct member_fun_next + { + MAKE_MOCK0(next, TestNode()); + }; + + struct free_fun_next + { + MAKE_MOCK0(get_next, TestNode()); + + friend TestNode next(free_fun_next& obj) + { + return obj.get_next(); + } + }; } TEST_CASE("graph::queue::empty serves as a customization point, detmerining whether the queue is empty.", "[graph][graph::queue]") @@ -98,3 +111,26 @@ TEST_CASE("graph::queue::insert serves as a customization point, inserting the r sg::queue::insert(mock, expected); } } + +TEST_CASE("graph::queue::next serves as a customization point, retrieving the next node.", "[graph][graph::queue]") +{ + const auto expected = GENERATE(as{}, 42, 1337); + + SECTION("Access via the member function.") + { + member_fun_next mock{}; + REQUIRE_CALL(mock, next()) + .RETURN(expected); + + REQUIRE(expected == sg::queue::next(mock)); + } + + SECTION("Access via the free function.") + { + free_fun_next mock{}; + REQUIRE_CALL(mock, get_next()) + .RETURN(expected); + + REQUIRE(expected == sg::queue::next(mock)); + } +} From 23b18359102bea50eb9831af2c04bd59f881f92e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 20 Aug 2023 23:56:22 +0200 Subject: [PATCH 017/256] fix: explicitly disable some linter messages --- tests/graph/Node.cpp | 2 ++ tests/graph/Queue.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 535c72d35..be5b6a291 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -16,6 +16,7 @@ namespace { struct member_vertex { + // ReSharper disable once CppDeclaratorNeverUsed int vertex; }; @@ -28,6 +29,7 @@ namespace { MAKE_CONST_MOCK0(my_vertex, int()); + // ReSharper disable once CppDeclaratorNeverUsed friend int vertex(const free_fun_vertex& v) { return v.my_vertex(); diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 51aa04995..3a7f48acb 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -12,6 +12,8 @@ #include "Defines.hpp" +// ReSharper disable CppDeclaratorNeverUsed + namespace { struct member_fun_empty @@ -31,6 +33,7 @@ namespace struct TestNode { + // ReSharper disable once CppTypeAliasNeverUsed using vertex_type = int; int vertex{}; From 3952de820c6e6d27724a04a8d88b5a28bb890862 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 00:00:12 +0200 Subject: [PATCH 018/256] feat: graph::concepts::queue_for --- include/Simple-Utility/graph/Queue.hpp | 43 ++++++++++++++++++++++++++ tests/graph/Queue.cpp | 17 ++++++++++ 2 files changed, 60 insertions(+) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 8c46c79b5..280f05c23 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -71,6 +71,36 @@ namespace sl::graph::queue::detail return next(queue); } }; + + /* This defines a dummy input_iterator, which we need to declare a input_range, which is then used + * to validate the graph::insert conformity in the queue_for concept. + */ + template + struct input_range_maker + { + struct input_iterator + { + using iterator_concept = std::input_iterator_tag; + using element_type = T; + using difference_type = std::ptrdiff_t; + + // ReSharper disable once CppFunctionIsNotImplemented + T& operator *() const; + // ReSharper disable once CppFunctionIsNotImplemented + input_iterator& operator ++(); + // ReSharper disable once CppFunctionIsNotImplemented + void operator ++(int); + bool operator==(const input_iterator&) const = default; + }; + + static_assert(std::input_iterator); + static_assert(!std::forward_iterator); + + using type = std::ranges::subrange; + }; + + template + using dummy_input_range = typename input_range_maker::type; } namespace sl::graph::queue @@ -80,4 +110,17 @@ namespace sl::graph::queue inline constexpr detail::next_fn next{}; } +namespace sl::graph::concepts +{ + template + concept queue_for = sl::concepts::unqualified + && node + && requires(T& queue) + { + { queue::empty(std::as_const(queue)) } -> std::convertible_to; + queue::insert(queue, std::declval&>()); + { queue::next(queue) } -> std::convertible_to; + }; +} + #endif diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 3a7f48acb..e109a32cf 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -137,3 +137,20 @@ TEST_CASE("graph::queue::next serves as a customization point, retrieving the ne REQUIRE(expected == sg::queue::next(mock)); } } + +TEMPLATE_TEST_CASE_SIG( + "concepts::queue_for determines, whether the given type satisfies the requirements of a queue for the specified node type.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (false, member_fun_empty), + (false, free_fun_empty), + (false, member_fun_insert), + (false, free_fun_insert), + (false, member_fun_next), + (false, free_fun_next), + (false, QueueMock>), + (true, QueueMock) +) +{ + STATIC_REQUIRE(expected == sg::concepts::queue_for); +} From 873f2907d3a34d272e75d3d4117ddb15e678dc61 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 00:11:26 +0200 Subject: [PATCH 019/256] fix: please many compilers --- include/Simple-Utility/graph/Queue.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 280f05c23..463eaa3d9 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -84,8 +84,9 @@ namespace sl::graph::queue::detail using element_type = T; using difference_type = std::ptrdiff_t; + T* dummy{}; // ReSharper disable once CppFunctionIsNotImplemented - T& operator *() const; + T& operator *() const { return *dummy; } // ReSharper disable once CppFunctionIsNotImplemented input_iterator& operator ++(); // ReSharper disable once CppFunctionIsNotImplemented From 295cc04d8b6056fd6d8803f45a14703855c592ae Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 00:35:08 +0200 Subject: [PATCH 020/256] fix: please gcc --- include/Simple-Utility/graph/Queue.hpp | 37 ++++++++++++++++++++++---- tests/graph/Defines.hpp | 21 +++++++++++++++ tests/graph/Queue.cpp | 2 +- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 463eaa3d9..26576e8b4 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -71,7 +71,19 @@ namespace sl::graph::queue::detail return next(queue); } }; +} + +namespace sl::graph::queue +{ + inline constexpr detail::empty_fn empty{}; + inline constexpr detail::insert_fn insert{}; + inline constexpr detail::next_fn next{}; +} + +#if __cpp_lib_ranges >= 201911L +namespace sl::graph::queue::detail +{ /* This defines a dummy input_iterator, which we need to declare a input_range, which is then used * to validate the graph::insert conformity in the queue_for concept. */ @@ -102,15 +114,30 @@ namespace sl::graph::queue::detail template using dummy_input_range = typename input_range_maker::type; + + template + concept has_insert = requires(T& queue) + { + queue::insert(queue, std::declval&>()); + }; + } -namespace sl::graph::queue +#else + +#include + +namespace sl::graph::queue::detail { - inline constexpr detail::empty_fn empty{}; - inline constexpr detail::insert_fn insert{}; - inline constexpr detail::next_fn next{}; + template + concept has_insert = requires(T& queue) + { + queue::insert(queue, std::declval>()); + }; } +#endif + namespace sl::graph::concepts { template @@ -119,7 +146,7 @@ namespace sl::graph::concepts && requires(T& queue) { { queue::empty(std::as_const(queue)) } -> std::convertible_to; - queue::insert(queue, std::declval&>()); + requires queue::detail::has_insert; { queue::next(queue) } -> std::convertible_to; }; } diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 7d53a98c0..61b0ff3b2 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -27,3 +27,24 @@ struct BasicTestNode return mock.vertex(node); } }; + +template +class QueueMock +{ +public: + inline static constexpr bool trompeloeil_movable_mock = true; + + MAKE_CONST_MOCK0(empty, bool()); + // Can't expect a template here, so just expect a std::vector + MAKE_MOCK1(insert, void(std::vector)); + MAKE_MOCK0(next, Node()); + + template + requires std::convertible_to, Node> + friend constexpr void insert(QueueMock& queue, Range&& range) + { + std::vector> vector{}; + std::ranges::copy(range, std::back_inserter(vector)); + queue.insert(std::move(vector)); + } +}; diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index e109a32cf..9a1a4f8c8 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -117,7 +117,7 @@ TEST_CASE("graph::queue::insert serves as a customization point, inserting the r TEST_CASE("graph::queue::next serves as a customization point, retrieving the next node.", "[graph][graph::queue]") { - const auto expected = GENERATE(as{}, 42, 1337); + const auto expected = GENERATE(TestNode{42}, TestNode{1337}); SECTION("Access via the member function.") { From 9991ffe4f42493ffbee2300261251fcbbdceebc1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 01:11:25 +0200 Subject: [PATCH 021/256] fix: add missing include --- tests/graph/Defines.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 61b0ff3b2..ceb9b5a31 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -9,6 +9,7 @@ #include #include +#include namespace sg = sl::graph; From 052682a0495bb98141a2419adcf8c4660d26b47b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 01:35:20 +0200 Subject: [PATCH 022/256] fix: please msvc v142 toolset --- include/Simple-Utility/graph/Queue.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 26576e8b4..b9ef4fbe0 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -118,7 +118,7 @@ namespace sl::graph::queue::detail template concept has_insert = requires(T& queue) { - queue::insert(queue, std::declval&>()); + queue::insert(queue, std::declval&>()); }; } @@ -143,10 +143,10 @@ namespace sl::graph::concepts template concept queue_for = sl::concepts::unqualified && node + && queue::detail::has_insert && requires(T& queue) { { queue::empty(std::as_const(queue)) } -> std::convertible_to; - requires queue::detail::has_insert; { queue::next(queue) } -> std::convertible_to; }; } From 00607a5db530a652ebc97031e33f5ccf2749856e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 03:09:35 +0200 Subject: [PATCH 023/256] fix: finally please clang --- include/Simple-Utility/graph/Queue.hpp | 36 ++++++++++---------------- tests/graph/Defines.hpp | 4 +-- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index b9ef4fbe0..84dd31829 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -80,7 +80,17 @@ namespace sl::graph::queue inline constexpr detail::next_fn next{}; } -#if __cpp_lib_ranges >= 201911L +#if __clang__ && __clang_major__ < 16 + +#include + +namespace sl::graph::queue::detail +{ + template + using dummy_input_range = std::forward_list; +} + +#else namespace sl::graph::queue::detail { @@ -114,26 +124,6 @@ namespace sl::graph::queue::detail template using dummy_input_range = typename input_range_maker::type; - - template - concept has_insert = requires(T& queue) - { - queue::insert(queue, std::declval&>()); - }; - -} - -#else - -#include - -namespace sl::graph::queue::detail -{ - template - concept has_insert = requires(T& queue) - { - queue::insert(queue, std::declval>()); - }; } #endif @@ -143,10 +133,10 @@ namespace sl::graph::concepts template concept queue_for = sl::concepts::unqualified && node - && queue::detail::has_insert - && requires(T& queue) + && requires(T& queue, queue::detail::dummy_input_range inputRange) { { queue::empty(std::as_const(queue)) } -> std::convertible_to; + queue::insert(queue, inputRange); { queue::next(queue) } -> std::convertible_to; }; } diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index ceb9b5a31..199fee62f 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -37,7 +37,7 @@ class QueueMock MAKE_CONST_MOCK0(empty, bool()); // Can't expect a template here, so just expect a std::vector - MAKE_MOCK1(insert, void(std::vector)); + MAKE_MOCK1(do_insert, void(std::vector)); MAKE_MOCK0(next, Node()); template @@ -46,6 +46,6 @@ class QueueMock { std::vector> vector{}; std::ranges::copy(range, std::back_inserter(vector)); - queue.insert(std::move(vector)); + queue.do_insert(std::move(vector)); } }; From 3cdd8b3ad2127f8fe54b7c982e159085678d9e5b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 03:24:37 +0200 Subject: [PATCH 024/256] fix: finally please msvc toolset v142 (again) --- include/Simple-Utility/graph/Queue.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 84dd31829..d9ef7455d 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -107,8 +107,10 @@ namespace sl::graph::queue::detail using difference_type = std::ptrdiff_t; T* dummy{}; + // ReSharper disable once CppFunctionIsNotImplemented T& operator *() const { return *dummy; } + // ReSharper disable once CppFunctionIsNotImplemented input_iterator& operator ++(); // ReSharper disable once CppFunctionIsNotImplemented @@ -133,7 +135,8 @@ namespace sl::graph::concepts template concept queue_for = sl::concepts::unqualified && node - && requires(T& queue, queue::detail::dummy_input_range inputRange) + // ReSharper disable once CppRedundantTemplateKeyword + && requires(T& queue, queue::detail::template dummy_input_range inputRange) { { queue::empty(std::as_const(queue)) } -> std::convertible_to; queue::insert(queue, inputRange); From d205d262bdb1bf45fe25fd1433f3a176baf5fd37 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 04:11:15 +0200 Subject: [PATCH 025/256] feat: graph::tracker::set_discovered --- include/Simple-Utility/graph/Tracker.hpp | 40 ++++++++++++++++++ tests/graph/CMakeLists.txt | 1 + tests/graph/Tracker.cpp | 54 ++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 include/Simple-Utility/graph/Tracker.hpp create mode 100644 tests/graph/Tracker.cpp diff --git a/include/Simple-Utility/graph/Tracker.hpp b/include/Simple-Utility/graph/Tracker.hpp new file mode 100644 index 000000000..0114e340a --- /dev/null +++ b/include/Simple-Utility/graph/Tracker.hpp @@ -0,0 +1,40 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_TRACKER_HPP +#define SIMPLE_UTILITY_GRAPH_TRACKER_HPP + +#pragma once + +#include "Simple-Utility/graph/Common.hpp" + +#include + +namespace sl::graph::tracker::detail +{ + struct set_discovered_fn + { + template + requires requires(T& t, const Vertex& v) { { t.set_discovered(v) } -> std::convertible_to; } + constexpr bool operator ()(T& tracker, const Vertex& v) const noexcept(noexcept(tracker.set_discovered(v))) + { + return tracker.set_discovered(v); + } + + template + requires requires(T& t, const Vertex& v) { { set_discovered(t, v) } -> std::convertible_to; } + constexpr bool operator ()(T& tracker, const Vertex& v) const noexcept(noexcept(set_discovered(tracker, v))) + { + return set_discovered(tracker, v); + } + }; +} + +namespace sl::graph::tracker +{ + inline constexpr detail::set_discovered_fn set_discovered{}; +} + +#endif diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 9d49774c2..5f8d5f1e5 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -4,4 +4,5 @@ target_sources( "Common.cpp" "Node.cpp" "Queue.cpp" + "Tracker.cpp" ) diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp new file mode 100644 index 000000000..e4cb53a8b --- /dev/null +++ b/tests/graph/Tracker.cpp @@ -0,0 +1,54 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/Tracker.hpp" + +#include +#include +#include + +#include "Defines.hpp" + +// ReSharper disable CppDeclaratorNeverUsed + +namespace +{ + struct member_fun_set_discovered + { + MAKE_MOCK1(set_discovered, bool(int)); + }; + + struct free_fun_set_discovered + { + MAKE_MOCK1(do_set_discovered, bool(int)); + + friend bool set_discovered(free_fun_set_discovered& obj, const int vertex) + { + return obj.do_set_discovered(vertex); + } + }; +} + +TEST_CASE("graph::queue::set_discovered serves as a customization point, modifing the tracker state.", "[graph][graph::queue]") +{ + const int vertex = GENERATE(take(5, random(0, std::numeric_limits::max()))); + const bool expected = GENERATE(true, false); + + SECTION("Access via the member function.") + { + member_fun_set_discovered mock{}; + REQUIRE_CALL(mock, set_discovered(vertex)) + .RETURN(expected); + REQUIRE(expected == sg::tracker::set_discovered(mock, vertex)); + } + + SECTION("Access via the free function.") + { + free_fun_set_discovered mock{}; + REQUIRE_CALL(mock, do_set_discovered(vertex)) + .RETURN(expected); + REQUIRE(expected == sg::tracker::set_discovered(mock, vertex)); + } +} From 050fe5455dc0fe3e6e40d8b0d0b23a5f4bf51fd3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 04:16:07 +0200 Subject: [PATCH 026/256] feat: graph::tracker::set_visited --- include/Simple-Utility/graph/Tracker.hpp | 18 +++++++++++ tests/graph/Tracker.cpp | 38 +++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Tracker.hpp b/include/Simple-Utility/graph/Tracker.hpp index 0114e340a..e00aa7605 100644 --- a/include/Simple-Utility/graph/Tracker.hpp +++ b/include/Simple-Utility/graph/Tracker.hpp @@ -30,11 +30,29 @@ namespace sl::graph::tracker::detail return set_discovered(tracker, v); } }; + + struct set_visited_fn + { + template + requires requires(T& t, const Vertex& v) { t.set_visited(v); } + constexpr void operator ()(T& tracker, const Vertex& v) const noexcept(noexcept(tracker.set_visited(v))) + { + tracker.set_visited(v); + } + + template + requires requires(T& t, const Vertex& v) { set_visited(t, v); } + constexpr void operator ()(T& tracker, const Vertex& v) const noexcept(noexcept(set_visited(tracker, v))) + { + set_visited(tracker, v); + } + }; } namespace sl::graph::tracker { inline constexpr detail::set_discovered_fn set_discovered{}; + inline constexpr detail::set_visited_fn set_visited{}; } #endif diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index e4cb53a8b..71403684e 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -29,9 +29,24 @@ namespace return obj.do_set_discovered(vertex); } }; + + struct member_fun_set_visited + { + MAKE_MOCK1(set_visited, void(int)); + }; + + struct free_fun_set_visited + { + MAKE_MOCK1(do_set_visited, void(int)); + + friend void set_visited(free_fun_set_visited& obj, const int vertex) + { + return obj.do_set_visited(vertex); + } + }; } -TEST_CASE("graph::queue::set_discovered serves as a customization point, modifing the tracker state.", "[graph][graph::queue]") +TEST_CASE("graph::queue::set_discovered serves as a customization point, modifing the tracker state.", "[graph][graph::tracker]") { const int vertex = GENERATE(take(5, random(0, std::numeric_limits::max()))); const bool expected = GENERATE(true, false); @@ -52,3 +67,24 @@ TEST_CASE("graph::queue::set_discovered serves as a customization point, modifin REQUIRE(expected == sg::tracker::set_discovered(mock, vertex)); } } + +TEST_CASE("graph::queue::set_visited serves as a customization point, modifing the tracker state.", "[graph][graph::tracker]") +{ + const int vertex = GENERATE(take(5, random(0, std::numeric_limits::max()))); + + SECTION("Access via the member function.") + { + member_fun_set_visited mock{}; + REQUIRE_CALL(mock, set_visited(vertex)); + + sg::tracker::set_visited(mock, vertex); + } + + SECTION("Access via the free function.") + { + free_fun_set_visited mock{}; + REQUIRE_CALL(mock, do_set_visited(vertex)); + + sg::tracker::set_visited(mock, vertex); + } +} From f4958f8b255a07f5c307ca4f8c8b5fd16fd66c41 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 04:22:10 +0200 Subject: [PATCH 027/256] add pragma once --- include/Simple-Utility/graph/Node.hpp | 2 ++ include/Simple-Utility/graph/Queue.hpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 635af53b4..e02cc695b 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -6,6 +6,8 @@ #ifndef SIMPLE_UTILITY_GRAPH_NODE_HPP #define SIMPLE_UTILITY_GRAPH_NODE_HPP +#pragma once + #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index d9ef7455d..88f1c7034 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -6,6 +6,8 @@ #ifndef SIMPLE_UTILITY_GRAPH_QUEUE_HPP #define SIMPLE_UTILITY_GRAPH_QUEUE_HPP +#pragma once + #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Node.hpp" From ee07f3fa422dd26ae408368dd212ecd33655aca3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 13:46:14 +0200 Subject: [PATCH 028/256] feat: graph::concepts::tracker_for --- include/Simple-Utility/graph/Tracker.hpp | 14 ++++++++++++++ tests/graph/Defines.hpp | 15 +++++++++++++-- tests/graph/Tracker.cpp | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Tracker.hpp b/include/Simple-Utility/graph/Tracker.hpp index e00aa7605..050c4a2ae 100644 --- a/include/Simple-Utility/graph/Tracker.hpp +++ b/include/Simple-Utility/graph/Tracker.hpp @@ -12,6 +12,8 @@ #include +#include "Simple-Utility/concepts/stl_extensions.hpp" + namespace sl::graph::tracker::detail { struct set_discovered_fn @@ -55,4 +57,16 @@ namespace sl::graph::tracker inline constexpr detail::set_visited_fn set_visited{}; } +namespace sl::graph::concepts +{ + template + concept tracker_for = sl::concepts::unqualified + && vertex + && requires(T& tracker, const Vertex& v) + { + { tracker::set_discovered(tracker, v) } -> std::convertible_to; + tracker::set_visited(tracker, v); + }; +} + #endif diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 199fee62f..474ff0fb5 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -8,8 +8,9 @@ #include #include -#include -#include +#include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Node.hpp" +#include "Simple-Utility/graph/Tracker.hpp" namespace sg = sl::graph; @@ -49,3 +50,13 @@ class QueueMock queue.do_insert(std::move(vector)); } }; + +template +class TrackerMock +{ +public: + inline static constexpr bool trompeloeil_movable_mock = true; + + MAKE_MOCK1(set_discovered, bool(const Vertex&)); + MAKE_MOCK1(set_visited, void(const Vertex&)); +}; diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index 71403684e..b78b7b34c 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -5,12 +5,15 @@ #include "Simple-Utility/graph/Tracker.hpp" +#include #include #include #include #include "Defines.hpp" +#include + // ReSharper disable CppDeclaratorNeverUsed namespace @@ -88,3 +91,18 @@ TEST_CASE("graph::queue::set_visited serves as a customization point, modifing t sg::tracker::set_visited(mock, vertex); } } + +TEMPLATE_TEST_CASE_SIG( + "concepts::tracker_for determines, whether the given type can be used to track the visitation state of the specified vertex.", + "[graph][graph::concepts][graph::tracker]", + ((bool expected, class T, class Vertex), expected, T, Vertex), + (false, member_fun_set_discovered, int), + (false, free_fun_set_discovered, int), + (false, member_fun_set_visited, int), + (false, free_fun_set_visited, int), + (false, TrackerMock, std::string), + (true, TrackerMock, int) +) +{ + STATIC_REQUIRE(expected == sg::concepts::tracker_for); +} From e706c35c48d6ceeb410ff7b15a1f891f6243892b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 14:54:35 +0200 Subject: [PATCH 029/256] feat: graph::detail::BasicState --- include/Simple-Utility/graph/Traverse.hpp | 70 ++++++++++++ tests/graph/CMakeLists.txt | 1 + tests/graph/Traverse.cpp | 126 ++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 include/Simple-Utility/graph/Traverse.hpp create mode 100644 tests/graph/Traverse.cpp diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp new file mode 100644 index 000000000..b989788f7 --- /dev/null +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -0,0 +1,70 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_TRAVERSE_HPP +#define SIMPLE_UTILITY_GRAPH_TRAVERSE_HPP + +#pragma once + +#include "Simple-Utility/Config.hpp" +#include "Simple-Utility/concepts/stl_extensions.hpp" +#include "Simple-Utility/functional/Tuple.hpp" +#include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Node.hpp" +#include "Simple-Utility/graph/Queue.hpp" +#include "Simple-Utility/graph/Tracker.hpp" + +#include +#include +#include + +namespace sl::graph::detail +{ + template QueuingStrategy> + class BasicState + { + public: + using node_type = Node; + using queue_type = QueuingStrategy; + + [[nodiscard]] + constexpr explicit BasicState(const node_type& origin) + : BasicState{std::move(origin), {}} + { + } + + [[nodiscard]] + constexpr explicit BasicState(const node_type& origin, queue_type queue) + : m_Queue{std::move(queue)} + { + queue::insert(m_Queue, std::views::single(origin)); + } + + [[nodiscard]] + constexpr const queue_type& queue() const noexcept + { + return m_Queue; + } + + template + requires std::convertible_to, node_type> + [[nodiscard]] + constexpr std::optional next(Neighbors&& neighbors) + { + queue::insert(m_Queue, std::forward(neighbors)); + if (!queue::empty(m_Queue)) + { + return queue::next(m_Queue); + } + + return std::nullopt; + } + + private: + QueuingStrategy m_Queue{}; + }; +} + +#endif diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 5f8d5f1e5..c58569b19 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -5,4 +5,5 @@ target_sources( "Node.cpp" "Queue.cpp" "Tracker.cpp" + "Traverse.cpp" ) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp new file mode 100644 index 000000000..a4968896e --- /dev/null +++ b/tests/graph/Traverse.cpp @@ -0,0 +1,126 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include + +#include "Defines.hpp" + +#include "Simple-Utility/graph/Traverse.hpp" + +#include + +// ReSharper disable CppDeclaratorNeverUsed +// ReSharper disable CppTypeAliasNeverUsed +// ReSharper disable CppClangTidyClangDiagnosticUnneededMemberFunction + +namespace +{ + struct VertexMemberNode + { + using vertex_type = int; + + int vertex{}; + + friend bool operator==(const VertexMemberNode&, const VertexMemberNode&) = default; + }; + + struct DefaultConstructibleQueue + { + std::vector queue{}; + + [[nodiscard]] + bool empty() const noexcept + { + return queue.empty(); + } + + void insert(const auto& elements) + { + queue.insert(std::end(queue), std::ranges::begin(elements), std::ranges::end(elements)); + } + + [[nodiscard]] + VertexMemberNode next() + { + const VertexMemberNode front = queue.front(); + queue.erase(std::begin(queue)); + return front; + } + }; +} + +using DefaultNode = VertexMemberNode; +using DefaultQueue = QueueMock; +using DefaultState = sg::detail::BasicState; + +TEST_CASE("BasicState is constructible from origin node and queue argument.", "[graph][graph::traverse][graph::detail]") +{ + using trompeloeil::_; + + const int originVertex = GENERATE(take(5, random(std::numeric_limits::min(), std::numeric_limits::max()))); + + DefaultQueue queueMock{}; + REQUIRE_CALL(queueMock, do_insert(_)) + .WITH(std::ssize(_1) == 1) + .WITH(sg::node::vertex(_1[0]) == originVertex); + + const DefaultState state{{originVertex}, std::move(queueMock)}; +} + +TEST_CASE("BasicState is constructible from origin node.", "[graph][graph::traverse][graph::detail]") +{ + const int originVertex = GENERATE(take(5, random(std::numeric_limits::min(), std::numeric_limits::max()))); + + const sg::detail::BasicState state{{originVertex}}; + + REQUIRE(originVertex == sg::node::vertex(state.queue().queue.front())); +} + +TEST_CASE("BasicState::next accepts current neighbors and returns the next node, if present.", "[graph][graph::traverse][graph::detail]") +{ + using trompeloeil::_; + + DefaultQueue queueMock{}; + REQUIRE_CALL(queueMock, do_insert(_)) + .WITH(std::ranges::equal(std::array{DefaultNode{42}}, _1)); + DefaultState state{{42}, std::move(queueMock)}; + + SECTION("Providing neighbor infos on next call.") + { + std::vector neighbors{{44}, {45}, {46}}; + auto& queueMember = const_cast(state.queue()); + + trompeloeil::sequence seq{}; + REQUIRE_CALL(queueMember, do_insert(_)) + .WITH(std::ranges::is_permutation(neighbors, _1)) + .IN_SEQUENCE(seq); + + REQUIRE_CALL(queueMember, empty()) + .RETURN(false) + .IN_SEQUENCE(seq); + + REQUIRE_CALL(queueMember, next()) + .RETURN(DefaultNode{42}) + .IN_SEQUENCE(seq); + + REQUIRE(DefaultNode{42} == state.next(neighbors)); + + SECTION("And when the internal queue depletes.") + { + REQUIRE_CALL(queueMember, do_insert(_)) + .WITH(std::ranges::empty(_1)) + .IN_SEQUENCE(seq); + + REQUIRE_CALL(queueMember, empty()) + .RETURN(true) + .IN_SEQUENCE(seq); + + REQUIRE(std::nullopt == state.next(std::array{})); + } + } +} From 3fbcd47d6ae37f2dcdb2faaec32e6e5867084a40 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 14:59:10 +0200 Subject: [PATCH 030/256] fix: please clang --- include/Simple-Utility/graph/Traverse.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index b989788f7..88d7afcd9 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -16,6 +16,7 @@ #include "Simple-Utility/graph/Queue.hpp" #include "Simple-Utility/graph/Tracker.hpp" +#include #include #include #include @@ -30,16 +31,16 @@ namespace sl::graph::detail using queue_type = QueuingStrategy; [[nodiscard]] - constexpr explicit BasicState(const node_type& origin) + constexpr explicit BasicState(node_type origin) : BasicState{std::move(origin), {}} { } [[nodiscard]] - constexpr explicit BasicState(const node_type& origin, queue_type queue) + constexpr explicit BasicState(node_type origin, queue_type queue) : m_Queue{std::move(queue)} { - queue::insert(m_Queue, std::views::single(origin)); + queue::insert(m_Queue, std::array{std::move(origin)}); } [[nodiscard]] From 280a31c8e5a06a3df51759223730ceabb9a9b6d9 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 16:05:15 +0200 Subject: [PATCH 031/256] feat: graph::concepts::node_factory_for --- include/Simple-Utility/graph/Node.hpp | 10 ++++++++ tests/graph/Node.cpp | 34 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index e02cc695b..6eef1f85f 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -52,5 +52,15 @@ namespace sl::graph::concepts { requires concepts::vertex>; }; + + template + concept node_factory_for = sl::concepts::unqualified + && node + && std::destructible + && requires(T& factory, const Node& node) + { + { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; + { factory.make_successor_node(node, node::vertex(node)) } -> std::convertible_to; + }; } #endif diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index be5b6a291..038313f11 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -35,6 +35,29 @@ namespace return v.my_vertex(); } }; + + struct minimal_node + { + using vertex_type = int; + + vertex_type vertex; + }; + + struct minimal_node_factory + { + using node_type = minimal_node; + using vertex_type = sg::feature_vertex_t; + + static node_type make_init_node(const vertex_type& v) + { + return {.vertex = v}; + } + + static node_type make_init_node([[maybe_unused]] const node_type& predecessor, const vertex_type& v) + { + return {.vertex = v}; + } + }; } TEST_CASE("graph::node::vertex serves as a customization point accessing the node vertex.", "[graph][graph::node]") @@ -75,3 +98,14 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::node); } + +TEMPLATE_TEST_CASE_SIG( + "concepts::node_factory_for determines, whether the given type satisfies the requirements of a node_factory for the specified node type.", + "[graph][graph::concepts]", + ((bool expected, class Factory, class Node), expected, Factory, Node), + (false, minimal_node_factory, BasicTestNode), + (true, minimal_node_factory, minimal_node) +) +{ + STATIC_REQUIRE(expected == sg::concepts::node_factory_for); +} From c0925dac6c0e820633fcae17f9bf91d3a45202a3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 16:15:36 +0200 Subject: [PATCH 032/256] fix: let minimal_node_factory satisfy concepts::node_factory_for --- tests/graph/Node.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 038313f11..84be46880 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -53,7 +53,7 @@ namespace return {.vertex = v}; } - static node_type make_init_node([[maybe_unused]] const node_type& predecessor, const vertex_type& v) + static node_type make_successor_node([[maybe_unused]] const node_type& predecessor, const vertex_type& v) { return {.vertex = v}; } @@ -103,7 +103,7 @@ TEMPLATE_TEST_CASE_SIG( "concepts::node_factory_for determines, whether the given type satisfies the requirements of a node_factory for the specified node type.", "[graph][graph::concepts]", ((bool expected, class Factory, class Node), expected, Factory, Node), - (false, minimal_node_factory, BasicTestNode), + //(false, minimal_node_factory, BasicTestNode), (true, minimal_node_factory, minimal_node) ) { From 1d3629f0ae99f40af478a3073fb398f585472a3b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 16:24:43 +0200 Subject: [PATCH 033/256] fix: try please msvc v142 on older os versions --- tests/graph/Traverse.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index a4968896e..7d34f59fa 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -12,6 +12,7 @@ #include "Simple-Utility/graph/Traverse.hpp" +#include #include // ReSharper disable CppDeclaratorNeverUsed From 2077426439fdc3abe897334fd8da9af453d47aa1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 17:33:08 +0200 Subject: [PATCH 034/256] fix: try please fix: try please msvc v142 on older os versions #2 --- tests/graph/Traverse.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 7d34f59fa..2bc46e58a 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -12,8 +12,10 @@ #include "Simple-Utility/graph/Traverse.hpp" +#include #include #include +#include // ReSharper disable CppDeclaratorNeverUsed // ReSharper disable CppTypeAliasNeverUsed From b1ec38d629c5990e8553f082e9b4fa3a1aa55d19 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 19:15:58 +0200 Subject: [PATCH 035/256] feat: trompeloeil_ext::matches matcher --- .../test_util/TrompeloeilExt.hpp | 51 +++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/test_util/CMakeLists.txt | 5 ++ tests/test_util/TrompeloeilExt.cpp | 17 +++++++ 4 files changed, 74 insertions(+) create mode 100644 include/Simple-Utility/test_util/TrompeloeilExt.hpp create mode 100644 tests/test_util/CMakeLists.txt create mode 100644 tests/test_util/TrompeloeilExt.cpp diff --git a/include/Simple-Utility/test_util/TrompeloeilExt.hpp b/include/Simple-Utility/test_util/TrompeloeilExt.hpp new file mode 100644 index 000000000..d357647c1 --- /dev/null +++ b/include/Simple-Utility/test_util/TrompeloeilExt.hpp @@ -0,0 +1,51 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_TEST_UTIL_TROMPELOEIL_EXT_HPP +#define SIMPLE_UTILITY_TEST_UTIL_TROMPELOEIL_EXT_HPP + +#pragma once + +#include +#include + +// strictly include this after the catch header +#include + +#include +#include + +namespace trompeloeil_ext +{ + template + struct matches_matcher_fn; + + template + requires std::derived_from, Catch::Matchers::MatcherGenericBase> + struct matches_matcher_fn + { + constexpr auto operator ()(Matcher&& matcher) const + { + using matcher_type = std::remove_cvref_t; + return trompeloeil::make_matcher( + [](const auto& value, const matcher_type& matcher) -> bool + { + return matcher.match(value); + }, + [](std::ostream& os, const matcher_type& matcher) + { + os << "matching matcher: " << matcher.describe(); + }, + std::forward(matcher)); + } + }; + + inline constexpr auto matches = [](Matcher&& matcher) + { + return matches_matcher_fn>{}(std::forward(matcher)); + }; +} + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23d0379c1..a66d901ca 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,6 +19,7 @@ add_subdirectory("nullables") add_subdirectory("concepts") add_subdirectory("functional") add_subdirectory("graph") +add_subdirectory("test_util") target_link_libraries( Simple-Utility-Tests diff --git a/tests/test_util/CMakeLists.txt b/tests/test_util/CMakeLists.txt new file mode 100644 index 000000000..ab9bee1e1 --- /dev/null +++ b/tests/test_util/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources( + Simple-Utility-Tests + PRIVATE + "TrompeloeilExt.cpp" +) diff --git a/tests/test_util/TrompeloeilExt.cpp b/tests/test_util/TrompeloeilExt.cpp new file mode 100644 index 000000000..f8370d57e --- /dev/null +++ b/tests/test_util/TrompeloeilExt.cpp @@ -0,0 +1,17 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/test_util/TrompeloeilExt.hpp" + +#include +#include + +TEST_CASE("Catch2 matchers can be used as argument for the trompeloeil_ext::matches matcher", "[test_util][test_util::trompeloeil]") +{ + constexpr std::array data{42, 43, 47}; + + REQUIRE(!trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::vector{42, 47})).matches(data)); + REQUIRE(trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::vector{42, 43, 47})).matches(data)); +} From 35cd0ec3f1f7e87b86f4ffb06a46e545bc5eb790 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 19:26:11 +0200 Subject: [PATCH 036/256] feat: catch_ext::RangesEmpty --- .../Simple-Utility/test_util/Catch2Ext.hpp | 33 +++++++++++++++++++ tests/test_util/CMakeLists.txt | 1 + tests/test_util/Catch2Ext.cpp | 15 +++++++++ 3 files changed, 49 insertions(+) create mode 100644 include/Simple-Utility/test_util/Catch2Ext.hpp create mode 100644 tests/test_util/Catch2Ext.cpp diff --git a/include/Simple-Utility/test_util/Catch2Ext.hpp b/include/Simple-Utility/test_util/Catch2Ext.hpp new file mode 100644 index 000000000..369fdfa92 --- /dev/null +++ b/include/Simple-Utility/test_util/Catch2Ext.hpp @@ -0,0 +1,33 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_TEST_UTIL_CATCH2_EXT_HPP +#define SIMPLE_UTILITY_TEST_UTIL_CATCH2_EXT_HPP + +#pragma once + +#include + +#include + +namespace catch_ext +{ + class RangesEmpty final + : public Catch::Matchers::MatcherGenericBase + { + public: + static constexpr bool match(const std::ranges::range auto& range) + { + return std::ranges::empty(range); + } + + std::string describe() const override + { + return "Empty"; + } + }; +} + +#endif diff --git a/tests/test_util/CMakeLists.txt b/tests/test_util/CMakeLists.txt index ab9bee1e1..432b97f4a 100644 --- a/tests/test_util/CMakeLists.txt +++ b/tests/test_util/CMakeLists.txt @@ -1,5 +1,6 @@ target_sources( Simple-Utility-Tests PRIVATE + "Catch2Ext.cpp" "TrompeloeilExt.cpp" ) diff --git a/tests/test_util/Catch2Ext.cpp b/tests/test_util/Catch2Ext.cpp new file mode 100644 index 000000000..3dafe5ce7 --- /dev/null +++ b/tests/test_util/Catch2Ext.cpp @@ -0,0 +1,15 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/test_util/Catch2Ext.hpp" + +#include +#include + +TEST_CASE("catch_ext::RangesEmpty matches empty ranges.", "[test_util][test_util::catch2]") +{ + REQUIRE_THAT(std::vector{}, catch_ext::RangesEmpty{}); + REQUIRE_THAT(std::vector{42}, !catch_ext::RangesEmpty{}); +} From 2ae9c4e4018de4f979753147a64edf43e375e000 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 19:33:41 +0200 Subject: [PATCH 037/256] fix: please several compilers --- include/Simple-Utility/graph/Traverse.hpp | 57 +++++++++++++++++++++++ tests/graph/Traverse.cpp | 25 +++++----- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 88d7afcd9..994105fae 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -66,6 +67,62 @@ namespace sl::graph::detail private: QueuingStrategy m_Queue{}; }; + + template < + concepts::node Node, + class StateStrategy, + concepts::tracker_for TrackingStrategy, + concepts::node_factory_for NodeFactoryStrategy + > + class BasicTraverseDriver + { + public: + using node_type = Node; + using vertex_type = feature_vertex_t; + + template + [[nodiscard]] + explicit BasicTraverseDriver(vertex_type origin, OtherArgs&&... otherArgs) + : m_Current{m_NodeFactory.make_init_node(std::move(origin), std::forward(otherArgs)...)}, + m_State{m_Current} + { + tracker::set_discovered(m_Tracker, node::vertex(m_Current)); + } + + template + [[nodiscard]] + constexpr std::optional next(const Graph& graph) + { + if (auto result = m_State.next( + graph.neighbor_infos(m_Current) + | std::views::filter([&](const auto& info) { return !tracker::set_discovered(m_Tracker, info.vertex); }) + | std::views::transform( + functional::envelop( + [&](Ts&&... infos) + { + return m_NodeFactory.make_successor_node(m_Current, std::forward(infos)...); + })))) + { + m_Current = *result; + tracker::set_visited(m_Tracker, node::vertex(m_Current)); + return result; + } + + return std::nullopt; + } + + [[nodiscard]] + constexpr const Node& current_node() const noexcept + { + return m_Current; + } + + private: + Node m_Current{}; + StateStrategy m_State{}; + SL_UTILITY_NO_UNIQUE_ADDRESS TrackingStrategy m_Tracker{}; + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactoryStrategy m_NodeFactory{}; + }; } #endif diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 2bc46e58a..d94809879 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -3,19 +3,18 @@ // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) +#include "Simple-Utility/graph/Traverse.hpp" + #include #include #include #include +#include #include "Defines.hpp" -#include "Simple-Utility/graph/Traverse.hpp" - -#include -#include -#include -#include +#include "Simple-Utility/test_util/Catch2Ext.hpp" +#include "Simple-Utility/test_util/TrompeloeilExt.hpp" // ReSharper disable CppDeclaratorNeverUsed // ReSharper disable CppTypeAliasNeverUsed @@ -29,7 +28,7 @@ namespace int vertex{}; - friend bool operator==(const VertexMemberNode&, const VertexMemberNode&) = default; + friend bool operator==(const VertexMemberNode& lhs, const VertexMemberNode& rhs) { return lhs.vertex == rhs.vertex; }; }; struct DefaultConstructibleQueue @@ -87,10 +86,12 @@ TEST_CASE("BasicState is constructible from origin node.", "[graph][graph::trave TEST_CASE("BasicState::next accepts current neighbors and returns the next node, if present.", "[graph][graph::traverse][graph::detail]") { using trompeloeil::_; + using namespace trompeloeil_ext; + using namespace Catch::Matchers; + using namespace catch_ext; DefaultQueue queueMock{}; - REQUIRE_CALL(queueMock, do_insert(_)) - .WITH(std::ranges::equal(std::array{DefaultNode{42}}, _1)); + REQUIRE_CALL(queueMock, do_insert(matches(RangeEquals(std::array{DefaultNode{42}})))); DefaultState state{{42}, std::move(queueMock)}; SECTION("Providing neighbor infos on next call.") @@ -99,8 +100,7 @@ TEST_CASE("BasicState::next accepts current neighbors and returns the next node, auto& queueMember = const_cast(state.queue()); trompeloeil::sequence seq{}; - REQUIRE_CALL(queueMember, do_insert(_)) - .WITH(std::ranges::is_permutation(neighbors, _1)) + REQUIRE_CALL(queueMember, do_insert(matches(UnorderedRangeEquals(neighbors)))) .IN_SEQUENCE(seq); REQUIRE_CALL(queueMember, empty()) @@ -115,8 +115,7 @@ TEST_CASE("BasicState::next accepts current neighbors and returns the next node, SECTION("And when the internal queue depletes.") { - REQUIRE_CALL(queueMember, do_insert(_)) - .WITH(std::ranges::empty(_1)) + REQUIRE_CALL(queueMember, do_insert(matches(RangesEmpty{}))) .IN_SEQUENCE(seq); REQUIRE_CALL(queueMember, empty()) From 7578dbb3152d6e62101e4498d9f03c7a2cdf7314 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 21 Aug 2023 19:36:17 +0200 Subject: [PATCH 038/256] test: add catch_ext::RangesEmpty::describe case --- tests/test_util/Catch2Ext.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_util/Catch2Ext.cpp b/tests/test_util/Catch2Ext.cpp index 3dafe5ce7..5bf946de8 100644 --- a/tests/test_util/Catch2Ext.cpp +++ b/tests/test_util/Catch2Ext.cpp @@ -13,3 +13,8 @@ TEST_CASE("catch_ext::RangesEmpty matches empty ranges.", "[test_util][test_util REQUIRE_THAT(std::vector{}, catch_ext::RangesEmpty{}); REQUIRE_THAT(std::vector{42}, !catch_ext::RangesEmpty{}); } + +TEST_CASE("catch_ext::RangesEmpty::describe prints a description.", "[test_util][test_util::catch2]") +{ + REQUIRE_THAT(catch_ext::RangesEmpty{}.describe(), !catch_ext::RangesEmpty{}); +} From df6ba8ccd3f8d2835b313b5ba39088c02b36a1ab Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 22 Aug 2023 00:13:01 +0200 Subject: [PATCH 039/256] test: add test case for trompeloeil_ext::matches operator << --- tests/test_util/TrompeloeilExt.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_util/TrompeloeilExt.cpp b/tests/test_util/TrompeloeilExt.cpp index f8370d57e..e75e5874b 100644 --- a/tests/test_util/TrompeloeilExt.cpp +++ b/tests/test_util/TrompeloeilExt.cpp @@ -15,3 +15,11 @@ TEST_CASE("Catch2 matchers can be used as argument for the trompeloeil_ext::matc REQUIRE(!trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::vector{42, 47})).matches(data)); REQUIRE(trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::vector{42, 43, 47})).matches(data)); } + +TEST_CASE("trompeloeil_ext::matches can be used to print something to an ostream.", "[test_util][test_util::trompeloeil]") +{ + std::ostringstream ss{}; + ss << trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::vector{42, 43, 47})); + + REQUIRE(!std::ranges::empty(ss.view())); +} From 029cf80b825f03d7340b8c14dc7c45f6bd5f88df Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 22 Aug 2023 00:25:40 +0200 Subject: [PATCH 040/256] fix: please various compilers --- tests/test_util/TrompeloeilExt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_util/TrompeloeilExt.cpp b/tests/test_util/TrompeloeilExt.cpp index e75e5874b..367d15720 100644 --- a/tests/test_util/TrompeloeilExt.cpp +++ b/tests/test_util/TrompeloeilExt.cpp @@ -21,5 +21,5 @@ TEST_CASE("trompeloeil_ext::matches can be used to print something to an ostream std::ostringstream ss{}; ss << trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::vector{42, 43, 47})); - REQUIRE(!std::ranges::empty(ss.view())); + REQUIRE(!std::ranges::empty(ss.str())); } From 0276c6a76bcb7fc97fc9bef6a39d1b452dbf6220 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 22 Aug 2023 01:05:48 +0200 Subject: [PATCH 041/256] feat: add support for catch old style matches in trompeloeil_ext::matches --- include/Simple-Utility/test_util/TrompeloeilExt.hpp | 3 ++- tests/test_util/TrompeloeilExt.cpp | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/test_util/TrompeloeilExt.hpp b/include/Simple-Utility/test_util/TrompeloeilExt.hpp index d357647c1..9a3841d25 100644 --- a/include/Simple-Utility/test_util/TrompeloeilExt.hpp +++ b/include/Simple-Utility/test_util/TrompeloeilExt.hpp @@ -23,7 +23,8 @@ namespace trompeloeil_ext struct matches_matcher_fn; template - requires std::derived_from, Catch::Matchers::MatcherGenericBase> + requires std::derived_from, Catch::Matchers::MatcherGenericBase> // new style + || std::derived_from, Catch::Matchers::MatcherUntypedBase> // old style struct matches_matcher_fn { constexpr auto operator ()(Matcher&& matcher) const diff --git a/tests/test_util/TrompeloeilExt.cpp b/tests/test_util/TrompeloeilExt.cpp index 367d15720..cb1652f25 100644 --- a/tests/test_util/TrompeloeilExt.cpp +++ b/tests/test_util/TrompeloeilExt.cpp @@ -7,13 +7,19 @@ #include #include +#include TEST_CASE("Catch2 matchers can be used as argument for the trompeloeil_ext::matches matcher", "[test_util][test_util::trompeloeil]") { - constexpr std::array data{42, 43, 47}; + const std::vector data{42, 43, 47}; - REQUIRE(!trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::vector{42, 47})).matches(data)); - REQUIRE(trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::vector{42, 43, 47})).matches(data)); + // new style + REQUIRE(!trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::array{42, 47})).matches(data)); + REQUIRE(trompeloeil_ext::matches(Catch::Matchers::RangeEquals(std::array{42, 43, 47})).matches(data)); + + // old style + REQUIRE(trompeloeil_ext::matches(Catch::Matchers::VectorContains(43)).matches(data)); + REQUIRE(!trompeloeil_ext::matches(Catch::Matchers::VectorContains(1337)).matches(data)); } TEST_CASE("trompeloeil_ext::matches can be used to print something to an ostream.", "[test_util][test_util::trompeloeil]") From be0b76a47c3121984331466253e94395a5b381c3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 22 Aug 2023 22:53:26 +0200 Subject: [PATCH 042/256] refactor: replace some complex constraints with sl::concepts::unqualified --- include/Simple-Utility/graph/Common.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index c77757ef3..e53e93313 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -10,6 +10,7 @@ #include "Simple-Utility/TypeList.hpp" #include "Simple-Utility/concepts/operators.hpp" +#include "Simple-Utility/concepts/stl_extensions.hpp" #include #include @@ -36,12 +37,12 @@ namespace sl::graph::concepts concept feature_category = type_list::contains_v; template - concept vertex = std::same_as> + concept vertex = sl::concepts::unqualified && std::equality_comparable && std::copyable; template - concept rank = std::same_as> + concept rank = sl::concepts::unqualified && std::totally_ordered && std::regular && sl::concepts::plus From a10ac2cfcc5bb8c85e25ff65d9d081540b6dd5c4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 22 Aug 2023 23:18:31 +0200 Subject: [PATCH 043/256] feat: concepts::compatible_with --- include/Simple-Utility/graph/Node.hpp | 6 ++++++ tests/graph/Node.cpp | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 6eef1f85f..7030c475c 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -62,5 +62,11 @@ namespace sl::graph::concepts { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; { factory.make_successor_node(node, node::vertex(node)) } -> std::convertible_to; }; + + template + concept compatible_with = std::same_as< + feature_category_t, + common_feature_category_t, feature_category_t>> + && std::convertible_to, feature_vertex_t>; } #endif diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 84be46880..105974307 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -109,3 +109,14 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::node_factory_for); } + +TEMPLATE_TEST_CASE_SIG( + "concepts::compatible_with determines, whether the other type is compatible with T.", + "[graph][graph::concepts]", + ((bool expected, class T, class Other), expected, T, Other), + (false, minimal_node, BasicTestNode), + (true, minimal_node, BasicTestNode) +) +{ + STATIC_REQUIRE(expected == sg::concepts::compatible_with); +} From 1bc507ccbc2728d24777d13f07dc26cef0e11f98 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 22 Aug 2023 23:18:52 +0200 Subject: [PATCH 044/256] tests: extend some test cases --- tests/graph/Node.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 105974307..b3eb1c7e8 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -93,6 +93,7 @@ TEMPLATE_TEST_CASE_SIG( (false, member_vertex), (false, member_fun_vertex), (false, free_fun_vertex), + (true, minimal_node), (true, BasicTestNode) ) { @@ -103,8 +104,9 @@ TEMPLATE_TEST_CASE_SIG( "concepts::node_factory_for determines, whether the given type satisfies the requirements of a node_factory for the specified node type.", "[graph][graph::concepts]", ((bool expected, class Factory, class Node), expected, Factory, Node), - //(false, minimal_node_factory, BasicTestNode), - (true, minimal_node_factory, minimal_node) + (false, minimal_node_factory, BasicTestNode), + (true, minimal_node_factory, minimal_node), + (true, BasicNodeFactoryMock, minimal_node) ) { STATIC_REQUIRE(expected == sg::concepts::node_factory_for); From d6ce3aab11a9641b51b8a71528b405517dbf1a81 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 22 Aug 2023 23:19:47 +0200 Subject: [PATCH 045/256] test: add BasicNodeFactoryMock type --- tests/graph/Defines.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 474ff0fb5..fde3c2681 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -60,3 +60,17 @@ class TrackerMock MAKE_MOCK1(set_discovered, bool(const Vertex&)); MAKE_MOCK1(set_visited, void(const Vertex&)); }; + +template +class BasicNodeFactoryMock +{ +public: + inline static constexpr bool trompeloeil_movable_mock = true; + + using vertex_type = sg::feature_vertex_t; + + using info_layout = std::tuple; + + MAKE_MOCK1(make_init_node, Node(const vertex_type&)); + MAKE_MOCK2(make_successor_node, Node(const Node&, const info_layout&)); +}; From 42f080100945ec3a1000abffd82a5a85b36c7119 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 22 Aug 2023 23:20:19 +0200 Subject: [PATCH 046/256] fix: add missing whitespace in trompeloeil_ext::matches_matcher_fn description print --- include/Simple-Utility/test_util/TrompeloeilExt.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Simple-Utility/test_util/TrompeloeilExt.hpp b/include/Simple-Utility/test_util/TrompeloeilExt.hpp index 9a3841d25..7f3413564 100644 --- a/include/Simple-Utility/test_util/TrompeloeilExt.hpp +++ b/include/Simple-Utility/test_util/TrompeloeilExt.hpp @@ -37,7 +37,7 @@ namespace trompeloeil_ext }, [](std::ostream& os, const matcher_type& matcher) { - os << "matching matcher: " << matcher.describe(); + os << " matching matcher: " << matcher.describe(); }, std::forward(matcher)); } From e0841b3d92d65dccb07bc1e4182e0cf82887d19c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 00:05:13 +0200 Subject: [PATCH 047/256] fix: strengthen concepts::compatible_with --- include/Simple-Utility/graph/Node.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 7030c475c..78c557e7c 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -64,7 +64,9 @@ namespace sl::graph::concepts }; template - concept compatible_with = std::same_as< + concept compatible_with = node + && node + && std::same_as< feature_category_t, common_feature_category_t, feature_category_t>> && std::convertible_to, feature_vertex_t>; From d58da8ad7dd04093e8fe2cf99e66b3169665cb67 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 00:05:42 +0200 Subject: [PATCH 048/256] test: extend node test cases --- tests/graph/Node.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index b3eb1c7e8..fe2e23ce6 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -58,6 +58,20 @@ namespace return {.vertex = v}; } }; + + struct generic_basic_graph + { + struct info + { + using vertex_type = int; + vertex_type vertex; + }; + + std::vector neighbor_infos(const sg::concepts::node auto&) const + { + return {}; +} + }; } TEST_CASE("graph::node::vertex serves as a customization point accessing the node vertex.", "[graph][graph::node]") @@ -94,7 +108,9 @@ TEMPLATE_TEST_CASE_SIG( (false, member_fun_vertex), (false, free_fun_vertex), (true, minimal_node), - (true, BasicTestNode) + (true, BasicTestNode), + (true, generic_basic_graph::info), + (true, BasicGraph::info) ) { STATIC_REQUIRE(expected == sg::concepts::node); @@ -106,7 +122,7 @@ TEMPLATE_TEST_CASE_SIG( ((bool expected, class Factory, class Node), expected, Factory, Node), (false, minimal_node_factory, BasicTestNode), (true, minimal_node_factory, minimal_node), - (true, BasicNodeFactoryMock, minimal_node) + (true, BasicNodeFactoryMock, minimal_node) ) { STATIC_REQUIRE(expected == sg::concepts::node_factory_for); @@ -117,7 +133,13 @@ TEMPLATE_TEST_CASE_SIG( "[graph][graph::concepts]", ((bool expected, class T, class Other), expected, T, Other), (false, minimal_node, BasicTestNode), - (true, minimal_node, BasicTestNode) + (true, minimal_node, BasicTestNode), + (true, minimal_node, BasicGraph::info), + (true, BasicTestNode, BasicGraph::info), + (false, BasicTestNode, BasicGraph::info), + (true, minimal_node, generic_basic_graph::info), + (true, BasicTestNode, generic_basic_graph::info), + (false, BasicTestNode, generic_basic_graph::info) ) { STATIC_REQUIRE(expected == sg::concepts::compatible_with); From 37f148e8b53ed061c155c41b5b44507a68a6029c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 00:06:29 +0200 Subject: [PATCH 049/256] fix: improve concepts::node_factory_for --- include/Simple-Utility/graph/Node.hpp | 2 +- tests/graph/Defines.hpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 78c557e7c..ce7872a7d 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -60,7 +60,7 @@ namespace sl::graph::concepts && requires(T& factory, const Node& node) { { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; - { factory.make_successor_node(node, node::vertex(node)) } -> std::convertible_to; + { factory.make_successor_node(node, {}) } -> std::convertible_to; }; template diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index fde3c2681..ea287b2b8 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -61,7 +61,8 @@ class TrackerMock MAKE_MOCK1(set_visited, void(const Vertex&)); }; -template +template + requires sg::concepts::compatible_with class BasicNodeFactoryMock { public: @@ -69,7 +70,10 @@ class BasicNodeFactoryMock using vertex_type = sg::feature_vertex_t; - using info_layout = std::tuple; + MAKE_MOCK1(make_init_node, Node(const vertex_type&)); + MAKE_MOCK2(make_successor_node, Node(const Node&, const VertexInfo&)); +}; + MAKE_MOCK1(make_init_node, Node(const vertex_type&)); MAKE_MOCK2(make_successor_node, Node(const Node&, const info_layout&)); From 15d66c0b6e6315212130d037cb9bb606092239b7 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 00:07:01 +0200 Subject: [PATCH 050/256] feat: concepts::graph_for --- include/Simple-Utility/graph/Node.hpp | 11 +++++++++++ tests/graph/Defines.hpp | 15 +++++++++++++-- tests/graph/Node.cpp | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index ce7872a7d..5a424c2cb 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -70,5 +70,16 @@ namespace sl::graph::concepts feature_category_t, common_feature_category_t, feature_category_t>> && std::convertible_to, feature_vertex_t>; + + template + concept graph_for = sl::concepts::unqualified + && node + && requires(const T& graph, const Node& node) + { + { graph.neighbor_infos(node) } -> std::ranges::input_range; + requires compatible_with< + Node, + std::ranges::range_value_t>; + }; } #endif diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index ea287b2b8..241ed3b1d 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -74,7 +74,18 @@ class BasicNodeFactoryMock MAKE_MOCK2(make_successor_node, Node(const Node&, const VertexInfo&)); }; +template +class BasicGraph +{ +public: + struct info + { + using vertex_type = sg::feature_vertex_t; - MAKE_MOCK1(make_init_node, Node(const vertex_type&)); - MAKE_MOCK2(make_successor_node, Node(const Node&, const info_layout&)); + vertex_type vertex; + + friend bool operator ==(const info&, const info&) = default; + }; + + MAKE_CONST_MOCK1(neighbor_infos, std::vector(const Node&)); }; diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index fe2e23ce6..ef1267972 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -144,3 +144,18 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::compatible_with); } + +TEMPLATE_TEST_CASE_SIG( + "concepts::graph_for determines, whether the graph type satisfies the minimal requirements of the specified node.", + "[graph][graph::concepts]", + ((bool expected, class Graph, class Node), expected, Graph, Node), + (true, generic_basic_graph, minimal_node), + (true, generic_basic_graph, BasicTestNode), + (false, generic_basic_graph, BasicTestNode), + (true, BasicGraph, minimal_node), + (false, BasicGraph, BasicTestNode), + (false, BasicGraph, BasicTestNode) +) +{ + STATIC_REQUIRE(expected == sg::concepts::graph_for); +} From 9d90bd5c264bd6dc884fdc7821a3a065f72a72c4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 00:16:25 +0200 Subject: [PATCH 051/256] test: add RankedTestNode --- tests/graph/Defines.hpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 241ed3b1d..50b3941bf 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -30,6 +30,29 @@ struct BasicTestNode } }; +template +struct RankedTestNode +{ + using vertex_type = Vertex; + using rank_type = Rank; + + struct Mock + { + MAKE_CONST_MOCK1(vertex, vertex_type(const RankedTestNode&)); + MAKE_CONST_MOCK1(rank, rank_type(const RankedTestNode&)); + } inline static mock{}; + + friend constexpr vertex_type vertex(const RankedTestNode& node) + { + return mock.vertex(node); + } + + friend constexpr rank_type rank(const RankedTestNode& node) + { + return mock.rank(node); + } +}; + template class QueueMock { From 1c640cd9a74492055fe040a2ae294bd85359fc8b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 00:16:48 +0200 Subject: [PATCH 052/256] test: add templated test case for existing feature categories --- tests/graph/Common.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 0177db5bb..d9056b3dc 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -259,3 +259,27 @@ TEST_CASE( STATIC_REQUIRE(std::same_as::rank_type>); STATIC_REQUIRE(std::same_as>); } + +TEMPLATE_TEST_CASE( + "Listed types yield at least basic_feature_category.", + "[graph][graph::traits]", + BasicTestNode, + (RankedTestNode), + (BasicGraph>::info) +) +{ + STATIC_REQUIRE(std::same_as< + sg::basic_feature_category, + sg::common_feature_category_t>>); +} + +TEMPLATE_TEST_CASE( + "Listed types yield at least ranked_feature_category.", + "[graph][graph::traits]", + (RankedTestNode) +) +{ + STATIC_REQUIRE(std::same_as< + sg::ranked_feature_category, + sg::common_feature_category_t>>); +} From 2b6d328269b2dccfa294f09272005acc45839a51 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 02:24:20 +0200 Subject: [PATCH 053/256] fix: make queue customization points actually customizable --- include/Simple-Utility/graph/Queue.hpp | 148 +++++++++++++++++++------ tests/graph/Queue.cpp | 69 ++++++++++++ 2 files changed, 185 insertions(+), 32 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 88f1c7034..c5ea2d954 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -13,64 +13,148 @@ #include +namespace sl::graph::customize +{ + template + struct empty_fn; + + template + struct insert_fn; + + template + struct next_fn; +} + namespace sl::graph::queue::detail { - struct empty_fn + template + struct priority_tag + : public priority_tag { - template - requires requires(const T& t) { { t.empty() } -> std::convertible_to; } - constexpr bool operator ()(const T& queue) const noexcept(noexcept(queue.empty())) - { - return queue.empty(); - } + }; + + template <> + struct priority_tag<0> + { + }; + + template + requires requires(const T& t) { { customize::empty_fn{}(t) } -> std::convertible_to; } + constexpr bool empty(const T& queue, const priority_tag<2>) noexcept(noexcept(customize::empty_fn{}(queue))) + { + return customize::empty_fn{}(queue); + } + + template + requires requires(const T& t) { { t.empty() } -> std::convertible_to; } + constexpr bool empty(const T& queue, const priority_tag<1>) noexcept(noexcept(queue.empty())) + { + return queue.empty(); + } + + template + requires requires(const T& t) { { empty(t) } -> std::convertible_to; } + constexpr decltype(auto) empty(const T& queue, const priority_tag<0>) noexcept(noexcept(empty(queue))) + { + return empty(queue); + } + struct empty_fn + { template - requires requires(const T& t) { { empty(t) } -> std::convertible_to; } - constexpr decltype(auto) operator ()(const T& queue) const noexcept(noexcept(empty(queue))) + requires requires(const T& t) { { detail::empty(t, priority_tag<2>{}) } -> std::convertible_to; } + constexpr bool operator ()(const T& queue) const noexcept(noexcept(detail::empty(queue, priority_tag<2>{}))) { - return empty(queue); + return detail::empty(queue, priority_tag<2>{}); } }; + template + requires requires(Queue& queue) { customize::insert_fn{}(queue, std::declval()); } + constexpr void insert( + Queue& queue, + Range&& elements, + const priority_tag<2> + ) noexcept(noexcept(customize::insert_fn{}(queue, std::forward(elements)))) + { + customize::insert_fn{}(queue, std::forward(elements)); + } + + template + requires requires(Queue& queue) { queue.insert(std::declval()); } + constexpr void insert( + Queue& container, + Range&& elements, + const priority_tag<1> + ) noexcept(noexcept(container.insert(std::forward(elements)))) + { + container.insert(std::forward(elements)); + } + + template + requires requires(Queue& queue) { insert(queue, std::declval()); } + constexpr void insert( + Queue& container, + Range&& elements, + const priority_tag<0> + ) noexcept(noexcept(insert(container, std::forward(elements)))) + { + insert(container, std::forward(elements)); + } + struct insert_fn { template - requires concepts::node> - && requires(Queue& queue) { queue.insert(std::declval()); } + requires requires(Queue& queue) { detail::insert(queue, std::declval(), priority_tag<2>{}); } constexpr void operator ()( - Queue& container, + Queue& queue, Range&& elements - ) const noexcept(noexcept(container.insert(std::forward(elements)))) + ) const noexcept(noexcept(detail::insert(queue, std::forward(elements), priority_tag<2>{}))) { - container.insert(std::forward(elements)); + detail::insert(queue, std::forward(elements), priority_tag<2>{}); } + }; - template - requires concepts::node> - && requires(Queue& queue) { insert(queue, std::declval()); } - constexpr void operator ()( - Queue& container, - Range&& elements - ) const noexcept(noexcept(insert(container, std::forward(elements)))) + template + requires requires(Queue& queue) { - insert(container, std::forward(elements)); + requires concepts::node{}(queue))>>; } - }; + constexpr decltype(auto) next(Queue& queue, const priority_tag<2>) noexcept(noexcept(customize::next_fn{}(queue))) + { + return customize::next_fn{}(queue); + } - struct next_fn + template + requires requires(Queue& queue) + { + requires concepts::node>; + } + constexpr decltype(auto) next(Queue& queue, const priority_tag<1>) noexcept(noexcept(queue.next())) { - template - requires concepts::node().next())>> - constexpr decltype(auto) operator ()(Queue& queue) const noexcept(noexcept(queue.next())) + return queue.next(); + } + + template + requires requires(Queue& queue) { - return queue.next(); + requires concepts::node>; } + constexpr decltype(auto) next(Queue& queue, const priority_tag<0>) noexcept(noexcept(next(queue))) + { + return next(queue); + } + struct next_fn + { template - requires concepts::node()))>> - constexpr decltype(auto) operator ()(Queue& queue) const noexcept(noexcept(next(queue))) + requires requires(Queue& queue) + { + requires concepts::node{}))>>; + } + constexpr decltype(auto) operator ()(Queue& queue) const noexcept(noexcept(detail::next(queue, priority_tag<2>{}))) { - return next(queue); + return detail::next(queue, priority_tag<2>{}); } }; } diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 9a1a4f8c8..3ff2be77a 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -31,6 +31,11 @@ namespace } }; + struct customized_fun_empty + { + MAKE_CONST_MOCK0(is_empty, bool()); + }; + struct TestNode { // ReSharper disable once CppTypeAliasNeverUsed @@ -55,6 +60,11 @@ namespace } }; + struct customized_fun_insert + { + MAKE_MOCK1(do_insert, void(std::vector)); + }; + struct member_fun_next { MAKE_MOCK0(next, TestNode()); @@ -69,8 +79,42 @@ namespace return obj.get_next(); } }; + + struct customized_fun_next + { + MAKE_MOCK0(get_next, TestNode()); + }; } +template <> +struct sl::graph::customize::empty_fn +{ + [[nodiscard]] + bool operator ()(const customized_fun_empty& e) const + { + return e.is_empty(); + } +}; + +template <> +struct sl::graph::customize::insert_fn +{ + void operator ()(customized_fun_insert& e, std::vector v) const + { + e.do_insert(std::move(v)); +} +}; + +template <> +struct sl::graph::customize::next_fn +{ + [[nodiscard]] + decltype(auto) operator ()(customized_fun_next& e) const + { + return e.get_next(); + } +}; + TEST_CASE("graph::queue::empty serves as a customization point, detmerining whether the queue is empty.", "[graph][graph::queue]") { const bool expected = GENERATE(false, true); @@ -90,6 +134,14 @@ TEST_CASE("graph::queue::empty serves as a customization point, detmerining whet .RETURN(expected); REQUIRE(expected == sg::queue::empty(std::as_const(mock))); } + + SECTION("Access via the customizization point.") + { + customized_fun_empty mock{}; + REQUIRE_CALL(mock, is_empty()) + .RETURN(expected); + REQUIRE(expected == sg::queue::empty(std::as_const(mock))); +} } TEST_CASE("graph::queue::insert serves as a customization point, inserting the range elements.", "[graph][graph::queue]") @@ -113,6 +165,14 @@ TEST_CASE("graph::queue::insert serves as a customization point, inserting the r sg::queue::insert(mock, expected); } + + SECTION("Access via the customization point.") + { + customized_fun_insert mock{}; + REQUIRE_CALL(mock, do_insert(expected)); + + sg::queue::insert(mock, expected); + } } TEST_CASE("graph::queue::next serves as a customization point, retrieving the next node.", "[graph][graph::queue]") @@ -136,6 +196,15 @@ TEST_CASE("graph::queue::next serves as a customization point, retrieving the ne REQUIRE(expected == sg::queue::next(mock)); } + + SECTION("Access via the customization point.") + { + customized_fun_next mock{}; + REQUIRE_CALL(mock, get_next()) + .RETURN(expected); + + REQUIRE(expected == sg::queue::next(mock)); +} } TEMPLATE_TEST_CASE_SIG( From c3e20f74777c87df161d181f8ee35a1c4dd1c69b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 02:24:49 +0200 Subject: [PATCH 054/256] feat: add Stack queue mixin --- .../graph/mixins/queue/Stack.hpp | 43 +++++++++++++++++++ tests/graph/Queue.cpp | 10 +++-- 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 include/Simple-Utility/graph/mixins/queue/Stack.hpp diff --git a/include/Simple-Utility/graph/mixins/queue/Stack.hpp b/include/Simple-Utility/graph/mixins/queue/Stack.hpp new file mode 100644 index 000000000..c5c22130e --- /dev/null +++ b/include/Simple-Utility/graph/mixins/queue/Stack.hpp @@ -0,0 +1,43 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STACK_HPP +#define SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STACK_HPP + +#pragma once + +#include +#include +#include +#include + +#include "Simple-Utility/graph/Queue.hpp" + +template +struct sl::graph::customize::insert_fn> +{ + template + requires std::convertible_to, T> + constexpr void operator ()(std::stack& container, Range&& elements) const + { + for (auto&& element : std::forward(elements)) + { + container.push(std::forward(element)); + } + } +}; + +template +struct sl::graph::customize::next_fn> +{ + constexpr T operator ()(std::stack& container) const + { + auto element = std::move(container.top()); + container.pop(); + return element; + } +}; + +#endif diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 3ff2be77a..3a3ac1dfa 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -4,6 +4,7 @@ // https://www.boost.org/LICENSE_1_0.txt) #include "Simple-Utility/graph/Queue.hpp" +#include "Simple-Utility/graph/mixins/queue/Stack.hpp" #include #include @@ -102,7 +103,7 @@ struct sl::graph::customize::insert_fn void operator ()(customized_fun_insert& e, std::vector v) const { e.do_insert(std::move(v)); -} + } }; template <> @@ -141,7 +142,7 @@ TEST_CASE("graph::queue::empty serves as a customization point, detmerining whet REQUIRE_CALL(mock, is_empty()) .RETURN(expected); REQUIRE(expected == sg::queue::empty(std::as_const(mock))); -} + } } TEST_CASE("graph::queue::insert serves as a customization point, inserting the range elements.", "[graph][graph::queue]") @@ -204,7 +205,7 @@ TEST_CASE("graph::queue::next serves as a customization point, retrieving the ne .RETURN(expected); REQUIRE(expected == sg::queue::next(mock)); -} + } } TEMPLATE_TEST_CASE_SIG( @@ -218,7 +219,8 @@ TEMPLATE_TEST_CASE_SIG( (false, member_fun_next), (false, free_fun_next), (false, QueueMock>), - (true, QueueMock) + (true, QueueMock), + (true, std::stack) ) { STATIC_REQUIRE(expected == sg::concepts::queue_for); From 8dcea0bb68b4fdc0598f3f57e55ac97533a4415e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 21:54:49 +0200 Subject: [PATCH 055/256] feat: priority_tag --- include/Simple-Utility/Utility.hpp | 40 ++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 3 ++- tests/Utility.cpp | 20 +++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 include/Simple-Utility/Utility.hpp create mode 100644 tests/Utility.cpp diff --git a/include/Simple-Utility/Utility.hpp b/include/Simple-Utility/Utility.hpp new file mode 100644 index 000000000..d5c91c591 --- /dev/null +++ b/include/Simple-Utility/Utility.hpp @@ -0,0 +1,40 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include + +namespace sl +{ + /** + * \defgroup GROUP_PRIORITY_TAG priority_tag + * \brief Helper type, useful when prioritize overloads of an overload set. + * \see Got the idea from here: https://quuxplusone.github.io/blog/2021/07/09/priority-tag/ + * \{ + */ + + /** + * \brief Primary template, inheriting from the other specializations with lesser priority. + */ + template + struct priority_tag + : public priority_tag + { + }; + + + /** + * \brief Specialization, denoting the least likely alternative. + */ + template <> + struct priority_tag<0> + { + }; + + /** + * \} + */ +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a66d901ca..5a6d71a81 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,10 +9,11 @@ add_executable( "CRTPBase.cpp" "helper.cpp" "math.cpp" - "unique_handle.cpp" "Tuple.cpp" "TypeList.cpp" "TypeTraits.cpp" + "unique_handle.cpp" + "Utility.cpp" ) add_subdirectory("nullables") diff --git a/tests/Utility.cpp b/tests/Utility.cpp new file mode 100644 index 000000000..fa60f553f --- /dev/null +++ b/tests/Utility.cpp @@ -0,0 +1,20 @@ +#include "Simple-Utility/Utility.hpp" + +#include + +#include + +TEMPLATE_TEST_CASE_SIG( + "Higher priority_tag always inherit from the lower ones.", + "[utility]", + ((bool expected, class Derived, class Base), expected, Derived, Base), + (true, sl::priority_tag<10>, sl::priority_tag<0>), + (true, sl::priority_tag<10>, sl::priority_tag<1>), + (true, sl::priority_tag<1>, sl::priority_tag<0>), + (true, sl::priority_tag<1>, sl::priority_tag<1>), + (false, sl::priority_tag<0>, sl::priority_tag<1>), + (false, sl::priority_tag<1>, sl::priority_tag<10>) +) +{ + STATIC_REQUIRE(expected == std::derived_from); +} From 847cb78003457e39a8a43f3aeda24ac0711422f0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 21:56:18 +0200 Subject: [PATCH 056/256] cleanup: remove priority_tag definition from graph/Queue.hpp --- include/Simple-Utility/graph/Queue.hpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index c5ea2d954..66a023f0a 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -8,6 +8,7 @@ #pragma once +#include "Simple-Utility/Utility.hpp" #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Node.hpp" @@ -27,17 +28,6 @@ namespace sl::graph::customize namespace sl::graph::queue::detail { - template - struct priority_tag - : public priority_tag - { - }; - - template <> - struct priority_tag<0> - { - }; - template requires requires(const T& t) { { customize::empty_fn{}(t) } -> std::convertible_to; } constexpr bool empty(const T& queue, const priority_tag<2>) noexcept(noexcept(customize::empty_fn{}(queue))) From 0aeaf48a007d9f8cfc484e3b3b029d60d865b10d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 22:29:24 +0200 Subject: [PATCH 057/256] fix: make tracker customization points actually customizable --- include/Simple-Utility/graph/Tracker.hpp | 97 +++++++++++++++++++----- tests/graph/Tracker.cpp | 45 +++++++++++ 2 files changed, 121 insertions(+), 21 deletions(-) diff --git a/include/Simple-Utility/graph/Tracker.hpp b/include/Simple-Utility/graph/Tracker.hpp index 050c4a2ae..7b8ad82eb 100644 --- a/include/Simple-Utility/graph/Tracker.hpp +++ b/include/Simple-Utility/graph/Tracker.hpp @@ -8,45 +8,100 @@ #pragma once +#include "Simple-Utility/Utility.hpp" +#include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" +// ReSharper disable once CppUnusedIncludeDirective #include -#include "Simple-Utility/concepts/stl_extensions.hpp" +namespace sl::graph::customize +{ + template + struct set_discovered_fn; + + template + struct set_visited_fn; +} namespace sl::graph::tracker::detail { - struct set_discovered_fn + template + requires requires(T& t, const Vertex& v) { { customize::set_discovered_fn{}(t, v) } -> std::convertible_to; } + constexpr bool set_discovered( + T& tracker, + const Vertex& v, + const priority_tag<2> + ) noexcept(noexcept(customize::set_discovered_fn{}(tracker, v))) { - template - requires requires(T& t, const Vertex& v) { { t.set_discovered(v) } -> std::convertible_to; } - constexpr bool operator ()(T& tracker, const Vertex& v) const noexcept(noexcept(tracker.set_discovered(v))) - { - return tracker.set_discovered(v); - } + return customize::set_discovered_fn{}(tracker, v); + } + + template + requires requires(T& t, const Vertex& v) { { t.set_discovered(v) } -> std::convertible_to; } + constexpr bool set_discovered(T& tracker, const Vertex& v, const priority_tag<1>) noexcept(noexcept(tracker.set_discovered(v))) + { + return tracker.set_discovered(v); + } + + template + requires requires(T& t, const Vertex& v) { { set_discovered(t, v) } -> std::convertible_to; } + constexpr bool set_discovered(T& tracker, const Vertex& v, const priority_tag<0>) noexcept(noexcept(set_discovered(tracker, v))) + { + return set_discovered(tracker, v); + } + struct set_discovered_fn + { template - requires requires(T& t, const Vertex& v) { { set_discovered(t, v) } -> std::convertible_to; } - constexpr bool operator ()(T& tracker, const Vertex& v) const noexcept(noexcept(set_discovered(tracker, v))) + requires requires(T& t, const Vertex& v) + { + { detail::set_discovered(t, v, priority_tag<2>{}) } -> std::convertible_to; + } + constexpr bool operator ()( + T& tracker, + const Vertex& v + ) const noexcept(noexcept(detail::set_discovered(tracker, v, priority_tag<2>{}))) { - return set_discovered(tracker, v); + return detail::set_discovered(tracker, v, priority_tag<2>{}); } }; - struct set_visited_fn + template + requires requires(T& t, const Vertex& v) { customize::set_visited_fn{}(t, v); } + constexpr void set_visited( + T& tracker, + const Vertex& v, + const priority_tag<2> + ) noexcept(noexcept(customize::set_visited_fn{}(tracker, v))) { - template - requires requires(T& t, const Vertex& v) { t.set_visited(v); } - constexpr void operator ()(T& tracker, const Vertex& v) const noexcept(noexcept(tracker.set_visited(v))) - { - tracker.set_visited(v); - } + customize::set_visited_fn{}(tracker, v); + } + template + requires requires(T& t, const Vertex& v) { t.set_visited(v); } + constexpr void set_visited(T& tracker, const Vertex& v, const priority_tag<1>) noexcept(noexcept(tracker.set_visited(v))) + { + tracker.set_visited(v); + } + + template + requires requires(T& t, const Vertex& v) { set_visited(t, v); } + constexpr void set_visited(T& tracker, const Vertex& v, const priority_tag<0>) noexcept(noexcept(set_visited(tracker, v))) + { + set_visited(tracker, v); + } + + struct set_visited_fn + { template - requires requires(T& t, const Vertex& v) { set_visited(t, v); } - constexpr void operator ()(T& tracker, const Vertex& v) const noexcept(noexcept(set_visited(tracker, v))) + requires requires(T& t, const Vertex& v) { detail::set_visited(t, v, priority_tag<2>{}); } + constexpr void operator ()( + T& tracker, + const Vertex& v + ) const noexcept(noexcept(detail::set_visited(tracker, v, priority_tag<2>{}))) { - set_visited(tracker, v); + detail::set_visited(tracker, v, priority_tag<2>{}); } }; } diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index b78b7b34c..c7b0745ac 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -33,6 +33,11 @@ namespace } }; + struct customized_set_discovered + { + MAKE_MOCK1(do_set_discovered, bool(int)); + }; + struct member_fun_set_visited { MAKE_MOCK1(set_visited, void(int)); @@ -47,8 +52,32 @@ namespace return obj.do_set_visited(vertex); } }; + + struct customized_set_visited + { + MAKE_MOCK1(do_set_visited, void(int)); + }; } +template <> +struct sl::graph::customize::set_discovered_fn +{ + [[nodiscard]] + bool operator ()(customized_set_discovered& e, const int v) const + { + return e.do_set_discovered(v); + } +}; + +template <> +struct sl::graph::customize::set_visited_fn +{ + void operator ()(customized_set_visited& e, const int v) const + { + e.do_set_visited(v); + } +}; + TEST_CASE("graph::queue::set_discovered serves as a customization point, modifing the tracker state.", "[graph][graph::tracker]") { const int vertex = GENERATE(take(5, random(0, std::numeric_limits::max()))); @@ -69,6 +98,14 @@ TEST_CASE("graph::queue::set_discovered serves as a customization point, modifin .RETURN(expected); REQUIRE(expected == sg::tracker::set_discovered(mock, vertex)); } + + SECTION("Access via customized function.") + { + customized_set_discovered mock{}; + REQUIRE_CALL(mock, do_set_discovered(vertex)) + .RETURN(expected); + REQUIRE(expected == sg::tracker::set_discovered(mock, vertex)); + } } TEST_CASE("graph::queue::set_visited serves as a customization point, modifing the tracker state.", "[graph][graph::tracker]") @@ -90,6 +127,14 @@ TEST_CASE("graph::queue::set_visited serves as a customization point, modifing t sg::tracker::set_visited(mock, vertex); } + + SECTION("Access via custom function.") + { + customized_set_visited mock{}; + REQUIRE_CALL(mock, do_set_visited(vertex)); + + sg::tracker::set_visited(mock, vertex); + } } TEMPLATE_TEST_CASE_SIG( From f18381e3e45255658be935cf18dce15958a5be97 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 22:44:08 +0200 Subject: [PATCH 058/256] refactor: rename graph/mixins/queue/Stack.hpp to std_stack.hpp --- .../graph/mixins/queue/{Stack.hpp => std_stack.hpp} | 4 ++-- tests/graph/Queue.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename include/Simple-Utility/graph/mixins/queue/{Stack.hpp => std_stack.hpp} (90%) diff --git a/include/Simple-Utility/graph/mixins/queue/Stack.hpp b/include/Simple-Utility/graph/mixins/queue/std_stack.hpp similarity index 90% rename from include/Simple-Utility/graph/mixins/queue/Stack.hpp rename to include/Simple-Utility/graph/mixins/queue/std_stack.hpp index c5c22130e..8003082db 100644 --- a/include/Simple-Utility/graph/mixins/queue/Stack.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_stack.hpp @@ -3,8 +3,8 @@ // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) -#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STACK_HPP -#define SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STACK_HPP +#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STD_STACK_HPP +#define SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STD_STACK_HPP #pragma once diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 3a3ac1dfa..419edad7a 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -4,7 +4,7 @@ // https://www.boost.org/LICENSE_1_0.txt) #include "Simple-Utility/graph/Queue.hpp" -#include "Simple-Utility/graph/mixins/queue/Stack.hpp" +#include "Simple-Utility/graph/mixins/queue/std_stack.hpp" #include #include From a98f636084bf035e4ded608e0b0fcb19b530b0c5 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 23:17:32 +0200 Subject: [PATCH 059/256] feat: add std::map and std::unordered_map as tracker mixin --- .../graph/mixins/tracker/std_map.hpp | 38 ++++++++++++++++ .../mixins/tracker/std_unordered_map.hpp | 38 ++++++++++++++++ tests/graph/Tracker.cpp | 44 ++++++++++++++++++- 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 include/Simple-Utility/graph/mixins/tracker/std_map.hpp create mode 100644 include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp diff --git a/include/Simple-Utility/graph/mixins/tracker/std_map.hpp b/include/Simple-Utility/graph/mixins/tracker/std_map.hpp new file mode 100644 index 000000000..6e0b480ab --- /dev/null +++ b/include/Simple-Utility/graph/mixins/tracker/std_map.hpp @@ -0,0 +1,38 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_TRACKER_STD_MAP_HPP +#define SIMPLE_UTILITY_GRAPH_MIXINS_TRACKER_STD_MAP_HPP + +#pragma once + +#include +#include + +#include "Simple-Utility/graph/Tracker.hpp" + +template +struct sl::graph::customize::set_discovered_fn> +{ + template K> + bool operator ()(std::map& container, K&& vertex) const + { + const auto [_, inserted] = container.try_emplace(std::forward(vertex), false); + return !inserted; + } +}; + +template +struct sl::graph::customize::set_visited_fn> +{ + template K> + constexpr void operator ()(std::map& container, K&& vertex) const + { + const auto [iter, inserted] = container.insert_or_assign(std::forward(vertex), true); + assert(!inserted && "Visited a vertex which hasn't been discovered yet."); + } +}; + +#endif diff --git a/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp b/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp new file mode 100644 index 000000000..90fb65fe5 --- /dev/null +++ b/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp @@ -0,0 +1,38 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_TRACKER_STD_UNORDERED_MAP_HPP +#define SIMPLE_UTILITY_GRAPH_MIXINS_TRACKER_STD_UNORDERED_MAP_HPP + +#pragma once + +#include +#include + +#include "Simple-Utility/graph/Tracker.hpp" + +template +struct sl::graph::customize::set_discovered_fn> +{ + template K> + bool operator ()(std::unordered_map& container, K&& vertex) + { + const auto [_, inserted] = container.try_emplace(std::forward(vertex), false); + return !inserted; + } +}; + +template +struct sl::graph::customize::set_visited_fn> +{ + template K> + constexpr void operator ()(std::unordered_map& container, K&& vertex) const + { + const auto [iter, inserted] = container.insert_or_assign(std::forward(vertex), true); + assert(!inserted && "Visited a vertex which hasn't been discovered yet."); + } +}; + +#endif diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index c7b0745ac..f4a6a7219 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -12,6 +12,9 @@ #include "Defines.hpp" +#include "Simple-Utility/graph/mixins/tracker/std_map.hpp" +#include "Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp" + #include // ReSharper disable CppDeclaratorNeverUsed @@ -146,8 +149,47 @@ TEMPLATE_TEST_CASE_SIG( (false, member_fun_set_visited, int), (false, free_fun_set_visited, int), (false, TrackerMock, std::string), - (true, TrackerMock, int) + (true, TrackerMock, int), + (true, std::unordered_map, int), + (true, std::map, int) ) { STATIC_REQUIRE(expected == sg::concepts::tracker_for); } + +TEMPLATE_TEST_CASE( + "Concrete tracker types behave as expected.", + "[graph][graph::concepts][graph::tracker]", + (std::unordered_map), + (std::map) +) +{ + TestType tracker{}; + + SECTION("Discovering a new vertex yields false.") + { + const int vertex = GENERATE(take(5, random(std::numeric_limits::min() + 1, std::numeric_limits::max()))); + + REQUIRE(!sg::tracker::set_discovered(tracker, vertex)); + + SECTION("Discovering the same vertex again, yields true.") + { + REQUIRE(sg::tracker::set_discovered(tracker, vertex)); + } + + SECTION("Discovering another vertex yields false.") + { + REQUIRE(!sg::tracker::set_discovered(tracker, -vertex)); + } + + SECTION("Visiting a discovered vertex is expected.") + { + sg::tracker::set_visited(tracker, vertex); + + SECTION("Discovering an already visited vertex yields true.") + { + REQUIRE(sg::tracker::set_discovered(tracker, vertex)); + } + } + } +} From 6660ec4720f7d599e2df47c1b3aac0529ff93992 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 23 Aug 2023 23:56:35 +0200 Subject: [PATCH 060/256] fix: correct tracker protocol --- include/Simple-Utility/graph/Tracker.hpp | 28 +++++----- .../graph/mixins/tracker/std_map.hpp | 12 ++--- .../mixins/tracker/std_unordered_map.hpp | 14 ++--- tests/graph/Defines.hpp | 2 +- tests/graph/Node.cpp | 2 +- tests/graph/Tracker.cpp | 51 +++++++++++-------- 6 files changed, 60 insertions(+), 49 deletions(-) diff --git a/include/Simple-Utility/graph/Tracker.hpp b/include/Simple-Utility/graph/Tracker.hpp index 7b8ad82eb..eddbf7b44 100644 --- a/include/Simple-Utility/graph/Tracker.hpp +++ b/include/Simple-Utility/graph/Tracker.hpp @@ -58,6 +58,7 @@ namespace sl::graph::tracker::detail { { detail::set_discovered(t, v, priority_tag<2>{}) } -> std::convertible_to; } + [[nodiscard]] constexpr bool operator ()( T& tracker, const Vertex& v @@ -68,40 +69,41 @@ namespace sl::graph::tracker::detail }; template - requires requires(T& t, const Vertex& v) { customize::set_visited_fn{}(t, v); } - constexpr void set_visited( + requires requires(T& t, const Vertex& v) { { customize::set_visited_fn{}(t, v) } -> std::convertible_to; } + constexpr bool set_visited( T& tracker, const Vertex& v, const priority_tag<2> ) noexcept(noexcept(customize::set_visited_fn{}(tracker, v))) { - customize::set_visited_fn{}(tracker, v); + return customize::set_visited_fn{}(tracker, v); } template - requires requires(T& t, const Vertex& v) { t.set_visited(v); } - constexpr void set_visited(T& tracker, const Vertex& v, const priority_tag<1>) noexcept(noexcept(tracker.set_visited(v))) + requires requires(T& t, const Vertex& v) { { t.set_visited(v) } -> std::convertible_to; } + constexpr bool set_visited(T& tracker, const Vertex& v, const priority_tag<1>) noexcept(noexcept(tracker.set_visited(v))) { - tracker.set_visited(v); + return tracker.set_visited(v); } template - requires requires(T& t, const Vertex& v) { set_visited(t, v); } - constexpr void set_visited(T& tracker, const Vertex& v, const priority_tag<0>) noexcept(noexcept(set_visited(tracker, v))) + requires requires(T& t, const Vertex& v) { { set_visited(t, v) } -> std::convertible_to; } + constexpr bool set_visited(T& tracker, const Vertex& v, const priority_tag<0>) noexcept(noexcept(set_visited(tracker, v))) { - set_visited(tracker, v); + return set_visited(tracker, v); } struct set_visited_fn { template - requires requires(T& t, const Vertex& v) { detail::set_visited(t, v, priority_tag<2>{}); } - constexpr void operator ()( + requires requires(T& t, const Vertex& v) { { detail::set_visited(t, v, priority_tag<2>{}) } -> std::convertible_to; } + [[nodiscard]] + constexpr bool operator ()( T& tracker, const Vertex& v ) const noexcept(noexcept(detail::set_visited(tracker, v, priority_tag<2>{}))) { - detail::set_visited(tracker, v, priority_tag<2>{}); + return detail::set_visited(tracker, v, priority_tag<2>{}); } }; } @@ -120,7 +122,7 @@ namespace sl::graph::concepts && requires(T& tracker, const Vertex& v) { { tracker::set_discovered(tracker, v) } -> std::convertible_to; - tracker::set_visited(tracker, v); + { tracker::set_visited(tracker, v) } -> std::convertible_to; }; } diff --git a/include/Simple-Utility/graph/mixins/tracker/std_map.hpp b/include/Simple-Utility/graph/mixins/tracker/std_map.hpp index 6e0b480ab..62c5b4db3 100644 --- a/include/Simple-Utility/graph/mixins/tracker/std_map.hpp +++ b/include/Simple-Utility/graph/mixins/tracker/std_map.hpp @@ -19,19 +19,19 @@ struct sl::graph::customize::set_discovered_fn K> bool operator ()(std::map& container, K&& vertex) const { - const auto [_, inserted] = container.try_emplace(std::forward(vertex), false); - return !inserted; + const auto [iter, inserted] = container.try_emplace(std::forward(vertex), false); + return inserted || !iter->second; } }; template struct sl::graph::customize::set_visited_fn> { - template K> - constexpr void operator ()(std::map& container, K&& vertex) const + bool operator ()(std::map& container, const Key& vertex) const { - const auto [iter, inserted] = container.insert_or_assign(std::forward(vertex), true); - assert(!inserted && "Visited a vertex which hasn't been discovered yet."); + const auto iter = container.find(vertex); + assert(iter != std::cend(container) && "Visited a vertex which hasn't been discovered yet."); + return !std::exchange(iter->second, true); } }; diff --git a/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp b/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp index 90fb65fe5..e1b9c31d1 100644 --- a/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp +++ b/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp @@ -17,21 +17,21 @@ template > { template K> - bool operator ()(std::unordered_map& container, K&& vertex) + bool operator ()(std::unordered_map& container, K&& vertex) const { - const auto [_, inserted] = container.try_emplace(std::forward(vertex), false); - return !inserted; + const auto [iter, inserted] = container.try_emplace(std::forward(vertex), false); + return inserted || !iter->second; } }; template struct sl::graph::customize::set_visited_fn> { - template K> - constexpr void operator ()(std::unordered_map& container, K&& vertex) const + bool operator ()(std::unordered_map& container, const Key& vertex) const { - const auto [iter, inserted] = container.insert_or_assign(std::forward(vertex), true); - assert(!inserted && "Visited a vertex which hasn't been discovered yet."); + const auto iter = container.find(vertex); + assert(iter != std::cend(container) && "Visited a vertex which hasn't been discovered yet."); + return !std::exchange(iter->second, true); } }; diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 50b3941bf..905c17f97 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -81,7 +81,7 @@ class TrackerMock inline static constexpr bool trompeloeil_movable_mock = true; MAKE_MOCK1(set_discovered, bool(const Vertex&)); - MAKE_MOCK1(set_visited, void(const Vertex&)); + MAKE_MOCK1(set_visited, bool(const Vertex&)); }; template diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index ef1267972..0965e5eb5 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -70,7 +70,7 @@ namespace std::vector neighbor_infos(const sg::concepts::node auto&) const { return {}; -} + } }; } diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index f4a6a7219..1df3173bb 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -43,14 +43,14 @@ namespace struct member_fun_set_visited { - MAKE_MOCK1(set_visited, void(int)); + MAKE_MOCK1(set_visited, bool(int)); }; struct free_fun_set_visited { - MAKE_MOCK1(do_set_visited, void(int)); + MAKE_MOCK1(do_set_visited, bool(int)); - friend void set_visited(free_fun_set_visited& obj, const int vertex) + friend bool set_visited(free_fun_set_visited& obj, const int vertex) { return obj.do_set_visited(vertex); } @@ -58,7 +58,7 @@ namespace struct customized_set_visited { - MAKE_MOCK1(do_set_visited, void(int)); + MAKE_MOCK1(do_set_visited, bool(int)); }; } @@ -75,9 +75,9 @@ struct sl::graph::customize::set_discovered_fn template <> struct sl::graph::customize::set_visited_fn { - void operator ()(customized_set_visited& e, const int v) const + bool operator ()(customized_set_visited& e, const int v) const { - e.do_set_visited(v); + return e.do_set_visited(v); } }; @@ -114,29 +114,33 @@ TEST_CASE("graph::queue::set_discovered serves as a customization point, modifin TEST_CASE("graph::queue::set_visited serves as a customization point, modifing the tracker state.", "[graph][graph::tracker]") { const int vertex = GENERATE(take(5, random(0, std::numeric_limits::max()))); + const bool expected = GENERATE(true, false); SECTION("Access via the member function.") { member_fun_set_visited mock{}; - REQUIRE_CALL(mock, set_visited(vertex)); + REQUIRE_CALL(mock, set_visited(vertex)) + .RETURN(expected); - sg::tracker::set_visited(mock, vertex); + REQUIRE(expected == sg::tracker::set_visited(mock, vertex)); } SECTION("Access via the free function.") { free_fun_set_visited mock{}; - REQUIRE_CALL(mock, do_set_visited(vertex)); + REQUIRE_CALL(mock, do_set_visited(vertex)) + .RETURN(expected); - sg::tracker::set_visited(mock, vertex); + REQUIRE(expected == sg::tracker::set_visited(mock, vertex)); } SECTION("Access via custom function.") { customized_set_visited mock{}; - REQUIRE_CALL(mock, do_set_visited(vertex)); + REQUIRE_CALL(mock, do_set_visited(vertex)) + .RETURN(expected); - sg::tracker::set_visited(mock, vertex); + REQUIRE(expected == sg::tracker::set_visited(mock, vertex)); } } @@ -166,29 +170,34 @@ TEMPLATE_TEST_CASE( { TestType tracker{}; - SECTION("Discovering a new vertex yields false.") + SECTION("Discovering a new vertex yields true.") { const int vertex = GENERATE(take(5, random(std::numeric_limits::min() + 1, std::numeric_limits::max()))); - REQUIRE(!sg::tracker::set_discovered(tracker, vertex)); + REQUIRE(sg::tracker::set_discovered(tracker, vertex)); - SECTION("Discovering the same vertex again, yields true.") + SECTION("Discovering the same vertex again, yields also true.") { REQUIRE(sg::tracker::set_discovered(tracker, vertex)); } - SECTION("Discovering another vertex yields false.") + SECTION("Discovering another vertex yields true.") { - REQUIRE(!sg::tracker::set_discovered(tracker, -vertex)); + REQUIRE(sg::tracker::set_discovered(tracker, -vertex)); } - SECTION("Visiting a discovered vertex is expected.") + SECTION("Visiting a discovered vertex yields true.") { - sg::tracker::set_visited(tracker, vertex); + REQUIRE(sg::tracker::set_visited(tracker, vertex)); + + SECTION("Discovering an already visited vertex, yields false.") + { + REQUIRE(!sg::tracker::set_discovered(tracker, vertex)); + } - SECTION("Discovering an already visited vertex yields true.") + SECTION("Visiting an already visited vertex, yields false.") { - REQUIRE(sg::tracker::set_discovered(tracker, vertex)); + REQUIRE(!sg::tracker::set_visited(tracker, vertex)); } } } From 4f45139cf12794ded7d4b858020497be56585535 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 00:02:17 +0200 Subject: [PATCH 061/256] extend BasicDriver implementation --- include/Simple-Utility/graph/Traverse.hpp | 97 +++++++++----- tests/graph/Traverse.cpp | 149 ++++++++++++++++++---- 2 files changed, 189 insertions(+), 57 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 994105fae..8f779d721 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -17,7 +17,9 @@ #include "Simple-Utility/graph/Tracker.hpp" #include +#include #include +#include #include #include #include @@ -32,16 +34,13 @@ namespace sl::graph::detail using queue_type = QueuingStrategy; [[nodiscard]] - constexpr explicit BasicState(node_type origin) - : BasicState{std::move(origin), {}} - { - } + constexpr explicit BasicState() = default; [[nodiscard]] - constexpr explicit BasicState(node_type origin, queue_type queue) + constexpr explicit BasicState(queue_type queue) noexcept(std::is_nothrow_move_constructible_v) : m_Queue{std::move(queue)} { - queue::insert(m_Queue, std::array{std::move(origin)}); + assert(queue::empty(m_Queue) && "Queue contains already elements."); } [[nodiscard]] @@ -68,44 +67,66 @@ namespace sl::graph::detail QueuingStrategy m_Queue{}; }; + template + concept state_for = sl::concepts::unqualified + && std::destructible + && concepts::node + && requires(T& state, const std::forward_list& inputRange) + { + { !state.next(inputRange) } -> std::convertible_to; + { *state.next(inputRange) } -> std::convertible_to; + }; + template < concepts::node Node, - class StateStrategy, - concepts::tracker_for TrackingStrategy, - concepts::node_factory_for NodeFactoryStrategy - > + state_for StateStrategy, + concepts::tracker_for> TrackingStrategy, + concepts::node_factory_for NodeFactoryStrategy> class BasicTraverseDriver { public: using node_type = Node; using vertex_type = feature_vertex_t; + using state_type = StateStrategy; + using tracker_type = TrackingStrategy; + using node_factory_type = NodeFactoryStrategy; - template [[nodiscard]] - explicit BasicTraverseDriver(vertex_type origin, OtherArgs&&... otherArgs) - : m_Current{m_NodeFactory.make_init_node(std::move(origin), std::forward(otherArgs)...)}, - m_State{m_Current} + explicit BasicTraverseDriver( + const vertex_type& origin, + tracker_type tracker = tracker_type{}, + node_factory_type nodeFactory = node_factory_type{}, + state_type state = state_type{} + ) + : m_Tracker{std::move(tracker)}, + m_NodeFactory{std::move(nodeFactory)}, + m_Current{m_NodeFactory.make_init_node(std::move(origin))}, + m_State{std::move(state)} { - tracker::set_discovered(m_Tracker, node::vertex(m_Current)); + const bool result = tracker::set_visited(m_Tracker, node::vertex(m_Current)); + assert(result && "Tracker returned false (already visited) the origin node."); } - template [[nodiscard]] - constexpr std::optional next(const Graph& graph) + constexpr std::optional next(const concepts::graph_for auto& graph) { - if (auto result = m_State.next( + auto result = m_State.next( graph.neighbor_infos(m_Current) - | std::views::filter([&](const auto& info) { return !tracker::set_discovered(m_Tracker, info.vertex); }) + | std::views::filter( + [&](const auto& info) { return tracker::set_discovered(m_Tracker, node::vertex(info)); }) | std::views::transform( - functional::envelop( - [&](Ts&&... infos) - { - return m_NodeFactory.make_successor_node(m_Current, std::forward(infos)...); - })))) + [&](const auto& info) + { + return m_NodeFactory.make_successor_node(m_Current, info); + })); + + for (; result; result = m_State.next(std::array{})) { - m_Current = *result; - tracker::set_visited(m_Tracker, node::vertex(m_Current)); - return result; + if (tracker::set_visited(m_Tracker, node::vertex(*result))) + { + m_Current = *result; + return result; + } } return std::nullopt; @@ -117,11 +138,29 @@ namespace sl::graph::detail return m_Current; } + [[nodiscard]] + constexpr const TrackingStrategy& tracker() const noexcept + { + return m_Tracker; + } + + [[nodiscard]] + constexpr const NodeFactoryStrategy& node_factory() const noexcept + { + return m_NodeFactory; + } + + [[nodiscard]] + constexpr const StateStrategy& state() const noexcept + { + return m_State; + } + private: - Node m_Current{}; - StateStrategy m_State{}; SL_UTILITY_NO_UNIQUE_ADDRESS TrackingStrategy m_Tracker{}; SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactoryStrategy m_NodeFactory{}; + Node m_Current{}; + StateStrategy m_State{}; }; } diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index d94809879..35e9a3d5c 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -28,7 +28,7 @@ namespace int vertex{}; - friend bool operator==(const VertexMemberNode& lhs, const VertexMemberNode& rhs) { return lhs.vertex == rhs.vertex; }; + friend bool operator==(const VertexMemberNode& lhs, const VertexMemberNode& rhs) { return lhs.vertex == rhs.vertex; } }; struct DefaultConstructibleQueue @@ -59,40 +59,44 @@ namespace using DefaultNode = VertexMemberNode; using DefaultQueue = QueueMock; using DefaultState = sg::detail::BasicState; - -TEST_CASE("BasicState is constructible from origin node and queue argument.", "[graph][graph::traverse][graph::detail]") -{ - using trompeloeil::_; - - const int originVertex = GENERATE(take(5, random(std::numeric_limits::min(), std::numeric_limits::max()))); - - DefaultQueue queueMock{}; - REQUIRE_CALL(queueMock, do_insert(_)) - .WITH(std::ssize(_1) == 1) - .WITH(sg::node::vertex(_1[0]) == originVertex); - - const DefaultState state{{originVertex}, std::move(queueMock)}; -} - -TEST_CASE("BasicState is constructible from origin node.", "[graph][graph::traverse][graph::detail]") +using DefaultTracker = TrackerMock>; +using DefaultGraph = BasicGraph; +using DefaultNodeFactory = BasicNodeFactoryMock; +using DefaultDriver = sg::detail::BasicTraverseDriver< + DefaultNode, + DefaultState, + DefaultTracker, + DefaultNodeFactory>; + +TEST_CASE("BasicState is constructible.", "[graph][graph::traverse][graph::detail]") { - const int originVertex = GENERATE(take(5, random(std::numeric_limits::min(), std::numeric_limits::max()))); - - const sg::detail::BasicState state{{originVertex}}; + SECTION("Default constructible.") + { + const DefaultState state{}; + } - REQUIRE(originVertex == sg::node::vertex(state.queue().queue.front())); + SECTION("Constructible with queue object as argument.") + { + DefaultQueue queue{}; + ALLOW_CALL(queue, empty()) // allow debug assertion + .RETURN(true); + const DefaultState state{std::move(queue)}; + } } -TEST_CASE("BasicState::next accepts current neighbors and returns the next node, if present.", "[graph][graph::traverse][graph::detail]") +TEST_CASE( + "BasicState::next accepts current neighbors and returns the next node, if present.", + "[graph][graph::traverse][graph::detail]") { using trompeloeil::_; - using namespace trompeloeil_ext; + using namespace trompeloeil_ext; using namespace Catch::Matchers; using namespace catch_ext; - DefaultQueue queueMock{}; - REQUIRE_CALL(queueMock, do_insert(matches(RangeEquals(std::array{DefaultNode{42}})))); - DefaultState state{{42}, std::move(queueMock)}; + DefaultQueue queue{}; + ALLOW_CALL(queue, empty()) // allow debug assertion + .RETURN(true); + DefaultState state{std::move(queue)}; SECTION("Providing neighbor infos on next call.") { @@ -108,10 +112,10 @@ TEST_CASE("BasicState::next accepts current neighbors and returns the next node, .IN_SEQUENCE(seq); REQUIRE_CALL(queueMember, next()) - .RETURN(DefaultNode{42}) + .RETURN(DefaultNode{44}) .IN_SEQUENCE(seq); - REQUIRE(DefaultNode{42} == state.next(neighbors)); + REQUIRE(DefaultNode{44} == state.next(neighbors)); SECTION("And when the internal queue depletes.") { @@ -126,3 +130,92 @@ TEST_CASE("BasicState::next accepts current neighbors and returns the next node, } } } + +TEST_CASE("BasicTraverseDriver can be constructed with an origin.", "[graph][graph::traverse][graph::detail]") +{ + using namespace trompeloeil_ext; + using namespace Catch::Matchers; + + const auto origin = GENERATE(take(5, random(std::numeric_limits::min(), std::numeric_limits::max()))); + + DefaultNodeFactory nodeFactoryMock{}; + REQUIRE_CALL(nodeFactoryMock, make_init_node(origin)) + .RETURN(DefaultNode{.vertex = origin}); + + DefaultTracker trackerMock{}; + REQUIRE_CALL(trackerMock, set_visited(origin)) + .RETURN(true); + + const DefaultDriver driver{origin, std::move(trackerMock), std::move(nodeFactoryMock)}; + + REQUIRE(DefaultNode{.vertex = origin} == driver.current_node()); +} + +TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt.", "[graph][graph::traverse][graph::detail]") +{ + using namespace trompeloeil_ext; + using namespace catch_ext; + using namespace Catch::Matchers; + + constexpr DefaultNode originNode{.vertex = 42}; + auto driver = [&] + { + DefaultNodeFactory nodeFactoryMock{}; + REQUIRE_CALL(nodeFactoryMock, make_init_node(42)) + .RETURN(originNode); + + DefaultTracker trackerMock{}; + REQUIRE_CALL(trackerMock, set_visited(42)) + .RETURN(true); + + return DefaultDriver{42, std::move(trackerMock), std::move(nodeFactoryMock)}; + }(); + + using VertexInfo = DefaultGraph::info; + DefaultGraph graphMock{}; + auto& nodeFactoryMock = const_cast(driver.node_factory()); + auto& queueMock = const_cast(driver.state().queue()); + auto& trackerMock = const_cast(driver.tracker()); + + SECTION("Next returns a node.") + { + REQUIRE_CALL(graphMock, neighbor_infos(originNode)) + .RETURN(std::vector{{41}, {43}, {44}}); + + REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{41})) + .RETURN(DefaultNode{.vertex = 41}); + REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{43})) + .RETURN(DefaultNode{.vertex = 43}); + REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{44})) + .RETURN(DefaultNode{.vertex = 44}); + + REQUIRE_CALL(queueMock, do_insert(matches(RangeEquals(std::vector{{41}, {43}, {44}})))); + REQUIRE_CALL(queueMock, empty()) + .RETURN(false); + REQUIRE_CALL(queueMock, next()) + .RETURN(DefaultNode{.vertex = 41}); + + REQUIRE_CALL(trackerMock, set_discovered(41)) + .RETURN(true); + REQUIRE_CALL(trackerMock, set_discovered(43)) + .RETURN(true); + REQUIRE_CALL(trackerMock, set_discovered(44)) + .RETURN(true); + REQUIRE_CALL(trackerMock, set_visited(41)) + .RETURN(true); + + REQUIRE(DefaultNode{.vertex = 41} == driver.next(std::as_const(graphMock))); + } + + SECTION("Next returns std::nullopt.") + { + REQUIRE_CALL(graphMock, neighbor_infos(originNode)) + .RETURN(std::vector{}); + + REQUIRE_CALL(queueMock, do_insert(matches(RangesEmpty{}))); + REQUIRE_CALL(queueMock, empty()) + .RETURN(true); + + REQUIRE(std::nullopt == driver.next(std::as_const(graphMock))); + } +} From 1b24a956ade26c38d6db1d41c0732e821f3dbc16 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 00:03:45 +0200 Subject: [PATCH 062/256] fix: quirky assert message --- include/Simple-Utility/graph/Traverse.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 8f779d721..33e0da447 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -40,7 +40,7 @@ namespace sl::graph::detail constexpr explicit BasicState(queue_type queue) noexcept(std::is_nothrow_move_constructible_v) : m_Queue{std::move(queue)} { - assert(queue::empty(m_Queue) && "Queue contains already elements."); + assert(queue::empty(m_Queue) && "Queue already contains elements."); } [[nodiscard]] From deb727d4cd91e21c09fcc89d4386b31cde59baa3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 00:44:32 +0200 Subject: [PATCH 063/256] fix: please msvc v142 --- include/Simple-Utility/graph/Traverse.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 33e0da447..d5b3251f8 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -107,10 +107,11 @@ namespace sl::graph::detail assert(result && "Tracker returned false (already visited) the origin node."); } + template Graph> // let the concept here, because otherwise it results in an ICE on msvc v142 [[nodiscard]] - constexpr std::optional next(const concepts::graph_for auto& graph) + constexpr std::optional next(const Graph& graph) { - auto result = m_State.next( + std::optional result = m_State.next( graph.neighbor_infos(m_Current) | std::views::filter( [&](const auto& info) { return tracker::set_discovered(m_Tracker, node::vertex(info)); }) From 388ebe249932ac9bd16557fbcd221da7eabf5402 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 00:44:47 +0200 Subject: [PATCH 064/256] cleanup: tests/traverse.cpp --- tests/graph/Traverse.cpp | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 35e9a3d5c..52d3d2d74 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -28,31 +28,7 @@ namespace int vertex{}; - friend bool operator==(const VertexMemberNode& lhs, const VertexMemberNode& rhs) { return lhs.vertex == rhs.vertex; } - }; - - struct DefaultConstructibleQueue - { - std::vector queue{}; - - [[nodiscard]] - bool empty() const noexcept - { - return queue.empty(); - } - - void insert(const auto& elements) - { - queue.insert(std::end(queue), std::ranges::begin(elements), std::ranges::end(elements)); - } - - [[nodiscard]] - VertexMemberNode next() - { - const VertexMemberNode front = queue.front(); - queue.erase(std::begin(queue)); - return front; - } + friend bool operator==(const VertexMemberNode&, const VertexMemberNode&) = default; }; } From 88e530130ea98d2f7db480669756e685ce29f8be Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 01:26:08 +0200 Subject: [PATCH 065/256] fix: replace view approach on for non supporting compilers --- include/Simple-Utility/graph/Traverse.hpp | 54 ++++++++++++++++++----- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index d5b3251f8..dfb9e1dd5 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -77,6 +77,49 @@ namespace sl::graph::detail { *state.next(inputRange) } -> std::convertible_to; }; +#if (defined(__clang__) && __clang_major__ < 16) \ + || (defined(__GNUG__) && __GNUG__ < 12) + + constexpr auto filterTransform = []( + const Source& neighbors, + auto& tracker, + auto& nodeFactory, + const Node& currentNode + ) + { + std::vector nodes{}; + if constexpr (std::ranges::sized_range) + { + nodes.reserve(std::ranges::size(neighbors)); + } + + for (const auto& info : neighbors) + { + if (tracker::set_discovered(tracker, node::vertex(info))) + { + nodes.emplace_back(nodeFactory.make_successor_node(currentNode, info)); + } + } + + return nodes; + }; + +#else + + constexpr auto filterTransform = []( + Source&& neighbors, + auto& tracker, + auto& nodeFactory, + const auto& currentNode + ) + { + return std::forward(neighbors) + | std::views::filter([&](const auto& info) { return tracker::set_discovered(tracker, node::vertex(info)); }) + | std::views::transform([&](const auto& info) { return nodeFactory.make_successor_node(currentNode, info); }); + }; + +#endif + template < concepts::node Node, state_for StateStrategy, @@ -111,16 +154,7 @@ namespace sl::graph::detail [[nodiscard]] constexpr std::optional next(const Graph& graph) { - std::optional result = m_State.next( - graph.neighbor_infos(m_Current) - | std::views::filter( - [&](const auto& info) { return tracker::set_discovered(m_Tracker, node::vertex(info)); }) - | std::views::transform( - [&](const auto& info) - { - return m_NodeFactory.make_successor_node(m_Current, info); - })); - + std::optional result = m_State.next(filterTransform(graph.neighbor_infos(m_Current), m_Tracker, m_NodeFactory, m_Current)); for (; result; result = m_State.next(std::array{})) { if (tracker::set_visited(m_Tracker, node::vertex(*result))) From de59ed11f8bfe501e864b7ab4d2caed4302b8fff Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 01:35:50 +0200 Subject: [PATCH 066/256] test: tweak single test case --- tests/graph/Traverse.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 52d3d2d74..efe818180 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -155,17 +155,16 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." SECTION("Next returns a node.") { + // vertex 43 will be skipped on purpose REQUIRE_CALL(graphMock, neighbor_infos(originNode)) .RETURN(std::vector{{41}, {43}, {44}}); REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{41})) .RETURN(DefaultNode{.vertex = 41}); - REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{43})) - .RETURN(DefaultNode{.vertex = 43}); REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{44})) .RETURN(DefaultNode{.vertex = 44}); - REQUIRE_CALL(queueMock, do_insert(matches(RangeEquals(std::vector{{41}, {43}, {44}})))); + REQUIRE_CALL(queueMock, do_insert(matches(RangeEquals(std::vector{{41}, {44}})))); REQUIRE_CALL(queueMock, empty()) .RETURN(false); REQUIRE_CALL(queueMock, next()) @@ -174,7 +173,7 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." REQUIRE_CALL(trackerMock, set_discovered(41)) .RETURN(true); REQUIRE_CALL(trackerMock, set_discovered(43)) - .RETURN(true); + .RETURN(false); REQUIRE_CALL(trackerMock, set_discovered(44)) .RETURN(true); REQUIRE_CALL(trackerMock, set_visited(41)) From dbfc996349aefce11763ae724368e54681c5d1db Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 01:45:27 +0200 Subject: [PATCH 067/256] refactor: make global lambda a free function --- include/Simple-Utility/graph/Traverse.hpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index dfb9e1dd5..08260b934 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -80,12 +80,9 @@ namespace sl::graph::detail #if (defined(__clang__) && __clang_major__ < 16) \ || (defined(__GNUG__) && __GNUG__ < 12) - constexpr auto filterTransform = []( - const Source& neighbors, - auto& tracker, - auto& nodeFactory, - const Node& currentNode - ) + template + [[nodiscard]] + constexpr std::vector filter_transform(const Source& neighbors, auto& tracker, auto& nodeFactory, const Node& currentNode) { std::vector nodes{}; if constexpr (std::ranges::sized_range) @@ -106,12 +103,9 @@ namespace sl::graph::detail #else - constexpr auto filterTransform = []( - Source&& neighbors, - auto& tracker, - auto& nodeFactory, - const auto& currentNode - ) + template + [[nodiscard]] + constexpr auto filter_transform(Source&& neighbors, auto& tracker, auto& nodeFactory, const Node& currentNode) { return std::forward(neighbors) | std::views::filter([&](const auto& info) { return tracker::set_discovered(tracker, node::vertex(info)); }) @@ -154,7 +148,8 @@ namespace sl::graph::detail [[nodiscard]] constexpr std::optional next(const Graph& graph) { - std::optional result = m_State.next(filterTransform(graph.neighbor_infos(m_Current), m_Tracker, m_NodeFactory, m_Current)); + std::optional result = m_State.next( + filter_transform(graph.neighbor_infos(m_Current), m_Tracker, m_NodeFactory, m_Current)); for (; result; result = m_State.next(std::array{})) { if (tracker::set_visited(m_Tracker, node::vertex(*result))) From 2634372a68696265ab48bd4b23187a9bb9298b8b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 01:52:51 +0200 Subject: [PATCH 068/256] fix: exclude small part from lcov --- include/Simple-Utility/graph/Traverse.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 08260b934..324117111 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -99,7 +99,9 @@ namespace sl::graph::detail } return nodes; + /* LCOV_EXCL_START */ }; + /* LCOV_EXCL_STOP */ #else From c9f088c212e51cebeced6b5acd37a2c859a127ef Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 02:01:46 +0200 Subject: [PATCH 069/256] fix: reorder BasicTraverseDriver members --- include/Simple-Utility/graph/Traverse.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 324117111..d9bb7e1a6 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -137,10 +137,10 @@ namespace sl::graph::detail node_factory_type nodeFactory = node_factory_type{}, state_type state = state_type{} ) - : m_Tracker{std::move(tracker)}, + : m_State{std::move(state)}, + m_Tracker{std::move(tracker)}, m_NodeFactory{std::move(nodeFactory)}, - m_Current{m_NodeFactory.make_init_node(std::move(origin))}, - m_State{std::move(state)} + m_Current{m_NodeFactory.make_init_node(std::move(origin))} { const bool result = tracker::set_visited(m_Tracker, node::vertex(m_Current)); assert(result && "Tracker returned false (already visited) the origin node."); @@ -189,10 +189,10 @@ namespace sl::graph::detail } private: + StateStrategy m_State{}; SL_UTILITY_NO_UNIQUE_ADDRESS TrackingStrategy m_Tracker{}; SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactoryStrategy m_NodeFactory{}; Node m_Current{}; - StateStrategy m_State{}; }; } From f1c3be1284ce8a5eecec2f5b3db05b642f1185f8 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 03:08:42 +0200 Subject: [PATCH 070/256] fix: let driver first discover and then visit the origin vertex --- include/Simple-Utility/graph/Traverse.hpp | 93 ++++++++++++++++++++++- tests/graph/Traverse.cpp | 4 + 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index d9bb7e1a6..28afd0a0c 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -142,8 +142,9 @@ namespace sl::graph::detail m_NodeFactory{std::move(nodeFactory)}, m_Current{m_NodeFactory.make_init_node(std::move(origin))} { - const bool result = tracker::set_visited(m_Tracker, node::vertex(m_Current)); - assert(result && "Tracker returned false (already visited) the origin node."); + const bool result = tracker::set_discovered(m_Tracker, node::vertex(m_Current)) + && tracker::set_visited(m_Tracker, node::vertex(m_Current)); + assert(result && "Tracker returned false (already visited) for the origin node."); } template Graph> // let the concept here, because otherwise it results in an ICE on msvc v142 @@ -196,4 +197,92 @@ namespace sl::graph::detail }; } +namespace sl::graph +{ + template + class Traverser final + { + public: + using node_type = typename Driver::node_type; + using vertex_type = feature_vertex_t; + + [[nodiscard]] + constexpr explicit Traverser(Graph graph, const vertex_type& origin) + : m_Graph{std::move(graph)}, + m_Driver{origin} + { + } + + std::optional next() + { + return m_Driver.next(m_Graph); + } + + struct Sentinel final + { + }; + + struct Iterator final + { + friend Traverser; + + public: + using iterator_concept = std::input_iterator_tag; + using element_type = node_type; + using difference_type = std::ptrdiff_t; + + [[nodiscard]] + constexpr const node_type& operator *() const noexcept + { + return *m_Value; + } + + constexpr Iterator& operator ++() + { + m_Value = m_Source->next(); + + return *this; + } + + constexpr void operator ++(int) + { + operator++(); + } + + [[nodiscard]] + constexpr bool operator==(const Iterator&) const = default; + + [[nodiscard]] + constexpr bool operator==([[maybe_unused]] const Sentinel) const + { + return !m_Value; + } + + private: + Traverser* m_Source{}; + std::optional m_Value{}; + + constexpr explicit Iterator(Traverser& source) + : m_Source{std::addressof(source)}, + m_Value{source.next()} + { + } + }; + + constexpr Iterator begin() noexcept + { + return Iterator{*this}; + } + + constexpr Sentinel end() const noexcept + { + return Sentinel{}; + } + + private: + Graph m_Graph{}; + Driver m_Driver{}; + }; +} + #endif diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index efe818180..9b494322b 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -119,6 +119,8 @@ TEST_CASE("BasicTraverseDriver can be constructed with an origin.", "[graph][gra .RETURN(DefaultNode{.vertex = origin}); DefaultTracker trackerMock{}; + REQUIRE_CALL(trackerMock, set_discovered(origin)) + .RETURN(true); REQUIRE_CALL(trackerMock, set_visited(origin)) .RETURN(true); @@ -141,6 +143,8 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." .RETURN(originNode); DefaultTracker trackerMock{}; + REQUIRE_CALL(trackerMock, set_discovered(42)) + .RETURN(true); REQUIRE_CALL(trackerMock, set_visited(42)) .RETURN(true); From ddf016d7e9a2b8ea69138889b51fda41b532e759 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 17:20:49 +0200 Subject: [PATCH 071/256] test: add EmptyQueueStub --- tests/graph/Defines.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 905c17f97..b6bfe444a 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -74,6 +74,22 @@ class QueueMock } }; +template +struct EmptyQueueStub +{ + [[nodiscard]] + static constexpr bool empty() noexcept { return true; } + + static constexpr void insert(auto&&) noexcept + { + } + + static constexpr Node next() + { + return {}; + } +}; + template class TrackerMock { From 9b9762bee88160a23a4bc592100eda0cbc0b69eb Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 17:22:05 +0200 Subject: [PATCH 072/256] feat: make BasicTraverseDriver members inplace constructible --- include/Simple-Utility/graph/Traverse.hpp | 58 ++++++++++++++--------- tests/graph/Traverse.cpp | 40 +++++++++++----- 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 28afd0a0c..fe07b8636 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -33,12 +33,17 @@ namespace sl::graph::detail using node_type = Node; using queue_type = QueuingStrategy; + ~BasicState() = default; + BasicState(const BasicState&) = delete; + BasicState& operator =(const BasicState&) = delete; + BasicState(BasicState&&) = default; + BasicState& operator =(BasicState&&) = default; + + template + requires std::constructible_from [[nodiscard]] - constexpr explicit BasicState() = default; - - [[nodiscard]] - constexpr explicit BasicState(queue_type queue) noexcept(std::is_nothrow_move_constructible_v) - : m_Queue{std::move(queue)} + constexpr explicit BasicState(QueueArgs&&... queueArgs) noexcept(std::is_nothrow_constructible_v) + : m_Queue{std::forward(queueArgs)...} { assert(queue::empty(m_Queue) && "Queue already contains elements."); } @@ -64,7 +69,7 @@ namespace sl::graph::detail } private: - QueuingStrategy m_Queue{}; + QueuingStrategy m_Queue; }; template @@ -130,17 +135,22 @@ namespace sl::graph::detail using tracker_type = TrackingStrategy; using node_factory_type = NodeFactoryStrategy; + template + requires std::constructible_from + && std::constructible_from + && std::constructible_from + && std::constructible_from [[nodiscard]] explicit BasicTraverseDriver( - const vertex_type& origin, - tracker_type tracker = tracker_type{}, - node_factory_type nodeFactory = node_factory_type{}, - state_type state = state_type{} + Origin&& origin, + std::tuple trackerArgs, + std::tuple nodeFactoryArgs, + std::tuple stateArgs ) - : m_State{std::move(state)}, - m_Tracker{std::move(tracker)}, - m_NodeFactory{std::move(nodeFactory)}, - m_Current{m_NodeFactory.make_init_node(std::move(origin))} + : m_Tracker{std::make_from_tuple(std::move(trackerArgs))}, + m_NodeFactory{std::make_from_tuple(std::move(nodeFactoryArgs))}, + m_State{std::make_from_tuple(std::move(stateArgs))}, + m_Current{m_NodeFactory.make_init_node(std::forward(origin))} { const bool result = tracker::set_discovered(m_Tracker, node::vertex(m_Current)) && tracker::set_visited(m_Tracker, node::vertex(m_Current)); @@ -190,10 +200,11 @@ namespace sl::graph::detail } private: - StateStrategy m_State{}; - SL_UTILITY_NO_UNIQUE_ADDRESS TrackingStrategy m_Tracker{}; - SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactoryStrategy m_NodeFactory{}; - Node m_Current{}; + // do not reorder. Seems to somehow cause segment faults, when ordered differently + SL_UTILITY_NO_UNIQUE_ADDRESS TrackingStrategy m_Tracker; + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactoryStrategy m_NodeFactory; + StateStrategy m_State; + Node m_Current; }; } @@ -207,12 +218,13 @@ namespace sl::graph using vertex_type = feature_vertex_t; [[nodiscard]] - constexpr explicit Traverser(Graph graph, const vertex_type& origin) + constexpr explicit Traverser(Graph graph, vertex_type origin) : m_Graph{std::move(graph)}, - m_Driver{origin} + m_Driver{std::move(origin), std::tuple{}, std::tuple{}, std::tuple{}} { } + [[nodiscard]] std::optional next() { return m_Driver.next(m_Graph); @@ -240,7 +252,6 @@ namespace sl::graph constexpr Iterator& operator ++() { m_Value = m_Source->next(); - return *this; } @@ -262,6 +273,7 @@ namespace sl::graph Traverser* m_Source{}; std::optional m_Value{}; + [[nodiscard]] constexpr explicit Iterator(Traverser& source) : m_Source{std::addressof(source)}, m_Value{source.next()} @@ -269,11 +281,13 @@ namespace sl::graph } }; - constexpr Iterator begin() noexcept + [[nodiscard]] + constexpr Iterator begin() & { return Iterator{*this}; } + [[nodiscard]] constexpr Sentinel end() const noexcept { return Sentinel{}; diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 9b494322b..5f8650a4c 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -39,24 +39,24 @@ using DefaultTracker = TrackerMock>; using DefaultGraph = BasicGraph; using DefaultNodeFactory = BasicNodeFactoryMock; using DefaultDriver = sg::detail::BasicTraverseDriver< - DefaultNode, - DefaultState, - DefaultTracker, - DefaultNodeFactory>; + DefaultNode, + DefaultState, + DefaultTracker, + DefaultNodeFactory>; TEST_CASE("BasicState is constructible.", "[graph][graph::traverse][graph::detail]") { + using State = sg::detail::BasicState>; + SECTION("Default constructible.") { - const DefaultState state{}; + constexpr State state{}; } SECTION("Constructible with queue object as argument.") { - DefaultQueue queue{}; - ALLOW_CALL(queue, empty()) // allow debug assertion - .RETURN(true); - const DefaultState state{std::move(queue)}; + EmptyQueueStub queue{}; + const State state{std::move(queue)}; } } @@ -124,7 +124,16 @@ TEST_CASE("BasicTraverseDriver can be constructed with an origin.", "[graph][gra REQUIRE_CALL(trackerMock, set_visited(origin)) .RETURN(true); - const DefaultDriver driver{origin, std::move(trackerMock), std::move(nodeFactoryMock)}; + const sg::detail::BasicTraverseDriver< + DefaultNode, + sg::detail::BasicState>, + DefaultTracker, + DefaultNodeFactory> driver{ + origin, + std::forward_as_tuple(std::move(trackerMock)), + std::forward_as_tuple(std::move(nodeFactoryMock)), + std::tuple{} + }; REQUIRE(DefaultNode{.vertex = origin} == driver.current_node()); } @@ -148,7 +157,16 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." REQUIRE_CALL(trackerMock, set_visited(42)) .RETURN(true); - return DefaultDriver{42, std::move(trackerMock), std::move(nodeFactoryMock)}; + DefaultQueue queue{}; + ALLOW_CALL(queue, empty()) + .RETURN(true); + + return DefaultDriver{ + 42, + std::forward_as_tuple(std::move(trackerMock)), + std::forward_as_tuple(std::move(nodeFactoryMock)), + std::forward_as_tuple(std::move(queue)) + }; }(); using VertexInfo = DefaultGraph::info; From cbf61131ceed3b31421696b2a31a467efd3cb846 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 17:41:58 +0200 Subject: [PATCH 073/256] fix: make node::vertex actually customizable --- include/Simple-Utility/graph/Node.hpp | 73 ++++++++++++++++++++------- tests/graph/Node.cpp | 26 +++++++++- 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 5a424c2cb..d52bc1b25 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -8,29 +8,68 @@ #pragma once +#include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" +namespace sl::graph::customize +{ + template + struct vertex_fn; +} + namespace sl::graph::node::detail { - struct vertex_fn + template + requires requires(const Node& node, customize::vertex_fn fn) + { + requires concepts::vertex>; + } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<3>) noexcept(noexcept(customize::vertex_fn{}(node))) { - constexpr auto& operator ()(const auto& node) const noexcept - requires concepts::vertex> + return customize::vertex_fn{}(node); + } + + template + requires requires(const Node& node) { - return node.vertex; + requires concepts::vertex>; } + constexpr auto& vertex(const Node& node, const priority_tag<2>) noexcept + { + return node.vertex; + } - constexpr decltype(auto) operator ()(const auto& node) const noexcept - requires concepts::vertex> + template + requires requires(const Node& node) { - return node.vertex(); + requires concepts::vertex>; } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<1>) noexcept(noexcept(node.vertex())) + { + return node.vertex(); + } - constexpr decltype(auto) operator ()(const auto& node) const noexcept - requires concepts::vertex> + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<0>) noexcept(noexcept(vertex(node))) + { + return vertex(node); + } + + struct vertex_fn + { + template + requires requires(const Node& node, const priority_tag<3> tag) + { + requires concepts::vertex>; + } + constexpr decltype(auto) operator ()(const Node& node) const noexcept(noexcept(detail::vertex(node, priority_tag<3>{}))) { - return vertex(node); + return detail::vertex(node, priority_tag<3>{}); } }; } @@ -55,13 +94,13 @@ namespace sl::graph::concepts template concept node_factory_for = sl::concepts::unqualified - && node - && std::destructible - && requires(T& factory, const Node& node) - { - { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; - { factory.make_successor_node(node, {}) } -> std::convertible_to; - }; + && node + && std::destructible + && requires(T& factory, const Node& node) + { + { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; + { factory.make_successor_node(node, {}) } -> std::convertible_to; + }; template concept compatible_with = node diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 0965e5eb5..27f84dab1 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -36,6 +36,11 @@ namespace } }; + struct custom_fun_vertex + { + MAKE_CONST_MOCK0(my_vertex, int()); + }; + struct minimal_node { using vertex_type = int; @@ -67,13 +72,24 @@ namespace vertex_type vertex; }; - std::vector neighbor_infos(const sg::concepts::node auto&) const + [[nodiscard]] + static std::vector neighbor_infos(const sg::concepts::node auto&) { return {}; } }; } +template <> +struct sg::customize::vertex_fn +{ + [[nodiscard]] + decltype(auto) operator ()(const custom_fun_vertex& e) const + { + return e.my_vertex(); + } +}; + TEST_CASE("graph::node::vertex serves as a customization point accessing the node vertex.", "[graph][graph::node]") { const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); @@ -98,6 +114,14 @@ TEST_CASE("graph::node::vertex serves as a customization point accessing the nod .RETURN(expected); REQUIRE(expected == sg::node::vertex(std::as_const(mock))); } + + SECTION("Access via custom function.") + { + custom_fun_vertex mock{}; + REQUIRE_CALL(mock, my_vertex()) + .RETURN(expected); + REQUIRE(expected == sg::node::vertex(std::as_const(mock))); + } } TEMPLATE_TEST_CASE_SIG( From 73adab69491759d95608d14373606ecb1270e051 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 17:42:15 +0200 Subject: [PATCH 074/256] refactor: simplify single test case --- tests/graph/Traverse.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 5f8650a4c..f5577e4c6 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -55,8 +55,7 @@ TEST_CASE("BasicState is constructible.", "[graph][graph::traverse][graph::detai SECTION("Constructible with queue object as argument.") { - EmptyQueueStub queue{}; - const State state{std::move(queue)}; + constexpr State state{EmptyQueueStub{}}; } } From c9c2251afaa3b577dd2da9794e0835feac7fd6af Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 24 Aug 2023 17:49:32 +0200 Subject: [PATCH 075/256] feat: graph::node::rank customization point --- include/Simple-Utility/graph/Node.hpp | 64 ++++++++++++++++++++++++ tests/graph/Node.cpp | 71 +++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index d52bc1b25..0ee3cf468 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -16,10 +16,17 @@ namespace sl::graph::customize { template struct vertex_fn; + + template + struct rank_fn; } namespace sl::graph::node::detail { + /* + * vertex extraction + */ + template requires requires(const Node& node, customize::vertex_fn fn) { @@ -72,11 +79,68 @@ namespace sl::graph::node::detail return detail::vertex(node, priority_tag<3>{}); } }; + + /* + * rank extraction + */ + template + requires requires(const Node& node, customize::rank_fn fn) + { + requires concepts::rank>; + } + constexpr decltype(auto) rank(const Node& node, const priority_tag<3>) noexcept(noexcept(customize::rank_fn{}(node))) + { + return customize::rank_fn{}(node); + } + + template + requires requires(const Node& node) + { + requires concepts::rank>; + } + constexpr auto& rank(const Node& node, const priority_tag<2>) noexcept + { + return node.rank; + } + + template + requires requires(const Node& node) + { + requires concepts::rank>; + } + constexpr decltype(auto) rank(const Node& node, const priority_tag<1>) noexcept(noexcept(node.rank())) + { + return node.rank(); + } + + template + requires requires(const Node& node) + { + requires concepts::rank>; + } + constexpr decltype(auto) rank(const Node& node, const priority_tag<0>) noexcept(noexcept(rank(node))) + { + return rank(node); + } + + struct rank_fn + { + template + requires requires(const Node& node, const priority_tag<3> tag) + { + requires concepts::rank>; + } + constexpr decltype(auto) operator ()(const Node& node) const noexcept(noexcept(detail::rank(node, priority_tag<3>{}))) + { + return detail::rank(node, priority_tag<3>{}); + } + }; } namespace sl::graph::node { inline constexpr detail::vertex_fn vertex{}; + inline constexpr detail::rank_fn rank{}; } namespace sl::graph::concepts diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 27f84dab1..d69e17900 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -41,6 +41,33 @@ namespace MAKE_CONST_MOCK0(my_vertex, int()); }; + struct member_rank + { + // ReSharper disable once CppDeclaratorNeverUsed + int rank; + }; + + struct member_fun_rank + { + MAKE_CONST_MOCK0(rank, int()); + }; + + struct free_fun_rank + { + MAKE_CONST_MOCK0(my_rank, int()); + + // ReSharper disable once CppDeclaratorNeverUsed + friend int rank(const free_fun_rank& v) + { + return v.my_rank(); + } + }; + + struct custom_fun_rank + { + MAKE_CONST_MOCK0(my_rank, int()); + }; + struct minimal_node { using vertex_type = int; @@ -90,6 +117,16 @@ struct sg::customize::vertex_fn } }; +template <> +struct sg::customize::rank_fn +{ + [[nodiscard]] + decltype(auto) operator ()(const custom_fun_rank& e) const + { + return e.my_rank(); + } +}; + TEST_CASE("graph::node::vertex serves as a customization point accessing the node vertex.", "[graph][graph::node]") { const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); @@ -124,6 +161,40 @@ TEST_CASE("graph::node::vertex serves as a customization point accessing the nod } } +TEST_CASE("graph::node::rank serves as a customization point accessing the node rank.", "[graph][graph::node]") +{ + const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); + + SECTION("Access via member.") + { + REQUIRE(expected == sg::node::rank(member_rank{expected})); + } + + SECTION("Access via member function.") + { + member_fun_rank mock{}; + REQUIRE_CALL(mock, rank()) + .RETURN(expected); + REQUIRE(expected == sg::node::rank(std::as_const(mock))); + } + + SECTION("Access via free function.") + { + free_fun_rank mock{}; + REQUIRE_CALL(mock, my_rank()) + .RETURN(expected); + REQUIRE(expected == sg::node::rank(std::as_const(mock))); + } + + SECTION("Access via custom function.") + { + custom_fun_rank mock{}; + REQUIRE_CALL(mock, my_rank()) + .RETURN(expected); + REQUIRE(expected == sg::node::rank(std::as_const(mock))); + } +} + TEMPLATE_TEST_CASE_SIG( "concepts::node determines, whether the given type satisfies the node requirements.", "[graph][graph::concepts]", From 1ee61c7efd859349f47df88b2d828b6fb9d7113d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 26 Aug 2023 15:11:20 +0200 Subject: [PATCH 076/256] feat: in_place_constructor --- include/Simple-Utility/Utility.hpp | 77 +++++++++++- tests/Utility.cpp | 195 ++++++++++++++++++++++++++++- 2 files changed, 268 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/Utility.hpp b/include/Simple-Utility/Utility.hpp index d5c91c591..8d83cf47c 100644 --- a/include/Simple-Utility/Utility.hpp +++ b/include/Simple-Utility/Utility.hpp @@ -3,9 +3,16 @@ // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) +#ifndef SIMPLE_UTILITY_UTILITY_HPP +#define SIMPLE_UTILITY_UTILITY_HPP + #pragma once +#include "Simple-Utility/concepts/stl_extensions.hpp" + +#include #include +#include namespace sl { @@ -25,7 +32,6 @@ namespace sl { }; - /** * \brief Specialization, denoting the least likely alternative. */ @@ -34,7 +40,76 @@ namespace sl { }; + /** + * \} + */ + + /** + * \defgroup GROUP_IN_PLACE in_place + * \brief Provides convenient option, to construct arguments in-place. + * \{ + */ + + /** + * \brief Helper type, enabling deferred construction, which will then (thanks to copy-elision) in-place construct the ``Type`` instance. + * \tparam Type The type to be constructed. + * \tparam Args The constructor argument types. + * \param args The constructor arguments. + * \note Instances of this type are not intended to be materialized anywhere. They should only be used during one expression. + */ + template + requires std::constructible_from + struct in_place_constructor + { + public: + in_place_constructor(const in_place_constructor&) = delete; + in_place_constructor& operator =(const in_place_constructor&) = delete; + in_place_constructor(in_place_constructor&&) = delete; + in_place_constructor& operator =(in_place_constructor&&) = delete; + + ~in_place_constructor() = default; + + [[nodiscard]] + in_place_constructor() = default; + + [[nodiscard]] + constexpr operator Type() && noexcept(std::is_nothrow_constructible_v) + { + return std::make_from_tuple(std::move(m_Args)); + } + + template + requires std::constructible_from + friend constexpr auto in_place(Ts&&... args) noexcept; + + private: + std::tuple m_Args; + + [[nodiscard]] + explicit constexpr in_place_constructor(std::tuple&& args) noexcept + : m_Args{std::move(args)} + { + } + }; + + /** + * \brief Forwards the given arguments as tuple into the internal storage. + * \tparam Type The type to be constructed. + * \tparam Args The constructor argument types. + * \param args The constructor arguments. + * \return in_place_constructor instance, storing the arguments as forwarding references. + */ + template + requires std::constructible_from + [[nodiscard]] + constexpr auto in_place(Args&&... args) noexcept + { + return in_place_constructor{std::forward_as_tuple(std::forward(args)...)}; + } + /** * \} */ } + +#endif diff --git a/tests/Utility.cpp b/tests/Utility.cpp index fa60f553f..6509df19c 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -1,15 +1,25 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + #include "Simple-Utility/Utility.hpp" #include +#include +#include +#include + +#include "Simple-Utility/test_util/Catch2Ext.hpp" -#include +#include TEMPLATE_TEST_CASE_SIG( "Higher priority_tag always inherit from the lower ones.", "[utility]", ((bool expected, class Derived, class Base), expected, Derived, Base), - (true, sl::priority_tag<10>, sl::priority_tag<0>), - (true, sl::priority_tag<10>, sl::priority_tag<1>), + (true, sl::priority_tag<10>, sl::priority_tag<0>), + (true, sl::priority_tag<10>, sl::priority_tag<1>), (true, sl::priority_tag<1>, sl::priority_tag<0>), (true, sl::priority_tag<1>, sl::priority_tag<1>), (false, sl::priority_tag<0>, sl::priority_tag<1>), @@ -18,3 +28,182 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == std::derived_from); } + +namespace +{ + template + class immobile + { + public: + ~immobile() = default; + + immobile(const immobile&) = delete; + immobile& operator =(const immobile&) = delete; + immobile(immobile&&) = delete; + immobile& operator =(immobile&&) = delete; + + template + requires std::constructible_from, Args...> + explicit immobile(Args&&... args) + : value{std::forward(args)...} + { + } + + std::tuple value; + }; + + template + class ForwardingCTor + { + public: + template Arg> + explicit ForwardingCTor(Arg&& arg) + : value{std::forward(arg)} + { + } + + T value; + }; + + template + class InPlaceExpecting + { + public: + template + explicit InPlaceExpecting(sl::in_place_constructor&& args) + : value{std::move(args)} + { + } + + T value; + }; + + template + class MultiInPlaceExpecting + { + public: + template + explicit MultiInPlaceExpecting( + sl::in_place_constructor&& args1, + sl::in_place_constructor&& args2, + sl::in_place_constructor&& args3 + ) + : value1{std::move(args1)}, + value2{std::move(args2)}, + value3{std::move(args3)} + { + } + + T1 value1; + T2 value2; + T3 value3; + }; +} + +TEST_CASE("in_place_constructor can be utilized from types, which expects it as its ctor argument.", "[utility]") +{ + SECTION("Supports convenient default construction") + { + const InPlaceExpecting> obj{{}}; + + REQUIRE(std::nullopt == obj.value); + } + + SECTION("Supports CTAD") + { + SECTION("When move constructing.") + { + std::string str{"Hello, World!"}; + const InPlaceExpecting obj{sl::in_place(std::move(str))}; + + REQUIRE("Hello, World!" == obj.value); + } + + SECTION("When copy constructing.") + { + const std::string str{"Hello, World!"}; + const InPlaceExpecting obj{sl::in_place(str)}; + + REQUIRE("Hello, World!" == obj.value); + } + + SECTION("When argument constructing.") + { + const InPlaceExpecting obj{sl::in_place("Hello, World!")}; + + REQUIRE("Hello, World!" == obj.value); + } + + SECTION("Even when constructing immobile types.") + { + const InPlaceExpecting obj{sl::in_place>("Hello, World!", 42)}; + + REQUIRE(std::tuple{"Hello, World!", 42} == obj.value.value); + } + } +} + +TEST_CASE("in_place_constructor can be utilized on custruction, when used on perfect forwarding ctors..", "[utility]") +{ + SECTION("Supports default construction") + { + const ForwardingCTor> obj{sl::in_place>()}; + + REQUIRE(std::nullopt == obj.value); + } + + SECTION("When move constructing.") + { + std::string str{"Hello, World!"}; + const ForwardingCTor obj{sl::in_place(std::move(str))}; + + REQUIRE("Hello, World!" == obj.value); + } + + SECTION("When copy constructing.") + { + const std::string str{"Hello, World!"}; + const ForwardingCTor obj{sl::in_place(str)}; + + REQUIRE("Hello, World!" == obj.value); + } + + SECTION("When argument constructing.") + { + const ForwardingCTor obj{sl::in_place("Hello, World!")}; + + REQUIRE("Hello, World!" == obj.value); + } + + SECTION("Even when constructing immobile types.") + { + const ForwardingCTor> obj{sl::in_place>("Hello, World!", 42)}; + + REQUIRE(std::tuple{"Hello, World!", 42} == obj.value.value); + } +} + +TEST_CASE("in_place_constructor can be utilized from types, which expects some of them as its ctor arguments.", "[utility]") +{ + SECTION("Supports convenient default construction") + { + const MultiInPlaceExpecting, std::string, int> obj{{}, {}, {}}; + + REQUIRE(std::nullopt == obj.value1); + REQUIRE(obj.value2.empty()); + REQUIRE(0 == obj.value3); + } + + SECTION("Supports CTAD") + { + const MultiInPlaceExpecting obj{ + sl::in_place>(1337), + sl::in_place("Hello, World!"), + sl::in_place(42) + }; + + REQUIRE(1337 == obj.value1); + REQUIRE("Hello, World!" == obj.value2); + REQUIRE(42 == obj.value3); + } +} From 8adf5d149e08d7d2ba90000139ee34c4f0e4f725 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 28 Aug 2023 12:33:40 +0200 Subject: [PATCH 077/256] fix: please msvc < 1934 --- include/Simple-Utility/Utility.hpp | 8 +++----- tests/Utility.cpp | 25 +++++++++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/include/Simple-Utility/Utility.hpp b/include/Simple-Utility/Utility.hpp index 8d83cf47c..ebef8dc01 100644 --- a/include/Simple-Utility/Utility.hpp +++ b/include/Simple-Utility/Utility.hpp @@ -73,14 +73,13 @@ namespace sl in_place_constructor() = default; [[nodiscard]] - constexpr operator Type() && noexcept(std::is_nothrow_constructible_v) + explicit constexpr operator Type() && noexcept(std::is_nothrow_constructible_v) { return std::make_from_tuple(std::move(m_Args)); } template - requires std::constructible_from - friend constexpr auto in_place(Ts&&... args) noexcept; + friend constexpr in_place_constructor in_place(Ts&&... args) noexcept; private: std::tuple m_Args; @@ -100,9 +99,8 @@ namespace sl * \return in_place_constructor instance, storing the arguments as forwarding references. */ template - requires std::constructible_from [[nodiscard]] - constexpr auto in_place(Args&&... args) noexcept + constexpr in_place_constructor in_place(Args&&... args) noexcept { return in_place_constructor{std::forward_as_tuple(std::forward(args)...)}; } diff --git a/tests/Utility.cpp b/tests/Utility.cpp index 6509df19c..843ca11ee 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -56,9 +56,9 @@ namespace class ForwardingCTor { public: - template Arg> + template explicit ForwardingCTor(Arg&& arg) - : value{std::forward(arg)} + : value{static_cast(std::forward(arg))} { } @@ -71,7 +71,7 @@ namespace public: template explicit InPlaceExpecting(sl::in_place_constructor&& args) - : value{std::move(args)} + : value{static_cast(std::move(args))} { } @@ -88,9 +88,9 @@ namespace sl::in_place_constructor&& args2, sl::in_place_constructor&& args3 ) - : value1{std::move(args1)}, - value2{std::move(args2)}, - value3{std::move(args3)} + : value1{static_cast(std::move(args1))}, + value2{static_cast(std::move(args2))}, + value3{static_cast(std::move(args3))} { } @@ -206,4 +206,17 @@ TEST_CASE("in_place_constructor can be utilized from types, which expects some o REQUIRE("Hello, World!" == obj.value2); REQUIRE(42 == obj.value3); } + + SECTION("Supports immobile types") + { + const MultiInPlaceExpecting obj{ + sl::in_place>("World, Hello!", 1337), + sl::in_place("Hello, World!"), + sl::in_place(42) + }; + + REQUIRE(std::tuple{"World!, Hello", 42} == obj.value1.value); + REQUIRE("Hello, World!" == obj.value2); + REQUIRE(42 == obj.value3); + } } From f4c45400e1ac25df9b56449f8dd177fd6f339e50 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 28 Aug 2023 12:43:20 +0200 Subject: [PATCH 078/256] fix: correct test case require --- tests/Utility.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Utility.cpp b/tests/Utility.cpp index 843ca11ee..8868886f3 100644 --- a/tests/Utility.cpp +++ b/tests/Utility.cpp @@ -202,6 +202,8 @@ TEST_CASE("in_place_constructor can be utilized from types, which expects some o sl::in_place(42) }; + using Expected = MultiInPlaceExpecting, std::string, int>; + STATIC_REQUIRE(std::same_as>); REQUIRE(1337 == obj.value1); REQUIRE("Hello, World!" == obj.value2); REQUIRE(42 == obj.value3); @@ -215,7 +217,9 @@ TEST_CASE("in_place_constructor can be utilized from types, which expects some o sl::in_place(42) }; - REQUIRE(std::tuple{"World!, Hello", 42} == obj.value1.value); + using Expected = MultiInPlaceExpecting, std::string, int>; + STATIC_REQUIRE(std::same_as>); + REQUIRE(std::tuple{"World, Hello!", 1337} == obj.value1.value); REQUIRE("Hello, World!" == obj.value2); REQUIRE(42 == obj.value3); } From 0f046ca8e93efad70efd2d4b8c49420d2129042c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 28 Aug 2023 13:43:15 +0200 Subject: [PATCH 079/256] refactor: move vertex customization point into Common.hpp --- include/Simple-Utility/graph/Common.hpp | 63 ++++++++++++++++++++ include/Simple-Utility/graph/Node.hpp | 62 +------------------ tests/graph/Common.cpp | 79 +++++++++++++++++++++++++ tests/graph/Node.cpp | 44 -------------- 4 files changed, 143 insertions(+), 105 deletions(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index e53e93313..30ea9ddc7 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -9,6 +9,7 @@ #pragma once #include "Simple-Utility/TypeList.hpp" +#include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/operators.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" @@ -59,6 +60,68 @@ namespace sl::graph::concepts && vertex; } +namespace sl::graph::customize +{ + template + struct vertex_fn; +} + +namespace sl::graph::detail +{ + template + requires requires(const Node& node, customize::vertex_fn fn) + { + requires concepts::vertex>; + } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<3>) noexcept(noexcept(customize::vertex_fn{}(node))) + { + return customize::vertex_fn{}(node); + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr auto& vertex(const Node& node, const priority_tag<2>) noexcept + { + return node.vertex; + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<1>) noexcept(noexcept(node.vertex())) + { + return node.vertex(); + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<0>) noexcept(noexcept(vertex(node))) + { + return vertex(node); + } + + struct vertex_fn + { + template + requires requires(const Node& node, const priority_tag<3> tag) + { + requires concepts::vertex>; + } + constexpr decltype(auto) operator ()(const Node& node) const noexcept(noexcept(detail::vertex(node, priority_tag<3>{}))) + { + return detail::vertex(node, priority_tag<3>{}); + } + }; +} + namespace sl::graph { template diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 0ee3cf468..194350a8c 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -23,66 +23,6 @@ namespace sl::graph::customize namespace sl::graph::node::detail { - /* - * vertex extraction - */ - - template - requires requires(const Node& node, customize::vertex_fn fn) - { - requires concepts::vertex>; - } - constexpr decltype(auto) vertex(const Node& node, const priority_tag<3>) noexcept(noexcept(customize::vertex_fn{}(node))) - { - return customize::vertex_fn{}(node); - } - - template - requires requires(const Node& node) - { - requires concepts::vertex>; - } - constexpr auto& vertex(const Node& node, const priority_tag<2>) noexcept - { - return node.vertex; - } - - template - requires requires(const Node& node) - { - requires concepts::vertex>; - } - constexpr decltype(auto) vertex(const Node& node, const priority_tag<1>) noexcept(noexcept(node.vertex())) - { - return node.vertex(); - } - - template - requires requires(const Node& node) - { - requires concepts::vertex>; - } - constexpr decltype(auto) vertex(const Node& node, const priority_tag<0>) noexcept(noexcept(vertex(node))) - { - return vertex(node); - } - - struct vertex_fn - { - template - requires requires(const Node& node, const priority_tag<3> tag) - { - requires concepts::vertex>; - } - constexpr decltype(auto) operator ()(const Node& node) const noexcept(noexcept(detail::vertex(node, priority_tag<3>{}))) - { - return detail::vertex(node, priority_tag<3>{}); - } - }; - - /* - * rank extraction - */ template requires requires(const Node& node, customize::rank_fn fn) { @@ -139,7 +79,7 @@ namespace sl::graph::node::detail namespace sl::graph::node { - inline constexpr detail::vertex_fn vertex{}; + inline constexpr graph::detail::vertex_fn vertex{}; inline constexpr detail::rank_fn rank{}; } diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index d9056b3dc..1a1b4d847 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -3,7 +3,12 @@ // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) +#include "Simple-Utility/graph/Common.hpp" + #include +#include +#include +#include #include "Defines.hpp" @@ -74,6 +79,33 @@ namespace valid_rank& operator -=([[maybe_unused]] const valid_rank&); valid_rank operator -([[maybe_unused]] const valid_rank&) const; }; + + struct member_vertex + { + // ReSharper disable once CppDeclaratorNeverUsed + int vertex; + }; + + struct member_fun_vertex + { + MAKE_CONST_MOCK0(vertex, int()); + }; + + struct free_fun_vertex + { + MAKE_CONST_MOCK0(my_vertex, int()); + + // ReSharper disable once CppDeclaratorNeverUsed + friend int vertex(const free_fun_vertex& v) + { + return v.my_vertex(); + } + }; + + struct custom_fun_vertex + { + MAKE_CONST_MOCK0(my_vertex, int()); + }; } TEMPLATE_TEST_CASE_SIG( @@ -188,6 +220,17 @@ struct sg::feature_traits using rank_type = float; }; +template <> +struct sg::customize::vertex_fn +{ + [[nodiscard]] + decltype(auto) operator ()(const custom_fun_vertex& e) const + { + return e.my_vertex(); + } +}; + + TEMPLATE_TEST_CASE_SIG( "graph::concepts::readable_vertex_type determines whether T contains a \"vertex_type\" member alias.", "[graph][graph::concepts]", @@ -283,3 +326,39 @@ TEMPLATE_TEST_CASE( sg::ranked_feature_category, sg::common_feature_category_t>>); } + +TEST_CASE("graph::details::vertex serves as a customization point accessing the vertex.", "[graph][detail]") +{ + constexpr sg::detail::vertex_fn fun{}; + + const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); + + SECTION("Access via the vertex member.") + { + REQUIRE(expected == fun(member_vertex{expected})); + } + + SECTION("Access via the vertex member function.") + { + member_fun_vertex mock{}; + REQUIRE_CALL(mock, vertex()) + .RETURN(expected); + REQUIRE(expected == fun(std::as_const(mock))); + } + + SECTION("Access via the vertex free function.") + { + free_fun_vertex mock{}; + REQUIRE_CALL(mock, my_vertex()) + .RETURN(expected); + REQUIRE(expected == fun(std::as_const(mock))); + } + + SECTION("Access via custom function.") + { + custom_fun_vertex mock{}; + REQUIRE_CALL(mock, my_vertex()) + .RETURN(expected); + REQUIRE(expected == fun(std::as_const(mock))); + } +} diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index d69e17900..005e787e9 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -107,16 +107,6 @@ namespace }; } -template <> -struct sg::customize::vertex_fn -{ - [[nodiscard]] - decltype(auto) operator ()(const custom_fun_vertex& e) const - { - return e.my_vertex(); - } -}; - template <> struct sg::customize::rank_fn { @@ -127,40 +117,6 @@ struct sg::customize::rank_fn } }; -TEST_CASE("graph::node::vertex serves as a customization point accessing the node vertex.", "[graph][graph::node]") -{ - const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); - - SECTION("Access via the vertex member.") - { - REQUIRE(expected == sg::node::vertex(member_vertex{expected})); - } - - SECTION("Access via the vertex member function.") - { - member_fun_vertex mock{}; - REQUIRE_CALL(mock, vertex()) - .RETURN(expected); - REQUIRE(expected == sg::node::vertex(std::as_const(mock))); - } - - SECTION("Access via the vertex free function.") - { - free_fun_vertex mock{}; - REQUIRE_CALL(mock, my_vertex()) - .RETURN(expected); - REQUIRE(expected == sg::node::vertex(std::as_const(mock))); - } - - SECTION("Access via custom function.") - { - custom_fun_vertex mock{}; - REQUIRE_CALL(mock, my_vertex()) - .RETURN(expected); - REQUIRE(expected == sg::node::vertex(std::as_const(mock))); - } -} - TEST_CASE("graph::node::rank serves as a customization point accessing the node rank.", "[graph][graph::node]") { const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); From 86bc26a009cf04c1b3c1e2f341ed079d0d677175 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 28 Aug 2023 14:11:45 +0200 Subject: [PATCH 080/256] feat: concepts::weight --- include/Simple-Utility/graph/Common.hpp | 10 ++-- tests/graph/Common.cpp | 62 ++++++++++++++----------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 30ea9ddc7..d25876f03 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -43,14 +43,18 @@ namespace sl::graph::concepts && std::copyable; template - concept rank = sl::concepts::unqualified - && std::totally_ordered - && std::regular + concept weight = sl::concepts::unqualified + && std::copyable && sl::concepts::plus && sl::concepts::minus && sl::concepts::plus_assign && sl::concepts::minus_assign; + template + concept rank = sl::concepts::unqualified + && std::regular + && std::totally_ordered; + template concept readable_vertex_type = requires { typename T::vertex_type; } && vertex; diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 1a1b4d847..567e9e2a3 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -12,8 +12,6 @@ #include "Defines.hpp" -#include "Simple-Utility/graph/Common.hpp" - // ReSharper disable CppDeclaratorNeverUsed // ReSharper disable CppTypeAliasNeverUsed // ReSharper disable CppClangTidyClangDiagnosticUnusedMemberFunction @@ -28,7 +26,7 @@ namespace auto operator <=>(const non_equality_comparable&) const = default; }; - struct non_copyable // NOLINT(cppcoreguidelines-special-member-functions) + struct non_copyable // NOLINT(cppcoreguidelines-special-member-functions) { non_copyable(const non_copyable&) = delete; non_copyable& operator =(const non_copyable&) = delete; @@ -45,39 +43,37 @@ namespace bool operator ==(const non_totally_ordered&) const = default; }; - struct non_mutable_plus_rank + struct non_mutable_plus { - auto operator <=>(const non_mutable_plus_rank&) const = default; - non_mutable_plus_rank operator +([[maybe_unused]] const non_mutable_plus_rank&) const; + non_mutable_plus operator +([[maybe_unused]] const non_mutable_plus&) const; }; - struct non_immutable_plus_rank + struct non_immutable_plus { - auto operator <=>(const non_immutable_plus_rank&) const = default; - - non_immutable_plus_rank& operator +=([[maybe_unused]] const non_immutable_plus_rank&); + non_immutable_plus& operator +=([[maybe_unused]] const non_immutable_plus&); }; - struct non_mutable_minus_rank + struct non_mutable_minus { - auto operator <=>(const non_mutable_minus_rank&) const = default; + non_mutable_minus operator +([[maybe_unused]] const non_mutable_minus&) const; + }; - non_mutable_minus_rank operator +([[maybe_unused]] const non_mutable_minus_rank&) const; + struct non_immutable_minus + { + non_immutable_minus& operator +=([[maybe_unused]] const non_immutable_minus&); }; - struct non_immutable_minus_rank + struct valid_weight { - auto operator <=>(const non_immutable_minus_rank&) const = default; - non_immutable_minus_rank& operator +=([[maybe_unused]] const non_immutable_minus_rank&); + valid_weight& operator +=([[maybe_unused]] const valid_weight&); + valid_weight operator +([[maybe_unused]] const valid_weight&) const; + valid_weight& operator -=([[maybe_unused]] const valid_weight&); + valid_weight operator -([[maybe_unused]] const valid_weight&) const; }; struct valid_rank { auto operator <=>(const valid_rank&) const = default; - valid_rank& operator +=([[maybe_unused]] const valid_rank&); - valid_rank operator +([[maybe_unused]] const valid_rank&) const; - valid_rank& operator -=([[maybe_unused]] const valid_rank&); - valid_rank operator -([[maybe_unused]] const valid_rank&) const; }; struct member_vertex @@ -124,6 +120,25 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == sg::concepts::vertex); } +TEMPLATE_TEST_CASE_SIG( + "graph::concepts::weight determines whether the given type can be used as weight type.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (true, int), + (true, float), + (true, valid_weight), + (false, const int), + (false, int&), + (false, non_copyable), + (false, non_mutable_plus), + (false, non_immutable_plus), + (false, non_mutable_minus), + (false, non_immutable_minus) +) +{ + STATIC_REQUIRE(expected == sg::concepts::weight); +} + TEMPLATE_TEST_CASE_SIG( "graph::concepts::rank determines whether the given type can be used as rank type.", "[graph][graph::concepts]", @@ -134,12 +149,7 @@ TEMPLATE_TEST_CASE_SIG( (false, const int), (false, int&), (false, non_equality_comparable), - (false, non_copyable), - (false, non_totally_ordered), - (false, non_mutable_plus_rank), - (false, non_immutable_plus_rank), - (false, non_mutable_minus_rank), - (false, non_immutable_minus_rank) + (false, non_copyable) ) { STATIC_REQUIRE(expected == sg::concepts::rank); From 424850b7c5f43807bb778e1e301fa2f678167d3b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 28 Aug 2023 14:13:07 +0200 Subject: [PATCH 081/256] feat: concepts::readable_weight_type --- include/Simple-Utility/graph/Common.hpp | 6 +++++- tests/graph/Common.cpp | 26 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index d25876f03..5056b4260 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -59,9 +59,13 @@ namespace sl::graph::concepts concept readable_vertex_type = requires { typename T::vertex_type; } && vertex; + template + concept readable_weight_type = requires { typename T::weight_type; } + && weight; + template concept readable_rank_type = requires { typename T::rank_type; } - && vertex; + && rank; } namespace sl::graph::customize diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 567e9e2a3..4e9708058 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -197,6 +197,20 @@ namespace using vertex_type = valid_vertex; }; + struct non_readable_weight_type + { + }; + + struct readable_but_unsatisfied_weight_type + { + using weight_type = non_mutable_plus; + }; + + struct readable_weight_type + { + using weight_type = valid_weight; + }; + struct non_readable_rank_type { }; @@ -253,6 +267,18 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == sg::concepts::readable_vertex_type); } +TEMPLATE_TEST_CASE_SIG( + "graph::concepts::readable_weight_type determines whether T contains a \"weight_type\" member alias.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (false, non_readable_weight_type), + (false, readable_but_unsatisfied_weight_type), + (true, readable_weight_type) +) +{ + STATIC_REQUIRE(expected == sg::concepts::readable_weight_type); +} + TEMPLATE_TEST_CASE_SIG( "graph::concepts::readable_rank_type determines whether T contains a \"rank_type\" member alias.", "[graph][graph::concepts]", From 000b6efe42b3230dee66a3e2b6d68bab46b83f5f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 28 Aug 2023 14:56:30 +0200 Subject: [PATCH 082/256] feat: graph::edge::weight customization point --- include/Simple-Utility/graph/Edge.hpp | 83 +++++++++++++++++++++++++ tests/graph/CMakeLists.txt | 1 + tests/graph/Edge.cpp | 87 +++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 include/Simple-Utility/graph/Edge.hpp create mode 100644 tests/graph/Edge.cpp diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp new file mode 100644 index 000000000..2e2856ff1 --- /dev/null +++ b/include/Simple-Utility/graph/Edge.hpp @@ -0,0 +1,83 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_EDGE_HPP +#define SIMPLE_UTILITY_GRAPH_EDGE_HPP + +#pragma once + +#include "Simple-Utility/Utility.hpp" +#include "Simple-Utility/concepts/stl_extensions.hpp" +#include "Simple-Utility/graph/Common.hpp" + +namespace sl::graph::customize +{ + template + struct weight_fn; +} + +namespace sl::graph::edge::detail +{ + template + requires requires(const Edge& node, customize::weight_fn fn) + { + requires concepts::weight>; + } + constexpr decltype(auto) weight(const Edge& node, const priority_tag<3>) noexcept(noexcept(customize::weight_fn{}(node))) + { + return customize::weight_fn{}(node); + } + + template + requires requires(const Edge& node) + { + requires concepts::weight>; + } + constexpr auto& weight(const Edge& node, const priority_tag<2>) noexcept + { + return node.weight; + } + + template + requires requires(const Edge& node) + { + requires concepts::weight>; + } + constexpr decltype(auto) weight(const Edge& node, const priority_tag<1>) noexcept(noexcept(node.weight())) + { + return node.weight(); + } + + template + requires requires(const Edge& node) + { + requires concepts::weight>; + } + constexpr decltype(auto) weight(const Edge& node, const priority_tag<0>) noexcept(noexcept(weight(node))) + { + return weight(node); + } + + struct weight_fn + { + template + requires requires(const Edge& node, const priority_tag<3> tag) + { + requires concepts::weight>; + } + constexpr decltype(auto) operator ()(const Edge& node) const noexcept(noexcept(detail::weight(node, priority_tag<3>{}))) + { + return detail::weight(node, priority_tag<3>{}); + } + }; +} + +namespace sl::graph::edge +{ + inline constexpr graph::detail::vertex_fn vertex{}; + inline constexpr detail::weight_fn weight{}; +} + +#endif diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index c58569b19..4ed2be422 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -2,6 +2,7 @@ target_sources( Simple-Utility-Tests PRIVATE "Common.cpp" + "Edge.cpp" "Node.cpp" "Queue.cpp" "Tracker.cpp" diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp new file mode 100644 index 000000000..19891694d --- /dev/null +++ b/tests/graph/Edge.cpp @@ -0,0 +1,87 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/Edge.hpp" + +#include +#include +#include +#include + +#include "Defines.hpp" + +namespace +{ + struct member_weight + { + // ReSharper disable once CppDeclaratorNeverUsed + int weight; + }; + + struct member_fun_weight + { + MAKE_CONST_MOCK0(weight, int()); + }; + + struct free_fun_weight + { + MAKE_CONST_MOCK0(my_weight, int()); + + // ReSharper disable once CppDeclaratorNeverUsed + friend int weight(const free_fun_weight& v) + { + return v.my_weight(); + } + }; + + struct custom_fun_weight + { + MAKE_CONST_MOCK0(my_weight, int()); + }; +} + +template <> +struct sg::customize::weight_fn +{ + [[nodiscard]] + decltype(auto) operator ()(const custom_fun_weight& e) const + { + return e.my_weight(); + } +}; + +TEST_CASE("graph::edge::weight serves as a customization point accessing the edge weight.", "[graph][graph::edge]") +{ + const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); + + SECTION("Access via member.") + { + REQUIRE(expected == sg::edge::weight(member_weight{expected})); + } + + SECTION("Access via member function.") + { + member_fun_weight mock{}; + REQUIRE_CALL(mock, weight()) + .RETURN(expected); + REQUIRE(expected == sg::edge::weight(std::as_const(mock))); + } + + SECTION("Access via free function.") + { + free_fun_weight mock{}; + REQUIRE_CALL(mock, my_weight()) + .RETURN(expected); + REQUIRE(expected == sg::edge::weight(std::as_const(mock))); + } + + SECTION("Access via custom function.") + { + custom_fun_weight mock{}; + REQUIRE_CALL(mock, my_weight()) + .RETURN(expected); + REQUIRE(expected == sg::edge::weight(std::as_const(mock))); + } +} From 4c73ed1f3e0271cdc002556ccef979b1c4f8b2e1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 28 Aug 2023 15:09:07 +0200 Subject: [PATCH 083/256] feat: edge concepts and traits --- include/Simple-Utility/graph/Edge.hpp | 49 ++++++++++++++++++++ tests/graph/Edge.cpp | 65 +++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index 2e2856ff1..5392a0382 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -78,6 +78,55 @@ namespace sl::graph::edge { inline constexpr graph::detail::vertex_fn vertex{}; inline constexpr detail::weight_fn weight{}; + + template + struct traits; + + template + requires concepts::readable_vertex_type + struct traits + { + using vertex_type = typename T::vertex_type; + }; + + template + requires concepts::readable_vertex_type + && concepts::readable_weight_type + struct traits + { + using vertex_type = typename T::vertex_type; + using weight_type = typename T::weight_type; + }; +} + +namespace sl::graph::concepts +{ + template + concept edge = sl::concepts::unqualified + && std::copyable + && std::destructible + && vertex::vertex_type> + && requires(const T& edge) + { + { edge::vertex(edge) } -> std::convertible_to::vertex_type>; + }; + + template + concept weighted_edge = edge + && weight::weight_type> + && requires(const T& edge) + { + { edge::weight(edge) } -> std::convertible_to::weight_type>; + }; +} + +namespace sl::graph::edge +{ + template + using vertex_t = typename traits::vertex_type; + + template + using weight_t = typename traits::weight_type; } #endif diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 19891694d..5de111f65 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -40,6 +40,22 @@ namespace { MAKE_CONST_MOCK0(my_weight, int()); }; + + struct minimal_edge + { + using vertex_type = std::string; + + vertex_type vertex; + }; + + struct minimal_weighted_edge + { + using vertex_type = std::string; + using weight_type = int; + + vertex_type vertex; + weight_type weight; + }; } template <> @@ -85,3 +101,52 @@ TEST_CASE("graph::edge::weight serves as a customization point accessing the edg REQUIRE(expected == sg::edge::weight(std::as_const(mock))); } } + +TEST_CASE( + "Default graph::edge::traits exposes vertex_type, if readable.", + "[graph][graph::edge]" +) +{ + using TestType = minimal_edge; + + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEST_CASE( + "Default graph::edge::traits exposes vertex_type and weight_type, if readable.", + "[graph][graph::edge]" +) +{ + using TestType = minimal_weighted_edge; + + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); + + STATIC_REQUIRE(std::same_as::weight_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEMPLATE_TEST_CASE_SIG( + "concepts::edge determines, whether the given type satisfies the requirements.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (true, minimal_edge), + (true, minimal_weighted_edge), + (true, BasicGraph>::edge_type) +) +{ + STATIC_REQUIRE(expected == sg::concepts::edge); +} + +TEMPLATE_TEST_CASE_SIG( + "concepts::weighted_edge determines, whether the given type satisfies the requirements.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (false, BasicGraph>::edge_type), + (false, minimal_edge), + (true, minimal_weighted_edge) +) +{ + STATIC_REQUIRE(expected == sg::concepts::weighted_edge); +} From 0f44313e0ebbbf57928cd9c603d434463b39fb45 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 28 Aug 2023 15:14:15 +0200 Subject: [PATCH 084/256] refactor: remove feature-categories and rework feature_traits to node traits --- include/Simple-Utility/graph/Common.hpp | 73 +-------- include/Simple-Utility/graph/Node.hpp | 64 ++++++-- include/Simple-Utility/graph/Traverse.hpp | 6 +- tests/graph/Common.cpp | 112 -------------- tests/graph/Defines.hpp | 13 +- tests/graph/Node.cpp | 175 +++++++++++++++------- tests/graph/Traverse.cpp | 8 +- 7 files changed, 190 insertions(+), 261 deletions(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 5056b4260..53089efe4 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -8,35 +8,14 @@ #pragma once -#include "Simple-Utility/TypeList.hpp" #include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/operators.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" -#include #include -namespace sl::graph -{ - struct basic_feature_category - { - }; - - struct ranked_feature_category - { - }; -} - -namespace sl::graph::detail -{ - using feature_category_list = type_list::TypeList; -} - namespace sl::graph::concepts { - template - concept feature_category = type_list::contains_v; - template concept vertex = sl::concepts::unqualified && std::equality_comparable @@ -61,7 +40,7 @@ namespace sl::graph::concepts template concept readable_weight_type = requires { typename T::weight_type; } - && weight; + && weight; template concept readable_rank_type = requires { typename T::rank_type; } @@ -76,7 +55,7 @@ namespace sl::graph::customize namespace sl::graph::detail { - template + template requires requires(const Node& node, customize::vertex_fn fn) { requires concepts::vertex>; @@ -130,52 +109,4 @@ namespace sl::graph::detail }; } -namespace sl::graph -{ - template - struct common_feature_category - { - using type = std::tuple_element_t< - std::min( // clang seems to have issues with std::ranges::min - { - type_list::index_of_v, - type_list::index_of_v... - }), - detail::feature_category_list>; - }; - - template - using common_feature_category_t = typename common_feature_category::type; - - template - struct feature_traits; - - template - using feature_category_t = typename feature_traits::category_type; - - template - using feature_vertex_t = typename feature_traits::vertex_type; - - template - using feature_rank_t = typename feature_traits::rank_type; - - template - requires concepts::readable_vertex_type - struct feature_traits - { - using category_type = basic_feature_category; - using vertex_type = typename T::vertex_type; - }; - - template - requires concepts::readable_vertex_type - && concepts::readable_rank_type - struct feature_traits - { - using category_type = ranked_feature_category; - using vertex_type = typename T::vertex_type; - using rank_type = typename T::rank_type; - }; -} - #endif diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 194350a8c..5ddecb851 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -11,6 +11,7 @@ #include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Edge.hpp" namespace sl::graph::customize { @@ -81,6 +82,25 @@ namespace sl::graph::node { inline constexpr graph::detail::vertex_fn vertex{}; inline constexpr detail::rank_fn rank{}; + + template + struct traits; + + template + requires concepts::readable_vertex_type + struct traits + { + using vertex_type = typename T::vertex_type; + }; + + template + requires concepts::readable_vertex_type + && concepts::readable_rank_type + struct traits + { + using vertex_type = typename T::vertex_type; + using rank_type = typename T::rank_type; + }; } namespace sl::graph::concepts @@ -89,11 +109,30 @@ namespace sl::graph::concepts concept node = sl::concepts::unqualified && std::copyable && std::destructible - && feature_category::category_type> - && vertex::vertex_type> + && vertex::vertex_type> && requires(const T& node) { - requires concepts::vertex>; + { node::vertex(node) } -> std::convertible_to::vertex_type>; + }; + + template + concept ranked_node = node + && rank::rank_type> + && requires(const T& node) + { + { node::rank(node) } -> std::convertible_to::rank_type>; + }; +} + +namespace sl::graph::node +{ + template + using vertex_t = typename traits::vertex_type; + + template + using rank_t = typename traits::rank_type; +} + }; template @@ -103,16 +142,19 @@ namespace sl::graph::concepts && requires(T& factory, const Node& node) { { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; - { factory.make_successor_node(node, {}) } -> std::convertible_to; + /* Well, the info param is quite tricky here. We could simply expect it to be default constructible and {} it, + * but this will fail for templated types. So, just insert the node type itself, as this should provide + * as many info as we need.. */ + { factory.make_successor_node(node, node) } -> std::convertible_to; }; - template - concept compatible_with = node - && node - && std::same_as< - feature_category_t, - common_feature_category_t, feature_category_t>> - && std::convertible_to, feature_vertex_t>; + template + concept compatible_with = node + && edge + && std::same_as, node::vertex_t> + && (!ranked_node || weighted_edge && std::convertible_to< + edge::weight_t, node::rank_t>) + ; template concept graph_for = sl::concepts::unqualified diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index fe07b8636..88d42cd23 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -124,13 +124,13 @@ namespace sl::graph::detail template < concepts::node Node, state_for StateStrategy, - concepts::tracker_for> TrackingStrategy, + concepts::tracker_for> TrackingStrategy, concepts::node_factory_for NodeFactoryStrategy> class BasicTraverseDriver { public: using node_type = Node; - using vertex_type = feature_vertex_t; + using vertex_type = node::vertex_t; using state_type = StateStrategy; using tracker_type = TrackingStrategy; using node_factory_type = NodeFactoryStrategy; @@ -215,7 +215,7 @@ namespace sl::graph { public: using node_type = typename Driver::node_type; - using vertex_type = feature_vertex_t; + using vertex_type = node::vertex_t; [[nodiscard]] constexpr explicit Traverser(Graph graph, vertex_type origin) diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 4e9708058..77ba72008 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -155,32 +155,6 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == sg::concepts::rank); } -TEMPLATE_TEST_CASE_SIG( - "graph::concepts::feature_category determines whether the given type denotes a feature category.", - "[graph][graph::concepts]", - ((bool expected, class T), expected, T), - (false, int), - (true, sg::basic_feature_category), - (true, sg::ranked_feature_category) -) -{ - STATIC_REQUIRE(expected == sg::concepts::feature_category); -} - -TEMPLATE_TEST_CASE_SIG( - "graph::common_feature_category trait yields the strictest category.", - "[graph][graph::traits]", - ((bool dummy, class Expected, class T, class... Others), dummy, Expected, T, Others...), - (true, sg::basic_feature_category, sg::basic_feature_category), - (true, sg::ranked_feature_category, sg::ranked_feature_category), - (true, sg::basic_feature_category, sg::ranked_feature_category, sg::basic_feature_category), - (true, sg::basic_feature_category, sg::ranked_feature_category, sg::basic_feature_category, sg::ranked_feature_category) -) -{ - STATIC_REQUIRE(std::same_as::type>); - STATIC_REQUIRE(std::same_as>); -} - namespace { struct non_readable_vertex_type @@ -230,20 +204,7 @@ namespace using vertex_type = valid_vertex; using rank_type = valid_rank; }; - - struct type_with_custom_trait - { - }; } - -template <> -struct sg::feature_traits -{ - using category_type = ranked_feature_category; - using vertex_type = int; - using rank_type = float; -}; - template <> struct sg::customize::vertex_fn { @@ -254,7 +215,6 @@ struct sg::customize::vertex_fn } }; - TEMPLATE_TEST_CASE_SIG( "graph::concepts::readable_vertex_type determines whether T contains a \"vertex_type\" member alias.", "[graph][graph::concepts]", @@ -291,78 +251,6 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == sg::concepts::readable_rank_type); } -TEST_CASE( - "graph::feature_traits categorizes T as basic_feature_category, if just vertex_type is readable.", - "[graph][graph::traits]" -) -{ - using TestType = readable_vertex_type; - - STATIC_REQUIRE(std::same_as::category_type>); - STATIC_REQUIRE(std::same_as>); - - STATIC_REQUIRE(std::same_as::vertex_type>); - STATIC_REQUIRE(std::same_as>); -} - -TEST_CASE( - "graph::feature_traits categorizes T as ranked_feature_category, if vertex_type and rank_type are readable.", - "[graph][graph::traits]" -) -{ - using TestType = ranked_feature_category_type; - - STATIC_REQUIRE(std::same_as::category_type>); - STATIC_REQUIRE(std::same_as>); - - STATIC_REQUIRE(std::same_as::vertex_type>); - STATIC_REQUIRE(std::same_as>); - - STATIC_REQUIRE(std::same_as::rank_type>); - STATIC_REQUIRE(std::same_as>); -} - -TEST_CASE( - "graph::feature_traits can be specialized.", - "[graph][graph::traits]" -) -{ - using TestType = type_with_custom_trait; - - STATIC_REQUIRE(std::same_as::category_type>); - STATIC_REQUIRE(std::same_as>); - - STATIC_REQUIRE(std::same_as::vertex_type>); - STATIC_REQUIRE(std::same_as>); - - STATIC_REQUIRE(std::same_as::rank_type>); - STATIC_REQUIRE(std::same_as>); -} - -TEMPLATE_TEST_CASE( - "Listed types yield at least basic_feature_category.", - "[graph][graph::traits]", - BasicTestNode, - (RankedTestNode), - (BasicGraph>::info) -) -{ - STATIC_REQUIRE(std::same_as< - sg::basic_feature_category, - sg::common_feature_category_t>>); -} - -TEMPLATE_TEST_CASE( - "Listed types yield at least ranked_feature_category.", - "[graph][graph::traits]", - (RankedTestNode) -) -{ - STATIC_REQUIRE(std::same_as< - sg::ranked_feature_category, - sg::common_feature_category_t>>); -} - TEST_CASE("graph::details::vertex serves as a customization point accessing the vertex.", "[graph][detail]") { constexpr sg::detail::vertex_fn fun{}; diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index b6bfe444a..b69aaa703 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -107,24 +107,25 @@ class BasicNodeFactoryMock public: inline static constexpr bool trompeloeil_movable_mock = true; - using vertex_type = sg::feature_vertex_t; + using node_type = Node; + using vertex_type = sg::node::vertex_t; MAKE_MOCK1(make_init_node, Node(const vertex_type&)); - MAKE_MOCK2(make_successor_node, Node(const Node&, const VertexInfo&)); + MAKE_MOCK2(make_successor_node, node_type(const node_type&, const VertexInfo&)); }; template class BasicGraph { public: - struct info + struct edge_type { - using vertex_type = sg::feature_vertex_t; + using vertex_type = sg::node::vertex_t; vertex_type vertex; - friend bool operator ==(const info&, const info&) = default; + friend bool operator ==(const edge_type&, const edge_type&) = default; }; - MAKE_CONST_MOCK1(neighbor_infos, std::vector(const Node&)); + MAKE_CONST_MOCK1(neighbor_infos, std::vector(const Node&)); }; diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 005e787e9..fecae03ad 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -12,35 +12,10 @@ #include "Defines.hpp" +#include + namespace { - struct member_vertex - { - // ReSharper disable once CppDeclaratorNeverUsed - int vertex; - }; - - struct member_fun_vertex - { - MAKE_CONST_MOCK0(vertex, int()); - }; - - struct free_fun_vertex - { - MAKE_CONST_MOCK0(my_vertex, int()); - - // ReSharper disable once CppDeclaratorNeverUsed - friend int vertex(const free_fun_vertex& v) - { - return v.my_vertex(); - } - }; - - struct custom_fun_vertex - { - MAKE_CONST_MOCK0(my_vertex, int()); - }; - struct member_rank { // ReSharper disable once CppDeclaratorNeverUsed @@ -75,38 +50,77 @@ namespace vertex_type vertex; }; + struct ranked_node + { + using vertex_type = std::string; + using rank_type = int; + + vertex_type vertex; + rank_type rank; + }; + struct minimal_node_factory { using node_type = minimal_node; - using vertex_type = sg::feature_vertex_t; + using vertex_type = sg::node::vertex_t; static node_type make_init_node(const vertex_type& v) { return {.vertex = v}; } - static node_type make_successor_node([[maybe_unused]] const node_type& predecessor, const vertex_type& v) + static node_type make_successor_node([[maybe_unused]] const node_type& predecessor, const node_type& v) { - return {.vertex = v}; + return {.vertex = sg::node::vertex(v)}; } }; - struct generic_basic_graph + struct generic_basic_graph_stub { - struct info + struct edge_type { using vertex_type = int; vertex_type vertex; }; [[nodiscard]] - static std::vector neighbor_infos(const sg::concepts::node auto&) + static std::vector neighbor_infos(const sg::concepts::node auto&) { return {}; } }; + + struct generic_ranked_graph_stub + { + struct edge_type + { + using vertex_type = std::string; + using weight_type = int; + vertex_type vertex; + weight_type weight; + }; + + [[nodiscard]] + static std::vector neighbor_infos(const sg::concepts::ranked_node auto&) + { + return {}; +} + }; + + struct node_with_custom_trait + { + int vertex; + float rank; + }; } +template <> +struct sg::node::traits +{ + using vertex_type = int; + using rank_type = float; +}; + template <> struct sg::customize::rank_fn { @@ -152,28 +166,81 @@ TEST_CASE("graph::node::rank serves as a customization point accessing the node } TEMPLATE_TEST_CASE_SIG( - "concepts::node determines, whether the given type satisfies the node requirements.", + "concepts::node determines, whether the given type satisfies the requirements.", "[graph][graph::concepts]", ((bool expected, class T), expected, T), - (false, member_vertex), - (false, member_fun_vertex), - (false, free_fun_vertex), (true, minimal_node), - (true, BasicTestNode), - (true, generic_basic_graph::info), - (true, BasicGraph::info) + (true, ranked_node), + (true, BasicTestNode) ) { STATIC_REQUIRE(expected == sg::concepts::node); } +TEMPLATE_TEST_CASE_SIG( + "concepts::ranked_node determines, whether the given type satisfies the requirements.", + "[graph][graph::concepts]", + ((bool expected, class T), expected, T), + (false, member_rank), + (false, member_fun_rank), + (false, free_fun_rank), + (false, minimal_node), + (true, ranked_node) +) +{ + STATIC_REQUIRE(expected == sg::concepts::ranked_node); +} + +TEST_CASE( + "Default graph::node::traits exposes vertex_type if readable.", + "[graph][graph::node]" +) +{ + using TestType = minimal_node; + + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEST_CASE( + "Default graph::node::traits exposes vertex_type and rank_type if readable.", + "[graph][graph::node]" +) +{ + using TestType = ranked_node; + + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); + + STATIC_REQUIRE(std::same_as::rank_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEST_CASE( + "graph::node::traits can be specialized.", + "[graph][graph::node]" +) +{ + using TestType = node_with_custom_trait; + + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); + + STATIC_REQUIRE(std::same_as::rank_type>); + STATIC_REQUIRE(std::same_as>); +} + TEMPLATE_TEST_CASE_SIG( "concepts::node_factory_for determines, whether the given type satisfies the requirements of a node_factory for the specified node type.", "[graph][graph::concepts]", ((bool expected, class Factory, class Node), expected, Factory, Node), (false, minimal_node_factory, BasicTestNode), (true, minimal_node_factory, minimal_node), - (true, BasicNodeFactoryMock, minimal_node) + (true, BasicNodeFactoryMock, minimal_node), + (true, sg::NodeFactory>, sg::BasicNode), + (false, sg::NodeFactory>, sg::BasicNode), + (false, sg::NodeFactory>, sg::RankedNode), + (true, sg::NodeFactory>, sg::RankedNode) ) { STATIC_REQUIRE(expected == sg::concepts::node_factory_for); @@ -182,27 +249,27 @@ TEMPLATE_TEST_CASE_SIG( TEMPLATE_TEST_CASE_SIG( "concepts::compatible_with determines, whether the other type is compatible with T.", "[graph][graph::concepts]", - ((bool expected, class T, class Other), expected, T, Other), - (false, minimal_node, BasicTestNode), - (true, minimal_node, BasicTestNode), - (true, minimal_node, BasicGraph::info), - (true, BasicTestNode, BasicGraph::info), - (false, BasicTestNode, BasicGraph::info), - (true, minimal_node, generic_basic_graph::info), - (true, BasicTestNode, generic_basic_graph::info), - (false, BasicTestNode, generic_basic_graph::info) + ((bool expected, class Node, class Edge), expected, Node, Edge), + (true, minimal_node, BasicGraph::edge_type), + (true, BasicTestNode, BasicGraph::edge_type), + (false, BasicTestNode, BasicGraph::edge_type), + (true, minimal_node, generic_basic_graph_stub::edge_type), + (true, BasicTestNode, generic_basic_graph_stub::edge_type), + (false, BasicTestNode, generic_basic_graph_stub::edge_type), + (false, ranked_node, generic_basic_graph_stub::edge_type), + (true, ranked_node, generic_ranked_graph_stub::edge_type) ) { - STATIC_REQUIRE(expected == sg::concepts::compatible_with); + STATIC_REQUIRE(expected == sg::concepts::compatible_with); } TEMPLATE_TEST_CASE_SIG( "concepts::graph_for determines, whether the graph type satisfies the minimal requirements of the specified node.", "[graph][graph::concepts]", ((bool expected, class Graph, class Node), expected, Graph, Node), - (true, generic_basic_graph, minimal_node), - (true, generic_basic_graph, BasicTestNode), - (false, generic_basic_graph, BasicTestNode), + (true, generic_basic_graph_stub, minimal_node), + (true, generic_basic_graph_stub, BasicTestNode), + (false, generic_basic_graph_stub, BasicTestNode), (true, BasicGraph, minimal_node), (false, BasicGraph, BasicTestNode), (false, BasicGraph, BasicTestNode) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index f5577e4c6..ab8ef4b35 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -35,9 +35,9 @@ namespace using DefaultNode = VertexMemberNode; using DefaultQueue = QueueMock; using DefaultState = sg::detail::BasicState; -using DefaultTracker = TrackerMock>; +using DefaultTracker = TrackerMock>; using DefaultGraph = BasicGraph; -using DefaultNodeFactory = BasicNodeFactoryMock; +using DefaultNodeFactory = BasicNodeFactoryMock; using DefaultDriver = sg::detail::BasicTraverseDriver< DefaultNode, DefaultState, @@ -168,7 +168,7 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." }; }(); - using VertexInfo = DefaultGraph::info; + using VertexInfo = DefaultGraph::edge_type; DefaultGraph graphMock{}; auto& nodeFactoryMock = const_cast(driver.node_factory()); auto& queueMock = const_cast(driver.state().queue()); @@ -206,7 +206,7 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." SECTION("Next returns std::nullopt.") { REQUIRE_CALL(graphMock, neighbor_infos(originNode)) - .RETURN(std::vector{}); + .RETURN(std::vector{}); REQUIRE_CALL(queueMock, do_insert(matches(RangesEmpty{}))); REQUIRE_CALL(queueMock, empty()) From aaa843839148af2d2426ece446bf189e28bae880 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 5 Sep 2023 19:09:28 +0200 Subject: [PATCH 085/256] refactor: rename compatible_with to edge_for --- include/Simple-Utility/graph/Node.hpp | 7 +++---- tests/graph/Node.cpp | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 5ddecb851..9f6c362fd 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -149,12 +149,11 @@ namespace sl::graph::node }; template - concept compatible_with = node + concept edge_for = node && edge && std::same_as, node::vertex_t> && (!ranked_node || weighted_edge && std::convertible_to< - edge::weight_t, node::rank_t>) - ; + edge::weight_t, node::rank_t>); template concept graph_for = sl::concepts::unqualified @@ -162,7 +161,7 @@ namespace sl::graph::node && requires(const T& graph, const Node& node) { { graph.neighbor_infos(node) } -> std::ranges::input_range; - requires compatible_with< + requires edge_for< Node, std::ranges::range_value_t>; }; diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index fecae03ad..dba5ad9a6 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -247,7 +247,7 @@ TEMPLATE_TEST_CASE_SIG( } TEMPLATE_TEST_CASE_SIG( - "concepts::compatible_with determines, whether the other type is compatible with T.", + "concepts::edge_for determines, whether the Edge type satisfies the minimal requirements of the Node type.", "[graph][graph::concepts]", ((bool expected, class Node, class Edge), expected, Node, Edge), (true, minimal_node, BasicGraph::edge_type), @@ -260,7 +260,7 @@ TEMPLATE_TEST_CASE_SIG( (true, ranked_node, generic_ranked_graph_stub::edge_type) ) { - STATIC_REQUIRE(expected == sg::concepts::compatible_with); + STATIC_REQUIRE(expected == sg::concepts::edge_for); } TEMPLATE_TEST_CASE_SIG( From bdd35f9bf40ab14bb96dca2f883110a5a890df24 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 5 Sep 2023 21:22:10 +0200 Subject: [PATCH 086/256] fix: concepts::edge_for --- include/Simple-Utility/graph/Edge.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index 5392a0382..47ae12e4d 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -11,6 +11,7 @@ #include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Node.hpp" namespace sl::graph::customize { @@ -129,4 +130,14 @@ namespace sl::graph::edge using weight_t = typename traits::weight_type; } +namespace sl::graph::concepts +{ + template + concept edge_for = node + && edge + && std::same_as, node::vertex_t> + && (!ranked_node + || weighted_edge && std::convertible_to, node::rank_t>); +} + #endif From 54d0705c4fa2c2aac35b5af112462ac5361d5769 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 5 Sep 2023 21:53:05 +0200 Subject: [PATCH 087/256] refactor: extract view symbols from graph/Node.hpp --- include/Simple-Utility/graph/Node.hpp | 18 -------- include/Simple-Utility/graph/View.hpp | 50 ++++++++++++++++++++ tests/graph/CMakeLists.txt | 1 + tests/graph/Defines.hpp | 37 +++++++++++++++ tests/graph/Node.cpp | 14 ------ tests/graph/View.cpp | 66 +++++++++++++++++++++++++++ 6 files changed, 154 insertions(+), 32 deletions(-) create mode 100644 include/Simple-Utility/graph/View.hpp create mode 100644 tests/graph/View.cpp diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 9f6c362fd..9ce55f16a 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -148,22 +148,4 @@ namespace sl::graph::node { factory.make_successor_node(node, node) } -> std::convertible_to; }; - template - concept edge_for = node - && edge - && std::same_as, node::vertex_t> - && (!ranked_node || weighted_edge && std::convertible_to< - edge::weight_t, node::rank_t>); - - template - concept graph_for = sl::concepts::unqualified - && node - && requires(const T& graph, const Node& node) - { - { graph.neighbor_infos(node) } -> std::ranges::input_range; - requires edge_for< - Node, - std::ranges::range_value_t>; - }; -} #endif diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp new file mode 100644 index 000000000..28bc54ad2 --- /dev/null +++ b/include/Simple-Utility/graph/View.hpp @@ -0,0 +1,50 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_VIEW_HPP +#define SIMPLE_UTILITY_GRAPH_VIEW_HPP + +#pragma once + +#include "Simple-Utility/graph/Edge.hpp" +#include "Simple-Utility/graph/Node.hpp" + +namespace sl::graph::view +{ + template + struct traits; +} + +namespace sl::graph::concepts +{ + template + concept view_for = node + && sl::concepts::unqualified + && requires(const T& view, const Node& node) + { + typename view::traits::edge_type; + requires edge_for::edge_type, Node>; + { view.edges(node) } -> std::ranges::input_range; + requires std::convertible_to< + std::ranges::range_value_t, + typename view::traits::edge_type>; + }; +} + +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/CMakeLists.txt b/tests/graph/CMakeLists.txt index 4ed2be422..18f2714f3 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -7,4 +7,5 @@ target_sources( "Queue.cpp" "Tracker.cpp" "Traverse.cpp" + "View.cpp" ) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index b69aaa703..acbec9a28 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -11,9 +11,46 @@ #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Tracker.hpp" +#include "Simple-Utility/graph/View.hpp" namespace sg = sl::graph; +template +struct GenericBasicNode +{ + using vertex_type = Vertex; + + vertex_type vertex; +}; + +template +struct GenericRankedNode +{ + using vertex_type = Vertex; + using rank_type = Rank; + + vertex_type vertex; + rank_type rank; +}; + +template +struct GenericBasicEdge +{ + using vertex_type = Vertex; + + vertex_type vertex; +}; + +template +struct GenericWeightedEdge +{ + using vertex_type = Vertex; + using weight_type = Weight; + + vertex_type vertex; + weight_type weight; +}; + template struct BasicTestNode { diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index dba5ad9a6..81231ee74 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -263,17 +263,3 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == sg::concepts::edge_for); } -TEMPLATE_TEST_CASE_SIG( - "concepts::graph_for determines, whether the graph type satisfies the minimal requirements of the specified node.", - "[graph][graph::concepts]", - ((bool expected, class Graph, class Node), expected, Graph, Node), - (true, generic_basic_graph_stub, minimal_node), - (true, generic_basic_graph_stub, BasicTestNode), - (false, generic_basic_graph_stub, BasicTestNode), - (true, BasicGraph, minimal_node), - (false, BasicGraph, BasicTestNode), - (false, BasicGraph, BasicTestNode) -) -{ - STATIC_REQUIRE(expected == sg::concepts::graph_for); -} diff --git a/tests/graph/View.cpp b/tests/graph/View.cpp new file mode 100644 index 000000000..d606e88fe --- /dev/null +++ b/tests/graph/View.cpp @@ -0,0 +1,66 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/View.hpp" + +#include + +#include "Defines.hpp" + +namespace +{ + template + struct GenericBasicView + { + using edge_type = GenericBasicEdge; + + template + requires sg::concepts::edge_for + // ReSharper disable once CppFunctionIsNotImplemented + static std::vector edges(const Node&); + }; + + template + struct GenericWeightedView + { + using edge_type = GenericWeightedEdge; + + template + requires sg::concepts::edge_for + // ReSharper disable once CppFunctionIsNotImplemented + static std::vector edges(const Node&); + }; +} + +TEMPLATE_TEST_CASE_SIG( + "view::traits extracts edge type.", + "[graph][graph::view]", + ((bool dummy, class Expected, class Graph), dummy, Expected, Graph), + (true, GenericBasicEdge, GenericBasicView), + (true, GenericBasicEdge, GenericBasicView), + (true, GenericWeightedEdge, GenericWeightedView) +) +{ + STATIC_REQUIRE(std::same_as::edge_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEMPLATE_TEST_CASE_SIG( + "concepts::view_for determines, whether the view type satisfies the minimal requirements of the specified node.", + "[graph][graph::concepts]", + ((bool expected, class Graph, class Node), expected, Graph, Node), + (false, GenericBasicView, GenericBasicNode), + (true, GenericBasicView, GenericBasicNode), + (true, GenericWeightedView, GenericBasicNode), + + (false, GenericBasicView, GenericRankedNode), + (false, GenericWeightedView, GenericRankedNode), + (true, GenericWeightedView, GenericRankedNode), + + (true, BasicView>, GenericBasicNode) +) +{ + STATIC_REQUIRE(expected == sg::concepts::view_for); +} From 2ed923e398c1a8d456fa7741303b51e471c4cea2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 5 Sep 2023 22:08:54 +0200 Subject: [PATCH 088/256] refactor: extract concepts::edge_for into Edge.hpp --- include/Simple-Utility/graph/Node.hpp | 1 - tests/graph/Defines.hpp | 1 + tests/graph/Edge.cpp | 53 +++++++++++++++------------ tests/graph/Node.cpp | 50 ------------------------- 4 files changed, 30 insertions(+), 75 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 9ce55f16a..f77b99e43 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -11,7 +11,6 @@ #include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" -#include "Simple-Utility/graph/Edge.hpp" namespace sl::graph::customize { diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index acbec9a28..8a7458750 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -9,6 +9,7 @@ #include #include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Edge.hpp" #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Tracker.hpp" #include "Simple-Utility/graph/View.hpp" diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 5de111f65..53399b750 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -40,22 +40,6 @@ namespace { MAKE_CONST_MOCK0(my_weight, int()); }; - - struct minimal_edge - { - using vertex_type = std::string; - - vertex_type vertex; - }; - - struct minimal_weighted_edge - { - using vertex_type = std::string; - using weight_type = int; - - vertex_type vertex; - weight_type weight; - }; } template <> @@ -107,7 +91,7 @@ TEST_CASE( "[graph][graph::edge]" ) { - using TestType = minimal_edge; + using TestType = GenericBasicEdge; STATIC_REQUIRE(std::same_as::vertex_type>); STATIC_REQUIRE(std::same_as>); @@ -118,7 +102,7 @@ TEST_CASE( "[graph][graph::edge]" ) { - using TestType = minimal_weighted_edge; + using TestType = GenericWeightedEdge; STATIC_REQUIRE(std::same_as::vertex_type>); STATIC_REQUIRE(std::same_as>); @@ -131,9 +115,12 @@ TEMPLATE_TEST_CASE_SIG( "concepts::edge determines, whether the given type satisfies the requirements.", "[graph][graph::concepts]", ((bool expected, class T), expected, T), - (true, minimal_edge), - (true, minimal_weighted_edge), - (true, BasicGraph>::edge_type) + (false, member_weight), + (false, member_fun_weight), + (false, free_fun_weight), + (false, custom_fun_weight), + (true, GenericBasicEdge), + (true, GenericWeightedEdge) ) { STATIC_REQUIRE(expected == sg::concepts::edge); @@ -143,10 +130,28 @@ TEMPLATE_TEST_CASE_SIG( "concepts::weighted_edge determines, whether the given type satisfies the requirements.", "[graph][graph::concepts]", ((bool expected, class T), expected, T), - (false, BasicGraph>::edge_type), - (false, minimal_edge), - (true, minimal_weighted_edge) + (false, member_weight), + (false, member_fun_weight), + (false, free_fun_weight), + (false, custom_fun_weight), + (false, GenericBasicEdge), + (true, GenericWeightedEdge) ) { STATIC_REQUIRE(expected == sg::concepts::weighted_edge); } + +TEMPLATE_TEST_CASE_SIG( + "concepts::edge_for determines, whether the Edge type satisfies the minimal requirements of the Node type.", + "[graph][graph::concepts]", + ((bool expected, class Edge, class Node), expected, Edge, Node), + (false, GenericBasicEdge, GenericBasicNode), + (true, GenericBasicEdge, GenericBasicNode), + (true, (GenericWeightedEdge), GenericBasicNode), + + (false, GenericBasicEdge, (GenericRankedNode)), + (true, (GenericWeightedEdge), (GenericRankedNode)) +) +{ + STATIC_REQUIRE(expected == sg::concepts::edge_for); +} diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 81231ee74..cbf3c35f0 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -74,39 +74,6 @@ namespace return {.vertex = sg::node::vertex(v)}; } }; - - struct generic_basic_graph_stub - { - struct edge_type - { - using vertex_type = int; - vertex_type vertex; - }; - - [[nodiscard]] - static std::vector neighbor_infos(const sg::concepts::node auto&) - { - return {}; - } - }; - - struct generic_ranked_graph_stub - { - struct edge_type - { - using vertex_type = std::string; - using weight_type = int; - vertex_type vertex; - weight_type weight; - }; - - [[nodiscard]] - static std::vector neighbor_infos(const sg::concepts::ranked_node auto&) - { - return {}; -} - }; - struct node_with_custom_trait { int vertex; @@ -246,20 +213,3 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == sg::concepts::node_factory_for); } -TEMPLATE_TEST_CASE_SIG( - "concepts::edge_for determines, whether the Edge type satisfies the minimal requirements of the Node type.", - "[graph][graph::concepts]", - ((bool expected, class Node, class Edge), expected, Node, Edge), - (true, minimal_node, BasicGraph::edge_type), - (true, BasicTestNode, BasicGraph::edge_type), - (false, BasicTestNode, BasicGraph::edge_type), - (true, minimal_node, generic_basic_graph_stub::edge_type), - (true, BasicTestNode, generic_basic_graph_stub::edge_type), - (false, BasicTestNode, generic_basic_graph_stub::edge_type), - (false, ranked_node, generic_basic_graph_stub::edge_type), - (true, ranked_node, generic_ranked_graph_stub::edge_type) -) -{ - STATIC_REQUIRE(expected == sg::concepts::edge_for); -} - From 90840446d5ec74817eefba1a13ed198fc91ba0f4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 5 Sep 2023 22:10:47 +0200 Subject: [PATCH 089/256] refactor: rename BasickView to BasicViewMock --- include/Simple-Utility/graph/Traverse.hpp | 15 ++++++++------- tests/graph/Defines.hpp | 20 ++++++++++++++++---- tests/graph/Traverse.cpp | 6 +++--- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 88d42cd23..bcb4af827 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -15,6 +15,7 @@ #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Queue.hpp" #include "Simple-Utility/graph/Tracker.hpp" +#include "Simple-Utility/graph/View.hpp" #include #include @@ -157,12 +158,12 @@ namespace sl::graph::detail assert(result && "Tracker returned false (already visited) for the origin node."); } - template Graph> // let the concept here, because otherwise it results in an ICE on msvc v142 + template Graph> // let the concept here, because otherwise it results in an ICE on msvc v142 [[nodiscard]] constexpr std::optional next(const Graph& graph) { std::optional result = m_State.next( - filter_transform(graph.neighbor_infos(m_Current), m_Tracker, m_NodeFactory, m_Current)); + filter_transform(graph.edges(m_Current), m_Tracker, m_NodeFactory, m_Current)); for (; result; result = m_State.next(std::array{})) { if (tracker::set_visited(m_Tracker, node::vertex(*result))) @@ -210,7 +211,7 @@ namespace sl::graph::detail namespace sl::graph { - template + template class Traverser final { public: @@ -218,8 +219,8 @@ namespace sl::graph using vertex_type = node::vertex_t; [[nodiscard]] - constexpr explicit Traverser(Graph graph, vertex_type origin) - : m_Graph{std::move(graph)}, + constexpr explicit Traverser(View view, vertex_type origin) + : m_View{std::move(view)}, m_Driver{std::move(origin), std::tuple{}, std::tuple{}, std::tuple{}} { } @@ -227,7 +228,7 @@ namespace sl::graph [[nodiscard]] std::optional next() { - return m_Driver.next(m_Graph); + return m_Driver.next(m_View); } struct Sentinel final @@ -294,7 +295,7 @@ namespace sl::graph } private: - Graph m_Graph{}; + View m_View{}; Driver m_Driver{}; }; } diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 8a7458750..0c1dafe27 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -139,7 +139,7 @@ class TrackerMock }; template - requires sg::concepts::compatible_with + requires sg::concepts::edge_for class BasicNodeFactoryMock { public: @@ -150,10 +150,22 @@ class BasicNodeFactoryMock MAKE_MOCK1(make_init_node, Node(const vertex_type&)); MAKE_MOCK2(make_successor_node, node_type(const node_type&, const VertexInfo&)); + + template + requires sg::concepts::edge_for + node_type make_successor_node(const node_type& current, const Info& info) + { + VertexInfo convertedInfo{.vertex = sg::node::vertex(info)}; + if constexpr (sg::concepts::ranked_node) + { + convertedInfo.rank = sg::node::rank(info); + } + return make_successor_node(current, convertedInfo); + } }; template -class BasicGraph +class BasicViewMock { public: struct edge_type @@ -162,8 +174,8 @@ class BasicGraph vertex_type vertex; - friend bool operator ==(const edge_type&, const edge_type&) = default; + friend bool operator ==(const edge_type&, const edge_type&) = default; }; - MAKE_CONST_MOCK1(neighbor_infos, std::vector(const Node&)); + MAKE_CONST_MOCK1(edges, std::vector(const Node&)); }; diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index ab8ef4b35..c073d6d47 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -36,7 +36,7 @@ using DefaultNode = VertexMemberNode; using DefaultQueue = QueueMock; using DefaultState = sg::detail::BasicState; using DefaultTracker = TrackerMock>; -using DefaultGraph = BasicGraph; +using DefaultGraph = BasicViewMock; using DefaultNodeFactory = BasicNodeFactoryMock; using DefaultDriver = sg::detail::BasicTraverseDriver< DefaultNode, @@ -177,7 +177,7 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." SECTION("Next returns a node.") { // vertex 43 will be skipped on purpose - REQUIRE_CALL(graphMock, neighbor_infos(originNode)) + REQUIRE_CALL(graphMock, edges(originNode)) .RETURN(std::vector{{41}, {43}, {44}}); REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{41})) @@ -205,7 +205,7 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." SECTION("Next returns std::nullopt.") { - REQUIRE_CALL(graphMock, neighbor_infos(originNode)) + REQUIRE_CALL(graphMock, edges(originNode)) .RETURN(std::vector{}); REQUIRE_CALL(queueMock, do_insert(matches(RangesEmpty{}))); From a96de394932aefef8388261aa28e8840e6e18731 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 5 Sep 2023 22:14:08 +0200 Subject: [PATCH 090/256] refactor: remove BasicTestNode and RankedTestNode --- tests/graph/Defines.hpp | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 0c1dafe27..1d61c711a 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -52,45 +52,6 @@ struct GenericWeightedEdge weight_type weight; }; -template -struct BasicTestNode -{ - using vertex_type = Vertex; - - struct Mock - { - MAKE_CONST_MOCK1(vertex, vertex_type(const BasicTestNode&)); - } inline static mock{}; - - friend constexpr vertex_type vertex(const BasicTestNode& node) - { - return mock.vertex(node); - } -}; - -template -struct RankedTestNode -{ - using vertex_type = Vertex; - using rank_type = Rank; - - struct Mock - { - MAKE_CONST_MOCK1(vertex, vertex_type(const RankedTestNode&)); - MAKE_CONST_MOCK1(rank, rank_type(const RankedTestNode&)); - } inline static mock{}; - - friend constexpr vertex_type vertex(const RankedTestNode& node) - { - return mock.vertex(node); - } - - friend constexpr rank_type rank(const RankedTestNode& node) - { - return mock.rank(node); - } -}; - template class QueueMock { From 07eb0880fe60c8c6c49fdaa774a0cfbfbff83a39 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 5 Sep 2023 22:29:08 +0200 Subject: [PATCH 091/256] refactor: traverse test code --- tests/graph/Defines.hpp | 24 +++++++++++++++--------- tests/graph/Queue.cpp | 1 - tests/graph/Traverse.cpp | 33 +++++++++++---------------------- tests/graph/View.cpp | 2 +- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 1d61c711a..7624f2b24 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -22,6 +22,8 @@ struct GenericBasicNode using vertex_type = Vertex; vertex_type vertex; + + friend bool operator==(const GenericBasicNode&, const GenericBasicNode&) = default; }; template @@ -32,6 +34,8 @@ struct GenericRankedNode vertex_type vertex; rank_type rank; + + friend bool operator==(const GenericRankedNode&, const GenericRankedNode&) = default; }; template @@ -40,6 +44,8 @@ struct GenericBasicEdge using vertex_type = Vertex; vertex_type vertex; + + friend bool operator==(const GenericBasicEdge&, const GenericBasicEdge&) = default; }; template @@ -50,6 +56,8 @@ struct GenericWeightedEdge vertex_type vertex; weight_type weight; + + friend bool operator==(const GenericWeightedEdge&, const GenericWeightedEdge&) = default; }; template @@ -125,18 +133,16 @@ class BasicNodeFactoryMock } }; -template +template class BasicViewMock { public: - struct edge_type - { - using vertex_type = sg::node::vertex_t; - - vertex_type vertex; + using edge_type = GenericBasicEdge; - friend bool operator ==(const edge_type&, const edge_type&) = default; - }; + MAKE_CONST_MOCK1(edges, std::vector(const GenericBasicNode&)); - MAKE_CONST_MOCK1(edges, std::vector(const Node&)); + std::vector edges(const sg::concepts::node auto& node) + { + return edges(GenericBasicNode{.vertex = sg::node::vertex(node)}); + } }; diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 419edad7a..e75e90393 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -218,7 +218,6 @@ TEMPLATE_TEST_CASE_SIG( (false, free_fun_insert), (false, member_fun_next), (false, free_fun_next), - (false, QueueMock>), (true, QueueMock), (true, std::stack) ) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index c073d6d47..f9c391803 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -20,24 +20,12 @@ // ReSharper disable CppTypeAliasNeverUsed // ReSharper disable CppClangTidyClangDiagnosticUnneededMemberFunction -namespace -{ - struct VertexMemberNode - { - using vertex_type = int; - - int vertex{}; - - friend bool operator==(const VertexMemberNode&, const VertexMemberNode&) = default; - }; -} - -using DefaultNode = VertexMemberNode; +using DefaultNode = GenericBasicNode; using DefaultQueue = QueueMock; using DefaultState = sg::detail::BasicState; using DefaultTracker = TrackerMock>; -using DefaultGraph = BasicViewMock; -using DefaultNodeFactory = BasicNodeFactoryMock; +using DefaultView = BasicViewMock>; +using DefaultNodeFactory = BasicNodeFactoryMock; using DefaultDriver = sg::detail::BasicTraverseDriver< DefaultNode, DefaultState, @@ -139,6 +127,7 @@ TEST_CASE("BasicTraverseDriver can be constructed with an origin.", "[graph][gra TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt.", "[graph][graph::traverse][graph::detail]") { + using trompeloeil::_; using namespace trompeloeil_ext; using namespace catch_ext; using namespace Catch::Matchers; @@ -168,8 +157,8 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." }; }(); - using VertexInfo = DefaultGraph::edge_type; - DefaultGraph graphMock{}; + using VertexInfo = DefaultView::edge_type; + DefaultView view{}; auto& nodeFactoryMock = const_cast(driver.node_factory()); auto& queueMock = const_cast(driver.state().queue()); auto& trackerMock = const_cast(driver.tracker()); @@ -177,7 +166,7 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." SECTION("Next returns a node.") { // vertex 43 will be skipped on purpose - REQUIRE_CALL(graphMock, edges(originNode)) + REQUIRE_CALL(view, edges(originNode)) .RETURN(std::vector{{41}, {43}, {44}}); REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{41})) @@ -200,18 +189,18 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." REQUIRE_CALL(trackerMock, set_visited(41)) .RETURN(true); - REQUIRE(DefaultNode{.vertex = 41} == driver.next(std::as_const(graphMock))); + //REQUIRE(DefaultNode{.vertex = 41} == driver.next(std::as_const(view))); } SECTION("Next returns std::nullopt.") { - REQUIRE_CALL(graphMock, edges(originNode)) - .RETURN(std::vector{}); + REQUIRE_CALL(view, edges(originNode)) + .RETURN(std::vector>{}); REQUIRE_CALL(queueMock, do_insert(matches(RangesEmpty{}))); REQUIRE_CALL(queueMock, empty()) .RETURN(true); - REQUIRE(std::nullopt == driver.next(std::as_const(graphMock))); + REQUIRE(std::nullopt == driver.next(std::as_const(view))); } } diff --git a/tests/graph/View.cpp b/tests/graph/View.cpp index d606e88fe..64f444016 100644 --- a/tests/graph/View.cpp +++ b/tests/graph/View.cpp @@ -59,7 +59,7 @@ TEMPLATE_TEST_CASE_SIG( (false, GenericWeightedView, GenericRankedNode), (true, GenericWeightedView, GenericRankedNode), - (true, BasicView>, GenericBasicNode) + (true, BasicViewMock, GenericBasicNode) ) { STATIC_REQUIRE(expected == sg::concepts::view_for); From 0ff4a6ea74231ad92deb0e4763bc11712dc8685f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 5 Sep 2023 22:47:33 +0200 Subject: [PATCH 092/256] refactor: extract node factory symbols from graph/Node.hpp into graph/NodeFactory.hpp --- include/Simple-Utility/graph/Node.hpp | 15 -------- include/Simple-Utility/graph/NodeFactory.hpp | 28 +++++++++++++++ include/Simple-Utility/graph/Traverse.hpp | 8 +++-- tests/graph/CMakeLists.txt | 1 + tests/graph/Defines.hpp | 38 +++++++++++++------- tests/graph/Node.cpp | 37 +++---------------- tests/graph/NodeFactory.cpp | 27 ++++++++++++++ tests/graph/Traverse.cpp | 2 +- 8 files changed, 92 insertions(+), 64 deletions(-) create mode 100644 include/Simple-Utility/graph/NodeFactory.hpp create mode 100644 tests/graph/NodeFactory.cpp diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index f77b99e43..477d38dc9 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -132,19 +132,4 @@ namespace sl::graph::node using rank_t = typename traits::rank_type; } - }; - - template - concept node_factory_for = sl::concepts::unqualified - && node - && std::destructible - && requires(T& factory, const Node& node) - { - { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; - /* Well, the info param is quite tricky here. We could simply expect it to be default constructible and {} it, - * but this will fail for templated types. So, just insert the node type itself, as this should provide - * as many info as we need.. */ - { factory.make_successor_node(node, node) } -> std::convertible_to; - }; - #endif diff --git a/include/Simple-Utility/graph/NodeFactory.hpp b/include/Simple-Utility/graph/NodeFactory.hpp new file mode 100644 index 000000000..4b8fa1ca0 --- /dev/null +++ b/include/Simple-Utility/graph/NodeFactory.hpp @@ -0,0 +1,28 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_NODE_FACTORY_HPP +#define SIMPLE_UTILITY_GRAPH_NODE_FACTORY_HPP + +#pragma once + +#include "Simple-Utility/graph/Edge.hpp" +#include "Simple-Utility/graph/Node.hpp" + +namespace sl::graph::concepts +{ + template + concept node_factory_for = sl::concepts::unqualified + && node + && edge_for + && std::destructible + && requires(T& factory, const Node& node, const Edge& edge) + { + { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; + { factory.make_successor_node(node, edge) } -> std::convertible_to; + }; +} + +#endif diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index bcb4af827..875728995 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -13,6 +13,7 @@ #include "Simple-Utility/functional/Tuple.hpp" #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Node.hpp" +#include "Simple-Utility/graph/NodeFactory.hpp" #include "Simple-Utility/graph/Queue.hpp" #include "Simple-Utility/graph/Tracker.hpp" #include "Simple-Utility/graph/View.hpp" @@ -126,7 +127,7 @@ namespace sl::graph::detail concepts::node Node, state_for StateStrategy, concepts::tracker_for> TrackingStrategy, - concepts::node_factory_for NodeFactoryStrategy> + class NodeFactoryStrategy> class BasicTraverseDriver { public: @@ -158,9 +159,10 @@ namespace sl::graph::detail assert(result && "Tracker returned false (already visited) for the origin node."); } - template Graph> // let the concept here, because otherwise it results in an ICE on msvc v142 + template View> // let the concept here, because otherwise it results in an ICE on msvc v142 + requires concepts::node_factory_for> [[nodiscard]] - constexpr std::optional next(const Graph& graph) + constexpr std::optional next(const View& graph) { std::optional result = m_State.next( filter_transform(graph.edges(m_Current), m_Tracker, m_NodeFactory, m_Current)); diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 18f2714f3..98e5f2ff0 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources( "Common.cpp" "Edge.cpp" "Node.cpp" + "NodeFactory.cpp" "Queue.cpp" "Tracker.cpp" "Traverse.cpp" diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 7624f2b24..f56737030 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -107,9 +107,27 @@ class TrackerMock MAKE_MOCK1(set_visited, bool(const Vertex&)); }; -template - requires sg::concepts::edge_for -class BasicNodeFactoryMock +template +class GenericBasicNodeFactoryMock +{ +public: + inline static constexpr bool trompeloeil_movable_mock = true; + + using node_type = Node; + using vertex_type = sg::node::vertex_t; + + MAKE_MOCK1(make_init_node, Node(const vertex_type&)); + MAKE_MOCK2(make_successor_node, node_type(const node_type&, const GenericBasicEdge&)); + + template Edge> + node_type make_successor_node(const node_type& current, const Edge& edge) + { + return make_successor_node(current, GenericBasicEdge{.vertex = sg::node::vertex(edge)}); + } +}; + +template +class GenericRankedNodeFactoryMock { public: inline static constexpr bool trompeloeil_movable_mock = true; @@ -118,18 +136,12 @@ class BasicNodeFactoryMock using vertex_type = sg::node::vertex_t; MAKE_MOCK1(make_init_node, Node(const vertex_type&)); - MAKE_MOCK2(make_successor_node, node_type(const node_type&, const VertexInfo&)); + MAKE_MOCK2(make_successor_node, node_type(const node_type&, const GenericBasicEdge&)); - template - requires sg::concepts::edge_for - node_type make_successor_node(const node_type& current, const Info& info) + template Edge> + node_type make_successor_node(const node_type& current, const Edge& edge) { - VertexInfo convertedInfo{.vertex = sg::node::vertex(info)}; - if constexpr (sg::concepts::ranked_node) - { - convertedInfo.rank = sg::node::rank(info); - } - return make_successor_node(current, convertedInfo); + return make_successor_node(current, GenericBasicEdge{.vertex = sg::node::vertex(edge)}); } }; diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index cbf3c35f0..585f5f2bf 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -59,21 +59,6 @@ namespace rank_type rank; }; - struct minimal_node_factory - { - using node_type = minimal_node; - using vertex_type = sg::node::vertex_t; - - static node_type make_init_node(const vertex_type& v) - { - return {.vertex = v}; - } - - static node_type make_successor_node([[maybe_unused]] const node_type& predecessor, const node_type& v) - { - return {.vertex = sg::node::vertex(v)}; - } - }; struct node_with_custom_trait { int vertex; @@ -138,7 +123,8 @@ TEMPLATE_TEST_CASE_SIG( ((bool expected, class T), expected, T), (true, minimal_node), (true, ranked_node), - (true, BasicTestNode) + (true, GenericBasicNode), + (true, GenericRankedNode) ) { STATIC_REQUIRE(expected == sg::concepts::node); @@ -152,7 +138,9 @@ TEMPLATE_TEST_CASE_SIG( (false, member_fun_rank), (false, free_fun_rank), (false, minimal_node), - (true, ranked_node) + (true, ranked_node), + (false, GenericBasicNode), + (true, GenericRankedNode) ) { STATIC_REQUIRE(expected == sg::concepts::ranked_node); @@ -197,19 +185,4 @@ TEST_CASE( STATIC_REQUIRE(std::same_as>); } -TEMPLATE_TEST_CASE_SIG( - "concepts::node_factory_for determines, whether the given type satisfies the requirements of a node_factory for the specified node type.", - "[graph][graph::concepts]", - ((bool expected, class Factory, class Node), expected, Factory, Node), - (false, minimal_node_factory, BasicTestNode), - (true, minimal_node_factory, minimal_node), - (true, BasicNodeFactoryMock, minimal_node), - (true, sg::NodeFactory>, sg::BasicNode), - (false, sg::NodeFactory>, sg::BasicNode), - (false, sg::NodeFactory>, sg::RankedNode), - (true, sg::NodeFactory>, sg::RankedNode) -) -{ - STATIC_REQUIRE(expected == sg::concepts::node_factory_for); -} diff --git a/tests/graph/NodeFactory.cpp b/tests/graph/NodeFactory.cpp new file mode 100644 index 000000000..2d423574c --- /dev/null +++ b/tests/graph/NodeFactory.cpp @@ -0,0 +1,27 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/NodeFactory.hpp" + +#include + +#include "Defines.hpp" + +TEMPLATE_TEST_CASE_SIG( + "concepts::node_factory_for determines, whether the given type satisfies the requirements of a node_factory for the specified node type.", + "[graph][graph::concepts]", + ((bool expected, class Factory, class Node, class Edge), expected, Factory, Node, Edge), + (false, GenericBasicNodeFactoryMock>, GenericBasicNode, GenericBasicEdge), + (true, GenericBasicNodeFactoryMock>, GenericBasicNode, GenericBasicEdge), + + (false, GenericRankedNodeFactoryMock>, GenericRankedNode, GenericBasicEdge), + (true, GenericRankedNodeFactoryMock>, GenericRankedNode, GenericWeightedEdge) +) +{ + STATIC_CHECK(sg::concepts::node); + STATIC_CHECK(sg::concepts::edge); + + STATIC_REQUIRE(expected == sg::concepts::node_factory_for); +} diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index f9c391803..062f76772 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -25,7 +25,7 @@ using DefaultQueue = QueueMock; using DefaultState = sg::detail::BasicState; using DefaultTracker = TrackerMock>; using DefaultView = BasicViewMock>; -using DefaultNodeFactory = BasicNodeFactoryMock; +using DefaultNodeFactory = GenericBasicNodeFactoryMock; using DefaultDriver = sg::detail::BasicTraverseDriver< DefaultNode, DefaultState, From a2cbab089a6199b55698b1adaccfd6dcf30110dd Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 00:13:57 +0200 Subject: [PATCH 093/256] fix: add missing utility include --- include/Simple-Utility/graph/Queue.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 66a023f0a..8c7b41b5e 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -13,6 +13,7 @@ #include "Simple-Utility/graph/Node.hpp" #include +#include namespace sl::graph::customize { From 7f1e16904f16fa42f65c996aebe25de4a2503084 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 00:14:17 +0200 Subject: [PATCH 094/256] fix: correct node factory mocks --- tests/graph/Defines.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index f56737030..f0f5cab3c 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -119,10 +119,10 @@ class GenericBasicNodeFactoryMock MAKE_MOCK1(make_init_node, Node(const vertex_type&)); MAKE_MOCK2(make_successor_node, node_type(const node_type&, const GenericBasicEdge&)); - template Edge> + template Edge> node_type make_successor_node(const node_type& current, const Edge& edge) { - return make_successor_node(current, GenericBasicEdge{.vertex = sg::node::vertex(edge)}); + return make_successor_node(current, GenericBasicEdge{.vertex = sg::edge::vertex(edge)}); } }; @@ -138,10 +138,10 @@ class GenericRankedNodeFactoryMock MAKE_MOCK1(make_init_node, Node(const vertex_type&)); MAKE_MOCK2(make_successor_node, node_type(const node_type&, const GenericBasicEdge&)); - template Edge> + template Edge> node_type make_successor_node(const node_type& current, const Edge& edge) { - return make_successor_node(current, GenericBasicEdge{.vertex = sg::node::vertex(edge)}); + return make_successor_node(current, GenericBasicEdge{.vertex = sg::edge::vertex(edge)}); } }; From 7c1a0a0f7984a21073c9864cddc52af3b7e39991 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 01:04:26 +0200 Subject: [PATCH 095/256] feat: concepts::formattable --- .../concepts/stl_extensions.hpp | 27 ++++++++++++ tests/concepts/stl_extensions.cpp | 44 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/include/Simple-Utility/concepts/stl_extensions.hpp b/include/Simple-Utility/concepts/stl_extensions.hpp index 6c0e3ec11..a4507c3c2 100644 --- a/include/Simple-Utility/concepts/stl_extensions.hpp +++ b/include/Simple-Utility/concepts/stl_extensions.hpp @@ -10,6 +10,7 @@ #include #include +#include // ReSharper disable CppClangTidyClangDiagnosticDocumentation // ReSharper disable CppIdenticalOperandsInBinaryExpression @@ -275,6 +276,32 @@ namespace sl::concepts { t2 <=> t1 } noexcept -> detail::compares_as; }; + /** + * \brief Determines, whether a complete specialization of ``std::formatter`` for the given (possibly cv-ref qualified) type exists. + * \tparam T Type to check. + * \tparam Char Used character type. + * \details This is an adapted implementation of the ``std::formattable`` concept, which is added c++23. + * \note This implementation takes a simple but reasonable shortcut in assuming, that ```Char`` is either ``char`` or ``wchar_t``, + * which must not necessarily true. + * \see Adapted from here: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2286r8.html#concept-formattable + * \see https://en.cppreference.com/w/cpp/utility/format/formattable + */ + template + concept formattable = + std::semiregular, Char>> + && requires( + std::formatter, Char> formatter, + T t, + std::conditional_t, std::format_context, std::wformat_context> formatContext, + std::basic_format_parse_context parseContext + ) + { + { formatter.parse(parseContext) } -> std::same_as::iterator>; + { + std::as_const(formatter).format(t, formatContext) + } -> std::same_as::iterator>; + }; + /** * \} */ diff --git a/tests/concepts/stl_extensions.cpp b/tests/concepts/stl_extensions.cpp index ff193cd89..315f60d5b 100644 --- a/tests/concepts/stl_extensions.cpp +++ b/tests/concepts/stl_extensions.cpp @@ -8,6 +8,7 @@ #include "../helper.hpp" #include +#include #include "Simple-Utility/concepts/stl_extensions.hpp" @@ -357,3 +358,46 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == nothrow_weakly_three_way_comparable_with); } + +namespace +{ + struct Formattable + { + int value{}; + }; + + struct NonFormattable + { + int value{}; + }; +} + +template +struct std::formatter + : public std::formatter +{ + template + auto format(Formattable t, FormatContext& fc) const + { + return std::formatter::format(t.value, fc); + } +}; + +TEMPLATE_TEST_CASE_SIG( + "formattable checks, whether a complete specialization of std::formatter for the given type T exists.", + "[concepts][stl_ext]", + ((bool expected, class T, class Char), expected, T, Char), + (false, NonFormattable, char), + (false, NonFormattable, wchar_t), + (true, Formattable, char), + (true, Formattable, wchar_t), + (true, int, char), + (true, int, wchar_t), + (true, std::string, char), + (false, std::string, wchar_t), + (false, std::wstring, char), + (true, std::wstring, wchar_t) +) +{ + STATIC_REQUIRE(expected == formattable); +} From 299885fe0d53aebcb9900638dfdaef657d0930a6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 01:25:33 +0200 Subject: [PATCH 096/256] fix: conditionally enable concepts::formattable --- include/Simple-Utility/Config.hpp | 5 +++++ .../concepts/stl_extensions.hpp | 21 ++++++++++++++++++- tests/concepts/stl_extensions.cpp | 6 ++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/Config.hpp b/include/Simple-Utility/Config.hpp index 4828bd82b..91c952de8 100644 --- a/include/Simple-Utility/Config.hpp +++ b/include/Simple-Utility/Config.hpp @@ -30,4 +30,9 @@ "." SL_UTILITY_XSTR(SL_UTILITY_VERSION_MINOR) \ "." SL_UTILITY_XSTR(SL_UTILITY_VERSION_PATCH) + +#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L + #define SL_UTLITY_HAS_STD_FORMAT +#endif + #endif diff --git a/include/Simple-Utility/concepts/stl_extensions.hpp b/include/Simple-Utility/concepts/stl_extensions.hpp index a4507c3c2..43a7a32a3 100644 --- a/include/Simple-Utility/concepts/stl_extensions.hpp +++ b/include/Simple-Utility/concepts/stl_extensions.hpp @@ -8,9 +8,10 @@ #pragma once +#include "Simple-Utility/Config.hpp" + #include #include -#include // ReSharper disable CppClangTidyClangDiagnosticDocumentation // ReSharper disable CppIdenticalOperandsInBinaryExpression @@ -276,6 +277,22 @@ namespace sl::concepts { t2 <=> t1 } noexcept -> detail::compares_as; }; + /** + * \} + */ +} + +#ifdef SL_UTLITY_HAS_STD_FORMAT + +#include + +namespace sl::concepts +{ + /** + * \addtogroup GROUP_STL_EXTENSION_CONCEPTS + * \{ + */ + /** * \brief Determines, whether a complete specialization of ``std::formatter`` for the given (possibly cv-ref qualified) type exists. * \tparam T Type to check. @@ -308,3 +325,5 @@ namespace sl::concepts } #endif + +#endif diff --git a/tests/concepts/stl_extensions.cpp b/tests/concepts/stl_extensions.cpp index 315f60d5b..100e91006 100644 --- a/tests/concepts/stl_extensions.cpp +++ b/tests/concepts/stl_extensions.cpp @@ -359,6 +359,8 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == nothrow_weakly_three_way_comparable_with); } +#ifdef SL_UTLITY_HAS_STD_FORMAT + namespace { struct Formattable @@ -390,6 +392,8 @@ TEMPLATE_TEST_CASE_SIG( (false, NonFormattable, char), (false, NonFormattable, wchar_t), (true, Formattable, char), + (true, const Formattable, char), + (true, const Formattable&, char), (true, Formattable, wchar_t), (true, int, char), (true, int, wchar_t), @@ -401,3 +405,5 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == formattable); } + +#endif From 8a80eb21623e123649310b566700e22a26080430 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 01:29:46 +0200 Subject: [PATCH 097/256] feat: catch2 std::format support --- .../Simple-Utility/test_util/Catch2Ext.hpp | 20 +++++++++++++ tests/test_util/Catch2Ext.cpp | 30 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/include/Simple-Utility/test_util/Catch2Ext.hpp b/include/Simple-Utility/test_util/Catch2Ext.hpp index 369fdfa92..50bc1f914 100644 --- a/include/Simple-Utility/test_util/Catch2Ext.hpp +++ b/include/Simple-Utility/test_util/Catch2Ext.hpp @@ -8,6 +8,9 @@ #pragma once +#include "Simple-Utility/Config.hpp" +#include "Simple-Utility/concepts/stl_extensions.hpp" + #include #include @@ -30,4 +33,21 @@ namespace catch_ext }; } +#ifdef SL_UTLITY_HAS_STD_FORMAT + +#include + +template + requires sl::concepts::formattable + && (!Catch::Detail::IsStreamInsertable::value) +struct Catch::StringMaker +{ + static std::string convert(const T& value) + { + return std::format("{}", value); + } +}; + +#endif + #endif diff --git a/tests/test_util/Catch2Ext.cpp b/tests/test_util/Catch2Ext.cpp index 5bf946de8..4905f169a 100644 --- a/tests/test_util/Catch2Ext.cpp +++ b/tests/test_util/Catch2Ext.cpp @@ -18,3 +18,33 @@ TEST_CASE("catch_ext::RangesEmpty::describe prints a description.", "[test_util] { REQUIRE_THAT(catch_ext::RangesEmpty{}.describe(), !catch_ext::RangesEmpty{}); } + +#ifdef SL_UTLITY_HAS_STD_FORMAT + +namespace +{ + struct TestType + { + int value{}; + }; +} + +template +struct std::formatter + : public std::formatter +{ + template + auto format(TestType t, FormatContext& fc) const + { + return std::format_to(fc.out(), "TestType: {}", t.value); + } +}; + +TEST_CASE("Catch::StringMaker is extended by std::format compatible types.", "[test_util][test_util::catch2]") +{ + STATIC_CHECK(sl::concepts::formattable); + + REQUIRE("TestType: 42" == Catch::StringMaker{}.convert(TestType{42})); +} + +#endif From 92d678d77dbf3bae185a4843e0e9ac21f33f42b6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 01:32:42 +0200 Subject: [PATCH 098/256] fix: add missing includes --- include/Simple-Utility/graph/View.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp index 28bc54ad2..2179e8185 100644 --- a/include/Simple-Utility/graph/View.hpp +++ b/include/Simple-Utility/graph/View.hpp @@ -8,9 +8,13 @@ #pragma once +#include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Edge.hpp" #include "Simple-Utility/graph/Node.hpp" +#include +#include + namespace sl::graph::view { template From 3e6cb32191472dd27568edc81a664962fd30d380 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 02:08:54 +0200 Subject: [PATCH 099/256] fix: SL_UTILITY_HAS_STD_FORMAT macro for msvc --- include/Simple-Utility/Config.hpp | 4 ++-- include/Simple-Utility/concepts/stl_extensions.hpp | 2 +- include/Simple-Utility/test_util/Catch2Ext.hpp | 2 +- tests/concepts/stl_extensions.cpp | 2 +- tests/test_util/Catch2Ext.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/Simple-Utility/Config.hpp b/include/Simple-Utility/Config.hpp index 91c952de8..0cd521887 100644 --- a/include/Simple-Utility/Config.hpp +++ b/include/Simple-Utility/Config.hpp @@ -31,8 +31,8 @@ "." SL_UTILITY_XSTR(SL_UTILITY_VERSION_PATCH) -#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L - #define SL_UTLITY_HAS_STD_FORMAT +#if defined(_MSC_VER) || (__cpp_lib_format >= 201907L) + #define SL_UTILITY_HAS_STD_FORMAT #endif #endif diff --git a/include/Simple-Utility/concepts/stl_extensions.hpp b/include/Simple-Utility/concepts/stl_extensions.hpp index 43a7a32a3..7cf0d0f10 100644 --- a/include/Simple-Utility/concepts/stl_extensions.hpp +++ b/include/Simple-Utility/concepts/stl_extensions.hpp @@ -282,7 +282,7 @@ namespace sl::concepts */ } -#ifdef SL_UTLITY_HAS_STD_FORMAT +#ifdef SL_UTILITY_HAS_STD_FORMAT #include diff --git a/include/Simple-Utility/test_util/Catch2Ext.hpp b/include/Simple-Utility/test_util/Catch2Ext.hpp index 50bc1f914..1de791aa4 100644 --- a/include/Simple-Utility/test_util/Catch2Ext.hpp +++ b/include/Simple-Utility/test_util/Catch2Ext.hpp @@ -33,7 +33,7 @@ namespace catch_ext }; } -#ifdef SL_UTLITY_HAS_STD_FORMAT +#ifdef SL_UTILITY_HAS_STD_FORMAT #include diff --git a/tests/concepts/stl_extensions.cpp b/tests/concepts/stl_extensions.cpp index 100e91006..ec23750a5 100644 --- a/tests/concepts/stl_extensions.cpp +++ b/tests/concepts/stl_extensions.cpp @@ -359,7 +359,7 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == nothrow_weakly_three_way_comparable_with); } -#ifdef SL_UTLITY_HAS_STD_FORMAT +#ifdef SL_UTILITY_HAS_STD_FORMAT namespace { diff --git a/tests/test_util/Catch2Ext.cpp b/tests/test_util/Catch2Ext.cpp index 4905f169a..31575152c 100644 --- a/tests/test_util/Catch2Ext.cpp +++ b/tests/test_util/Catch2Ext.cpp @@ -19,7 +19,7 @@ TEST_CASE("catch_ext::RangesEmpty::describe prints a description.", "[test_util] REQUIRE_THAT(catch_ext::RangesEmpty{}.describe(), !catch_ext::RangesEmpty{}); } -#ifdef SL_UTLITY_HAS_STD_FORMAT +#ifdef SL_UTILITY_HAS_STD_FORMAT namespace { From 001cfc953aa545bea4601d28251ba39d184cbaf4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 02:14:03 +0200 Subject: [PATCH 100/256] fix: BasicTraverseDriver::next test case --- tests/graph/Traverse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 062f76772..4e7180042 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -189,7 +189,7 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." REQUIRE_CALL(trackerMock, set_visited(41)) .RETURN(true); - //REQUIRE(DefaultNode{.vertex = 41} == driver.next(std::as_const(view))); + REQUIRE(DefaultNode{.vertex = 41} == driver.next(std::as_const(view))); } SECTION("Next returns std::nullopt.") From bcd96ac3db1e604196b54cd045f1019a56169e93 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 02:14:24 +0200 Subject: [PATCH 101/256] feat: add first draft of dfs namespace --- .../Simple-Utility/graph/DepthFirstSearch.hpp | 96 +++++++++++++++++++ tests/graph/CMakeLists.txt | 1 + tests/graph/DepthFirstSearch.cpp | 67 +++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 include/Simple-Utility/graph/DepthFirstSearch.hpp create mode 100644 tests/graph/DepthFirstSearch.cpp diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp new file mode 100644 index 000000000..62ea14a56 --- /dev/null +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -0,0 +1,96 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include "Simple-Utility/graph/Traverse.hpp" +#include "Simple-Utility/graph/mixins/queue/std_stack.hpp" +#include "Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp" + +#include + +namespace sl::graph::dfs +{ + template + struct Node + { + using vertex_type = Vertex; + vertex_type vertex{}; + }; + + template + struct ExtendedNode + { + using vertex_type = Vertex; + vertex_type vertex{}; + int depth{}; + std::optional predecessor{}; + }; + + template + struct NodeFactory; + + template + struct NodeFactory> + { + using node_type = Node; + using vertex_type = node::vertex_t; + + static constexpr node_type make_init_node(vertex_type origin) + { + return node_type{.vertex = std::move(origin)}; + } + + template Edge> + [[nodiscard]] + static constexpr node_type make_successor_node([[maybe_unused]] const node_type& current, const Edge& edge) + { + return node_type{.vertex = edge::vertex(edge)}; + } + }; + + template + struct NodeFactory> + { + using node_type = ExtendedNode; + using vertex_type = node::vertex_t; + + static constexpr node_type make_init_node(vertex_type origin) + { + return node_type{.vertex = std::move(origin), .depth = 0, .predecessor = std::nullopt}; + } + + template Edge> + [[nodiscard]] + static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) + { + return node_type{.vertex = edge::vertex(edge), .depth = current.depth + 1, .predecessor = current.vertex}; + } + }; + + template + struct Edge + { + using vertex_type = Vertex; + vertex_type vertex{}; + }; + + template < + class View, + concepts::node Node = Node>>, + concepts::node_factory_for> NodeFactory = NodeFactory, + concepts::tracker_for> Tracker = std::unordered_map, bool>> + requires concepts::view_for + using BasicTraverser = Traverser< + View, + detail::BasicTraverseDriver< + Node, + detail::BasicState>, + Tracker, + NodeFactory>>; + + template + using ExtendedTraverser = BasicTraverser>>>; +} diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 98e5f2ff0..4711bac14 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -2,6 +2,7 @@ target_sources( Simple-Utility-Tests PRIVATE "Common.cpp" + "DepthFirstSearch.cpp" "Edge.cpp" "Node.cpp" "NodeFactory.cpp" diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp new file mode 100644 index 000000000..3af663f47 --- /dev/null +++ b/tests/graph/DepthFirstSearch.cpp @@ -0,0 +1,67 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/DepthFirstSearch.hpp" + +#include +#include +#include + +#include "Defines.hpp" + +#include + +namespace +{ + struct View + { + inline static const std::unordered_map> graph{ + {1, {2, 3}}, + {2, {6}}, + {3, {5, 6}}, + {5, {5}}, + {6, {2}}, + + // begin isolated sub-graph + {4, {7}}, + {7, {4, 7, 9}}, + {8, {7, 9}}, + {9, {4}} + }; + + using edge_type = sg::dfs::Edge; + + static std::vector edges(const sg::dfs::Node current) + { + const auto& vertices = graph.at(current.vertex); + std::vector infos{}; + infos.reserve(std::ranges::size(vertices)); + std::ranges::transform( + vertices, + std::back_inserter(infos), + [](const int v) { return edge_type{.vertex = v}; }); + return infos; + } + } inline constexpr graph{}; +} + +TEST_CASE("dfs::BasicTraverser visits all reachable vertices.", "[graph][graph::dfs]") +{ + const auto& [expected, origin] = GENERATE( + (table, int>)({ + {{/*3,*/ 6, 2, 5}, 3}, + {{/*6,*/ 2}, 6}, + {{/*1,*/ 3, 6, 2, 5}, 1}, + {{/*8,*/ 9, 4, 7}, 8}, + })); + + sg::dfs::BasicTraverser traverser{graph, origin}; + STATIC_CHECK(std::ranges::input_range); + + std::vector visitedVertices{}; + std::ranges::transform(traverser, std::back_inserter(visitedVertices), &sg::dfs::Node::vertex); + + REQUIRE_THAT(visitedVertices, Catch::Matchers::RangeEquals(expected)); +} From 840883bf05bb0e9388a1233d2a90e4317fbf231f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 02:26:56 +0200 Subject: [PATCH 102/256] feat: add formattable specialization for trompeloeil::printer --- .../test_util/TrompeloeilExt.hpp | 17 ++++++++++ tests/test_util/TrompeloeilExt.cpp | 33 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/include/Simple-Utility/test_util/TrompeloeilExt.hpp b/include/Simple-Utility/test_util/TrompeloeilExt.hpp index 7f3413564..8c3280ce1 100644 --- a/include/Simple-Utility/test_util/TrompeloeilExt.hpp +++ b/include/Simple-Utility/test_util/TrompeloeilExt.hpp @@ -8,6 +8,7 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective #include #include @@ -17,6 +18,9 @@ #include #include +#include "Simple-Utility/Config.hpp" +#include "Simple-Utility/concepts/stl_extensions.hpp" + namespace trompeloeil_ext { template @@ -49,4 +53,17 @@ namespace trompeloeil_ext }; } +#ifdef SL_UTILITY_HAS_STD_FORMAT + +template T> +struct trompeloeil::printer +{ + static void print(std::ostream& os, const T& obj) + { + std::format_to(std::ostream_iterator{os}, "{}", obj); + } +}; + +#endif + #endif diff --git a/tests/test_util/TrompeloeilExt.cpp b/tests/test_util/TrompeloeilExt.cpp index cb1652f25..a3b4d92f1 100644 --- a/tests/test_util/TrompeloeilExt.cpp +++ b/tests/test_util/TrompeloeilExt.cpp @@ -29,3 +29,36 @@ TEST_CASE("trompeloeil_ext::matches can be used to print something to an ostream REQUIRE(!std::ranges::empty(ss.str())); } + +#ifdef SL_UTILITY_HAS_STD_FORMAT + +namespace +{ + struct TestType + { + int value{}; + }; +} + +template +struct std::formatter + : public std::formatter +{ + template + auto format(TestType t, FormatContext& fc) const + { + return std::format_to(fc.out(), "TestType: {}", t.value); + } +}; + +TEST_CASE("trompeloeil::printer is extended by std::format compatible types.", "[test_util][test_util::trompeloeil]") +{ + STATIC_CHECK(sl::concepts::formattable); + + std::ostringstream os{}; + trompeloeil::print(os, TestType{42}); + + REQUIRE("TestType: 42" == os.str()); +} + +#endif From 56adfba905979acb89383b83a385ca12a924b364 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 03:18:24 +0200 Subject: [PATCH 103/256] fix: please msvc v142 --- include/Simple-Utility/graph/Edge.hpp | 8 ++++++-- include/Simple-Utility/graph/Node.hpp | 8 ++++++-- include/Simple-Utility/graph/View.hpp | 10 +++++++--- tests/graph/Defines.hpp | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index 47ae12e4d..482a80c15 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -109,7 +109,9 @@ namespace sl::graph::concepts && vertex::vertex_type> && requires(const T& edge) { - { edge::vertex(edge) } -> std::convertible_to::vertex_type>; + // fixes compile error on msvc v142 + // ReSharper disable once CppRedundantTemplateKeyword + { edge::vertex(edge) } -> std::convertible_to::vertex_type>; }; template @@ -117,7 +119,9 @@ namespace sl::graph::concepts && weight::weight_type> && requires(const T& edge) { - { edge::weight(edge) } -> std::convertible_to::weight_type>; + // fixes compile error on msvc v142 + // ReSharper disable once CppRedundantTemplateKeyword + { edge::weight(edge) } -> std::convertible_to::weight_type>; }; } diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 477d38dc9..3cec7e6d4 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -111,7 +111,9 @@ namespace sl::graph::concepts && vertex::vertex_type> && requires(const T& node) { - { node::vertex(node) } -> std::convertible_to::vertex_type>; + // fixes compile error on msvc v142 + // ReSharper disable once CppRedundantTemplateKeyword + { node::vertex(node) } -> std::convertible_to::vertex_type>; }; template @@ -119,7 +121,9 @@ namespace sl::graph::concepts && rank::rank_type> && requires(const T& node) { - { node::rank(node) } -> std::convertible_to::rank_type>; + // fixes compile error on msvc v142 + // ReSharper disable once CppRedundantTemplateKeyword + { node::rank(node) } -> std::convertible_to::rank_type>; }; } diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp index 2179e8185..e5ce698d4 100644 --- a/include/Simple-Utility/graph/View.hpp +++ b/include/Simple-Utility/graph/View.hpp @@ -28,12 +28,16 @@ namespace sl::graph::concepts && sl::concepts::unqualified && requires(const T& view, const Node& node) { - typename view::traits::edge_type; - requires edge_for::edge_type, Node>; + // 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 std::convertible_to< std::ranges::range_value_t, - typename view::traits::edge_type>; + // ReSharper disable once CppRedundantTemplateKeyword + typename view::template traits::edge_type>; }; } diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index f0f5cab3c..3bf250789 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -153,7 +153,7 @@ class BasicViewMock MAKE_CONST_MOCK1(edges, std::vector(const GenericBasicNode&)); - std::vector edges(const sg::concepts::node auto& node) + std::vector edges(const sg::concepts::node auto& node) const { return edges(GenericBasicNode{.vertex = sg::node::vertex(node)}); } From f2bafebde32318129e4c8d0f2e056811a9e000fd Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 03:25:01 +0200 Subject: [PATCH 104/256] feat: node formatting support --- include/Simple-Utility/graph/Node.hpp | 45 +++++++++++++++++++++++++++ tests/graph/Defines.hpp | 2 ++ tests/graph/Node.cpp | 16 ++++++++++ 3 files changed, 63 insertions(+) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 3cec7e6d4..9ceeb3fca 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -8,6 +8,7 @@ #pragma once +#include "Simple-Utility/Config.hpp" #include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" @@ -136,4 +137,48 @@ namespace sl::graph::node using rank_t = typename traits::rank_type; } +#ifdef SL_UTILITY_HAS_STD_FORMAT + +#include + +template + requires sl::concepts::formattable, Char> +struct std::formatter // NOLINT(cert-dcl58-cpp) +{ + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } + + template + auto format(const Node& node, FormatContext& fc) const + { + return std::format_to(fc.out(), "{}vertex: {}{}", "{", sl::graph::node::vertex(node), "}"); + } +}; + +template + requires sl::concepts::formattable, Char> +struct std::formatter // NOLINT(cert-dcl58-cpp) +{ + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } + + template + auto format(const Node& node, FormatContext& fc) const + { + return std::format_to( + fc.out(), + "{}vertex: {}, rank: {}{}", + "{", + sl::graph::node::vertex(node), + sl::graph::node::rank(node), + "}"); + } +}; + +#endif + #endif diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 3bf250789..baf4985eb 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -13,6 +13,8 @@ #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Tracker.hpp" #include "Simple-Utility/graph/View.hpp" +#include "Simple-Utility/test_util/Catch2Ext.hpp" +#include "Simple-Utility/test_util/TrompeloeilExt.hpp" namespace sg = sl::graph; diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 585f5f2bf..34053c37e 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -185,4 +185,20 @@ TEST_CASE( STATIC_REQUIRE(std::same_as>); } +#ifdef SL_UTILITY_HAS_STD_FORMAT +TEST_CASE("node types can be formatted.", "[graph][graph::node]") +{ + using TestType = GenericBasicNode; + + REQUIRE("{vertex: Hello, World!}" == std::format("{}", TestType{.vertex = "Hello, World!"})); +} + +TEST_CASE("ranked_node types can be formatted.", "[graph][graph::node]") +{ + using TestType = GenericRankedNode; + + REQUIRE("{vertex: Hello, World!, rank: 42}" == std::format("{}", TestType{.vertex = "Hello, World!", .rank = 42})); +} + +#endif From 54d7dcface81e9a18108f25309ff61728fef1c1b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 03:26:20 +0200 Subject: [PATCH 105/256] test: tweak dfs test case --- .../Simple-Utility/graph/DepthFirstSearch.hpp | 33 +------------------ tests/graph/DepthFirstSearch.cpp | 16 ++++----- 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index 62ea14a56..f46bf98d1 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -9,8 +9,6 @@ #include "Simple-Utility/graph/mixins/queue/std_stack.hpp" #include "Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp" -#include - namespace sl::graph::dfs { template @@ -18,15 +16,8 @@ namespace sl::graph::dfs { using vertex_type = Vertex; vertex_type vertex{}; - }; - template - struct ExtendedNode - { - using vertex_type = Vertex; - vertex_type vertex{}; - int depth{}; - std::optional predecessor{}; + friend bool operator==(const Node&, const Node&) = default; }; template @@ -51,25 +42,6 @@ namespace sl::graph::dfs } }; - template - struct NodeFactory> - { - using node_type = ExtendedNode; - using vertex_type = node::vertex_t; - - static constexpr node_type make_init_node(vertex_type origin) - { - return node_type{.vertex = std::move(origin), .depth = 0, .predecessor = std::nullopt}; - } - - template Edge> - [[nodiscard]] - static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) - { - return node_type{.vertex = edge::vertex(edge), .depth = current.depth + 1, .predecessor = current.vertex}; - } - }; - template struct Edge { @@ -90,7 +62,4 @@ namespace sl::graph::dfs detail::BasicState>, Tracker, NodeFactory>>; - - template - using ExtendedTraverser = BasicTraverser>>>; } diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 3af663f47..1a5d13382 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -50,18 +50,18 @@ namespace TEST_CASE("dfs::BasicTraverser visits all reachable vertices.", "[graph][graph::dfs]") { const auto& [expected, origin] = GENERATE( - (table, int>)({ - {{/*3,*/ 6, 2, 5}, 3}, - {{/*6,*/ 2}, 6}, - {{/*1,*/ 3, 6, 2, 5}, 1}, - {{/*8,*/ 9, 4, 7}, 8}, + (table>, int>)({ + {{/*3,*/ {6}, {2}, {5}}, 3}, + {{/*6,*/ {2}}, 6}, + {{/*1,*/ {3}, {6}, {2}, {5}}, 1}, + {{/*8,*/ {9}, {4}, {7}}, 8}, })); sg::dfs::BasicTraverser traverser{graph, origin}; STATIC_CHECK(std::ranges::input_range); - std::vector visitedVertices{}; - std::ranges::transform(traverser, std::back_inserter(visitedVertices), &sg::dfs::Node::vertex); + std::vector> nodes{}; + std::ranges::copy(traverser, std::back_inserter(nodes)); - REQUIRE_THAT(visitedVertices, Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(nodes, Catch::Matchers::RangeEquals(expected)); } From a8e656ff9c782a4e031a6aaa432908c699331798 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 03:26:55 +0200 Subject: [PATCH 106/256] fix: remove unnecessary include --- tests/graph/DepthFirstSearch.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 1a5d13382..e2e80a08a 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -11,8 +11,6 @@ #include "Defines.hpp" -#include - namespace { struct View From b9f793d23ac1ef8c8773aa70615d371784b30964 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 03:30:06 +0200 Subject: [PATCH 107/256] fix: remove unnecessary member --- tests/concepts/stl_extensions.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/concepts/stl_extensions.cpp b/tests/concepts/stl_extensions.cpp index ec23750a5..80f9b8041 100644 --- a/tests/concepts/stl_extensions.cpp +++ b/tests/concepts/stl_extensions.cpp @@ -370,7 +370,6 @@ namespace struct NonFormattable { - int value{}; }; } From 59fe07a61cab0837a7ac64eb83e9c6c53985ba81 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 03:32:19 +0200 Subject: [PATCH 108/256] fix: silent several resharper complaints --- tests/graph/Common.cpp | 8 -------- tests/graph/Edge.cpp | 4 ++-- tests/graph/Node.cpp | 4 ++-- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 77ba72008..81ece0a21 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -78,7 +78,6 @@ namespace struct member_vertex { - // ReSharper disable once CppDeclaratorNeverUsed int vertex; }; @@ -91,7 +90,6 @@ namespace { MAKE_CONST_MOCK0(my_vertex, int()); - // ReSharper disable once CppDeclaratorNeverUsed friend int vertex(const free_fun_vertex& v) { return v.my_vertex(); @@ -198,12 +196,6 @@ namespace { using rank_type = valid_rank; }; - - struct ranked_feature_category_type - { - using vertex_type = valid_vertex; - using rank_type = valid_rank; - }; } template <> struct sg::customize::vertex_fn diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 53399b750..09cd8ace8 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -12,11 +12,12 @@ #include "Defines.hpp" +// ReSharper disable CppDeclaratorNeverUsed + namespace { struct member_weight { - // ReSharper disable once CppDeclaratorNeverUsed int weight; }; @@ -29,7 +30,6 @@ namespace { MAKE_CONST_MOCK0(my_weight, int()); - // ReSharper disable once CppDeclaratorNeverUsed friend int weight(const free_fun_weight& v) { return v.my_weight(); diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 34053c37e..af287b533 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -14,11 +14,12 @@ #include +// ReSharper disable CppDeclaratorNeverUsed + namespace { struct member_rank { - // ReSharper disable once CppDeclaratorNeverUsed int rank; }; @@ -31,7 +32,6 @@ namespace { MAKE_CONST_MOCK0(my_rank, int()); - // ReSharper disable once CppDeclaratorNeverUsed friend int rank(const free_fun_rank& v) { return v.my_rank(); From 47d738f73a1f218a9c7f92829b3fa44a15a6edf4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 03:42:15 +0200 Subject: [PATCH 109/256] fix: check constructed traverse driver --- tests/graph/Traverse.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 4e7180042..0a6bd78f2 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -157,6 +157,8 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." }; }(); + CHECK(driver.current_node() == originNode); + using VertexInfo = DefaultView::edge_type; DefaultView view{}; auto& nodeFactoryMock = const_cast(driver.node_factory()); From 4bc24fd8e1f00e2aeb257d1130e687885e477915 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 03:52:24 +0200 Subject: [PATCH 110/256] fix: try please lcov --- tests/graph/Traverse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 0a6bd78f2..1ca7dbc25 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -150,7 +150,7 @@ TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt." .RETURN(true); return DefaultDriver{ - 42, + originNode.vertex, std::forward_as_tuple(std::move(trackerMock)), std::forward_as_tuple(std::move(nodeFactoryMock)), std::forward_as_tuple(std::move(queue)) From 46f620b3faf4d9e1ec6d2a928463ca8f88cccdc7 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 04:01:20 +0200 Subject: [PATCH 111/256] feat: CommonBasicNode and CommonRankedNode --- include/Simple-Utility/graph/Node.hpp | 27 +++++++++++++++++++++++++++ tests/graph/Node.cpp | 8 ++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 9ceeb3fca..23a8f654a 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -137,6 +137,33 @@ namespace sl::graph::node using rank_t = typename traits::rank_type; } +namespace sl::graph +{ + template + struct CommonBasicNode + { + using vertex_type = Vertex; + + vertex_type vertex; + + [[nodiscard]] + friend bool operator==(const CommonBasicNode&, const CommonBasicNode&) = default; + }; + + template + struct CommonRankedNode + { + using vertex_type = Vertex; + using rank_type = Rank; + + vertex_type vertex; + rank_type rank; + + [[nodiscard]] + friend bool operator==(const CommonRankedNode&, const CommonRankedNode&) = default; + }; +} + #ifdef SL_UTILITY_HAS_STD_FORMAT #include diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index af287b533..d36c413d8 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -124,7 +124,9 @@ TEMPLATE_TEST_CASE_SIG( (true, minimal_node), (true, ranked_node), (true, GenericBasicNode), - (true, GenericRankedNode) + (true, GenericRankedNode), + (true, sg::CommonBasicNode), + (true, sg::CommonRankedNode) ) { STATIC_REQUIRE(expected == sg::concepts::node); @@ -140,7 +142,9 @@ TEMPLATE_TEST_CASE_SIG( (false, minimal_node), (true, ranked_node), (false, GenericBasicNode), - (true, GenericRankedNode) + (true, GenericRankedNode), + (false, sg::CommonBasicNode), + (true, sg::CommonRankedNode) ) { STATIC_REQUIRE(expected == sg::concepts::ranked_node); From cc5f9706a3218d60146200c7d8ff78edaf119343 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 04:15:17 +0200 Subject: [PATCH 112/256] feat: CommonBasicEdge and CommonWeightedEdge --- include/Simple-Utility/graph/Edge.hpp | 27 +++++++++++++++++++++++++++ tests/graph/Edge.cpp | 8 ++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index 482a80c15..ebd46a0f1 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -144,4 +144,31 @@ namespace sl::graph::concepts || weighted_edge && std::convertible_to, node::rank_t>); } +namespace sl::graph +{ + template + struct CommonBasicEdge + { + using vertex_type = Vertex; + + vertex_type vertex; + + [[nodiscard]] + friend bool operator==(const CommonBasicEdge&, const CommonBasicEdge&) = default; + }; + + template + struct CommonWeightedEdge + { + using vertex_type = Vertex; + using weight_type = Weight; + + vertex_type vertex; + weight_type weight; + + [[nodiscard]] + friend bool operator==(const CommonWeightedEdge&, const CommonWeightedEdge&) = default; + }; +} + #endif diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 09cd8ace8..441b560b2 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -120,7 +120,9 @@ TEMPLATE_TEST_CASE_SIG( (false, free_fun_weight), (false, custom_fun_weight), (true, GenericBasicEdge), - (true, GenericWeightedEdge) + (true, GenericWeightedEdge), + (true, sg::CommonBasicEdge), + (true, sg::CommonWeightedEdge) ) { STATIC_REQUIRE(expected == sg::concepts::edge); @@ -135,7 +137,9 @@ TEMPLATE_TEST_CASE_SIG( (false, free_fun_weight), (false, custom_fun_weight), (false, GenericBasicEdge), - (true, GenericWeightedEdge) + (true, GenericWeightedEdge), + (false, sg::CommonBasicEdge), + (true, sg::CommonWeightedEdge) ) { STATIC_REQUIRE(expected == sg::concepts::weighted_edge); From f11a7e629fd4c8ad122bf4b8aa7c69679463c6f8 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 04:45:14 +0200 Subject: [PATCH 113/256] refactor: introduce better distinction between node and edge --- include/Simple-Utility/graph/Common.hpp | 63 ---------- .../Simple-Utility/graph/DepthFirstSearch.hpp | 4 +- include/Simple-Utility/graph/Edge.hpp | 112 +++++++++++++++++- include/Simple-Utility/graph/Node.hpp | 58 ++++++++- include/Simple-Utility/graph/Traverse.hpp | 26 ++-- tests/graph/Common.cpp | 70 ----------- tests/graph/Defines.hpp | 8 +- tests/graph/DepthFirstSearch.cpp | 2 +- tests/graph/Edge.cpp | 87 ++++++++++++++ tests/graph/Node.cpp | 69 +++++++++++ 10 files changed, 339 insertions(+), 160 deletions(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 53089efe4..e37010453 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -8,7 +8,6 @@ #pragma once -#include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/operators.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" @@ -47,66 +46,4 @@ namespace sl::graph::concepts && rank; } -namespace sl::graph::customize -{ - template - struct vertex_fn; -} - -namespace sl::graph::detail -{ - template - requires requires(const Node& node, customize::vertex_fn fn) - { - requires concepts::vertex>; - } - constexpr decltype(auto) vertex(const Node& node, const priority_tag<3>) noexcept(noexcept(customize::vertex_fn{}(node))) - { - return customize::vertex_fn{}(node); - } - - template - requires requires(const Node& node) - { - requires concepts::vertex>; - } - constexpr auto& vertex(const Node& node, const priority_tag<2>) noexcept - { - return node.vertex; - } - - template - requires requires(const Node& node) - { - requires concepts::vertex>; - } - constexpr decltype(auto) vertex(const Node& node, const priority_tag<1>) noexcept(noexcept(node.vertex())) - { - return node.vertex(); - } - - template - requires requires(const Node& node) - { - requires concepts::vertex>; - } - constexpr decltype(auto) vertex(const Node& node, const priority_tag<0>) noexcept(noexcept(vertex(node))) - { - return vertex(node); - } - - struct vertex_fn - { - template - requires requires(const Node& node, const priority_tag<3> tag) - { - requires concepts::vertex>; - } - constexpr decltype(auto) operator ()(const Node& node) const noexcept(noexcept(detail::vertex(node, priority_tag<3>{}))) - { - return detail::vertex(node, priority_tag<3>{}); - } - }; -} - #endif diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index f46bf98d1..3834f8ff5 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -38,7 +38,7 @@ namespace sl::graph::dfs [[nodiscard]] static constexpr node_type make_successor_node([[maybe_unused]] const node_type& current, const Edge& edge) { - return node_type{.vertex = edge::vertex(edge)}; + return node_type{.vertex = edge::destination(edge)}; } }; @@ -46,7 +46,7 @@ namespace sl::graph::dfs struct Edge { using vertex_type = Vertex; - vertex_type vertex{}; + vertex_type destination{}; }; template < diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index ebd46a0f1..ec6a6979c 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -17,10 +17,69 @@ namespace sl::graph::customize { template struct weight_fn; + + template + struct destination_fn; } namespace sl::graph::edge::detail { + template + requires requires(const Node& node, customize::destination_fn fn) + { + requires concepts::vertex>; + } + constexpr decltype(auto) destination( + const Node& node, + const priority_tag<3> + ) noexcept(noexcept(customize::destination_fn{}(node))) + { + return customize::destination_fn{}(node); + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr auto& destination(const Node& node, const priority_tag<2>) noexcept + { + return node.destination; + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr decltype(auto) destination(const Node& node, const priority_tag<1>) noexcept(noexcept(node.destination())) + { + return node.destination(); + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr decltype(auto) destination(const Node& node, const priority_tag<0>) noexcept(noexcept(destination(node))) + { + return destination(node); + } + + struct destination_fn + { + template + requires requires(const Node& node, const priority_tag<3> tag) + { + requires concepts::vertex>; + } + constexpr decltype(auto) operator ()(const Node& node) const noexcept(noexcept(detail::destination(node, priority_tag<3>{}))) + { + return detail::destination(node, priority_tag<3>{}); + } + }; + template requires requires(const Edge& node, customize::weight_fn fn) { @@ -77,7 +136,7 @@ namespace sl::graph::edge::detail namespace sl::graph::edge { - inline constexpr graph::detail::vertex_fn vertex{}; + inline constexpr detail::destination_fn destination{}; inline constexpr detail::weight_fn weight{}; template @@ -111,7 +170,7 @@ namespace sl::graph::concepts { // fixes compile error on msvc v142 // ReSharper disable once CppRedundantTemplateKeyword - { edge::vertex(edge) } -> std::convertible_to::vertex_type>; + { edge::destination(edge) } -> std::convertible_to::vertex_type>; }; template @@ -151,7 +210,7 @@ namespace sl::graph { using vertex_type = Vertex; - vertex_type vertex; + vertex_type destination; [[nodiscard]] friend bool operator==(const CommonBasicEdge&, const CommonBasicEdge&) = default; @@ -163,7 +222,7 @@ namespace sl::graph using vertex_type = Vertex; using weight_type = Weight; - vertex_type vertex; + vertex_type destination; weight_type weight; [[nodiscard]] @@ -171,4 +230,49 @@ namespace sl::graph }; } +#ifdef SL_UTILITY_HAS_STD_FORMAT + +#include + +template + requires sl::concepts::formattable, Char> +struct std::formatter // NOLINT(cert-dcl58-cpp) +{ + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } + + template + auto format(const Edge& node, FormatContext& fc) const + { + return std::format_to(fc.out(), "{}destination: {}{}", "{", sl::graph::edge::destination(node), "}"); + } +}; + +template + requires sl::concepts::formattable, Char> + && sl::concepts::formattable, Char> +struct std::formatter // NOLINT(cert-dcl58-cpp) +{ + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } + + template + auto format(const Edge& edge, FormatContext& fc) const + { + return std::format_to( + fc.out(), + "{}destination: {}, weight: {}{}", + "{", + sl::graph::edge::destination(edge), + sl::graph::edge::weight(edge), + "}"); + } +}; + +#endif + #endif diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 23a8f654a..5d956fd31 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -22,8 +22,61 @@ namespace sl::graph::customize struct rank_fn; } -namespace sl::graph::node::detail +namespace sl::graph::detail { + template + requires requires(const Node& node, customize::vertex_fn fn) + { + requires concepts::vertex>; + } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<3>) noexcept(noexcept(customize::vertex_fn{}(node))) + { + return customize::vertex_fn{}(node); + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr auto& vertex(const Node& node, const priority_tag<2>) noexcept + { + return node.vertex; + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<1>) noexcept(noexcept(node.vertex())) + { + return node.vertex(); + } + + template + requires requires(const Node& node) + { + requires concepts::vertex>; + } + constexpr decltype(auto) vertex(const Node& node, const priority_tag<0>) noexcept(noexcept(vertex(node))) + { + return vertex(node); + } + + struct vertex_fn + { + template + requires requires(const Node& node, const priority_tag<3> tag) + { + requires concepts::vertex>; + } + constexpr decltype(auto) operator ()(const Node& node) const noexcept(noexcept(detail::vertex(node, priority_tag<3>{}))) + { + return detail::vertex(node, priority_tag<3>{}); + } + }; + template requires requires(const Node& node, customize::rank_fn fn) { @@ -80,7 +133,7 @@ namespace sl::graph::node::detail namespace sl::graph::node { - inline constexpr graph::detail::vertex_fn vertex{}; + inline constexpr detail::vertex_fn vertex{}; inline constexpr detail::rank_fn rank{}; template @@ -186,6 +239,7 @@ struct std::formatter // NOLINT(cert-dcl58-cpp) template requires sl::concepts::formattable, Char> + && sl::concepts::formattable, Char> struct std::formatter // NOLINT(cert-dcl58-cpp) { static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 875728995..ee862e6da 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -87,38 +87,36 @@ namespace sl::graph::detail #if (defined(__clang__) && __clang_major__ < 16) \ || (defined(__GNUG__) && __GNUG__ < 12) - template + template [[nodiscard]] - constexpr std::vector filter_transform(const Source& neighbors, auto& tracker, auto& nodeFactory, const Node& currentNode) + constexpr std::vector filter_transform(const Edges& edges, auto& tracker, auto& nodeFactory, const Node& currentNode) { std::vector nodes{}; - if constexpr (std::ranges::sized_range) + if constexpr (std::ranges::sized_range) { - nodes.reserve(std::ranges::size(neighbors)); + nodes.reserve(std::ranges::size(edges)); } - for (const auto& info : neighbors) + for (const auto& edge : edges) { - if (tracker::set_discovered(tracker, node::vertex(info))) + if (tracker::set_discovered(tracker, edge::destination(edge))) { - nodes.emplace_back(nodeFactory.make_successor_node(currentNode, info)); + nodes.emplace_back(nodeFactory.make_successor_node(currentNode, edge)); } } return nodes; - /* LCOV_EXCL_START */ }; - /* LCOV_EXCL_STOP */ #else - template + template [[nodiscard]] - constexpr auto filter_transform(Source&& neighbors, auto& tracker, auto& nodeFactory, const Node& currentNode) + constexpr auto filter_transform(Edges&& edges, auto& tracker, auto& nodeFactory, const Node& currentNode) { - return std::forward(neighbors) - | std::views::filter([&](const auto& info) { return tracker::set_discovered(tracker, node::vertex(info)); }) - | std::views::transform([&](const auto& info) { return nodeFactory.make_successor_node(currentNode, info); }); + return std::forward(edges) + | std::views::filter([&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }) + | std::views::transform([&](const auto& edge) { return nodeFactory.make_successor_node(currentNode, edge); }); }; #endif diff --git a/tests/graph/Common.cpp b/tests/graph/Common.cpp index 81ece0a21..d795ba006 100644 --- a/tests/graph/Common.cpp +++ b/tests/graph/Common.cpp @@ -75,31 +75,6 @@ namespace { auto operator <=>(const valid_rank&) const = default; }; - - struct member_vertex - { - int vertex; - }; - - struct member_fun_vertex - { - MAKE_CONST_MOCK0(vertex, int()); - }; - - struct free_fun_vertex - { - MAKE_CONST_MOCK0(my_vertex, int()); - - friend int vertex(const free_fun_vertex& v) - { - return v.my_vertex(); - } - }; - - struct custom_fun_vertex - { - MAKE_CONST_MOCK0(my_vertex, int()); - }; } TEMPLATE_TEST_CASE_SIG( @@ -197,15 +172,6 @@ namespace using rank_type = valid_rank; }; } -template <> -struct sg::customize::vertex_fn -{ - [[nodiscard]] - decltype(auto) operator ()(const custom_fun_vertex& e) const - { - return e.my_vertex(); - } -}; TEMPLATE_TEST_CASE_SIG( "graph::concepts::readable_vertex_type determines whether T contains a \"vertex_type\" member alias.", @@ -242,39 +208,3 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::readable_rank_type); } - -TEST_CASE("graph::details::vertex serves as a customization point accessing the vertex.", "[graph][detail]") -{ - constexpr sg::detail::vertex_fn fun{}; - - const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); - - SECTION("Access via the vertex member.") - { - REQUIRE(expected == fun(member_vertex{expected})); - } - - SECTION("Access via the vertex member function.") - { - member_fun_vertex mock{}; - REQUIRE_CALL(mock, vertex()) - .RETURN(expected); - REQUIRE(expected == fun(std::as_const(mock))); - } - - SECTION("Access via the vertex free function.") - { - free_fun_vertex mock{}; - REQUIRE_CALL(mock, my_vertex()) - .RETURN(expected); - REQUIRE(expected == fun(std::as_const(mock))); - } - - SECTION("Access via custom function.") - { - custom_fun_vertex mock{}; - REQUIRE_CALL(mock, my_vertex()) - .RETURN(expected); - REQUIRE(expected == fun(std::as_const(mock))); - } -} diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index baf4985eb..63a29d74e 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -45,7 +45,7 @@ struct GenericBasicEdge { using vertex_type = Vertex; - vertex_type vertex; + vertex_type destination; friend bool operator==(const GenericBasicEdge&, const GenericBasicEdge&) = default; }; @@ -56,7 +56,7 @@ struct GenericWeightedEdge using vertex_type = Vertex; using weight_type = Weight; - vertex_type vertex; + vertex_type destination; weight_type weight; friend bool operator==(const GenericWeightedEdge&, const GenericWeightedEdge&) = default; @@ -124,7 +124,7 @@ class GenericBasicNodeFactoryMock template Edge> node_type make_successor_node(const node_type& current, const Edge& edge) { - return make_successor_node(current, GenericBasicEdge{.vertex = sg::edge::vertex(edge)}); + return make_successor_node(current, GenericBasicEdge{.vertex = sg::edge::destination(edge)}); } }; @@ -143,7 +143,7 @@ class GenericRankedNodeFactoryMock template Edge> node_type make_successor_node(const node_type& current, const Edge& edge) { - return make_successor_node(current, GenericBasicEdge{.vertex = sg::edge::vertex(edge)}); + return make_successor_node(current, GenericBasicEdge{.vertex = sg::edge::destination(edge)}); } }; diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index e2e80a08a..8fc174476 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -39,7 +39,7 @@ namespace std::ranges::transform( vertices, std::back_inserter(infos), - [](const int v) { return edge_type{.vertex = v}; }); + [](const int v) { return edge_type{.destination = v}; }); return infos; } } inline constexpr graph{}; diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 441b560b2..6f286996d 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -16,6 +16,31 @@ namespace { + struct member_destination + { + int destination; + }; + + struct member_fun_destination + { + MAKE_CONST_MOCK0(destination, int()); + }; + + struct free_fun_destination + { + MAKE_CONST_MOCK0(my_destination, int()); + + friend int destination(const free_fun_destination& v) + { + return v.my_destination(); + } + }; + + struct custom_fun_destination + { + MAKE_CONST_MOCK0(my_destination, int()); + }; + struct member_weight { int weight; @@ -42,6 +67,16 @@ namespace }; } +template <> +struct sg::customize::destination_fn +{ + [[nodiscard]] + decltype(auto) operator ()(const custom_fun_destination& e) const + { + return e.my_destination(); + } +}; + template <> struct sg::customize::weight_fn { @@ -52,6 +87,40 @@ struct sg::customize::weight_fn } }; +TEST_CASE("graph::edge::destination serves as a customization point accessing the destination.", "[graph][detail]") +{ + const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); + + SECTION("Access via the destination member.") + { + REQUIRE(expected == sg::edge::destination(member_destination{expected})); + } + + SECTION("Access via the destination member function.") + { + member_fun_destination mock{}; + REQUIRE_CALL(mock, destination()) + .RETURN(expected); + REQUIRE(expected == sg::edge::destination(std::as_const(mock))); + } + + SECTION("Access via the destination free function.") + { + free_fun_destination mock{}; + REQUIRE_CALL(mock, my_destination()) + .RETURN(expected); + REQUIRE(expected == sg::edge::destination(std::as_const(mock))); + } + + SECTION("Access via custom function.") + { + custom_fun_destination mock{}; + REQUIRE_CALL(mock, my_destination()) + .RETURN(expected); + REQUIRE(expected == sg::edge::destination(std::as_const(mock))); + } +} + TEST_CASE("graph::edge::weight serves as a customization point accessing the edge weight.", "[graph][graph::edge]") { const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); @@ -159,3 +228,21 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::edge_for); } + +#ifdef SL_UTILITY_HAS_STD_FORMAT + +TEST_CASE("edge types can be formatted.", "[graph][graph::edge]") +{ + using TestType = GenericBasicEdge; + + REQUIRE("{vertex: Hello, World!}" == std::format("{}", TestType{.destination = "Hello, World!"})); +} + +TEST_CASE("weighted_edge types can be formatted.", "[graph][graph::edge]") +{ + using TestType = GenericWeightedEdge; + + REQUIRE("{vertex: Hello, World!, weight: 42}" == std::format("{}", TestType{.destination = "Hello, World!", .weight = 42})); +} + +#endif diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index d36c413d8..28741ac94 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -18,6 +18,31 @@ namespace { + struct member_vertex + { + int vertex; + }; + + struct member_fun_vertex + { + MAKE_CONST_MOCK0(vertex, int()); + }; + + struct free_fun_vertex + { + MAKE_CONST_MOCK0(my_vertex, int()); + + friend int vertex(const free_fun_vertex& v) + { + return v.my_vertex(); + } + }; + + struct custom_fun_vertex + { + MAKE_CONST_MOCK0(my_vertex, int()); + }; + struct member_rank { int rank; @@ -66,6 +91,16 @@ namespace }; } +template <> +struct sg::customize::vertex_fn +{ + [[nodiscard]] + decltype(auto) operator ()(const custom_fun_vertex& e) const + { + return e.my_vertex(); + } +}; + template <> struct sg::node::traits { @@ -83,6 +118,40 @@ struct sg::customize::rank_fn } }; +TEST_CASE("graph::node::vertex serves as a customization point accessing the vertex.", "[graph][detail]") +{ + const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); + + SECTION("Access via the vertex member.") + { + REQUIRE(expected == sg::node::vertex(member_vertex{expected})); + } + + SECTION("Access via the vertex member function.") + { + member_fun_vertex mock{}; + REQUIRE_CALL(mock, vertex()) + .RETURN(expected); + REQUIRE(expected == sg::node::vertex(std::as_const(mock))); + } + + SECTION("Access via the vertex free function.") + { + free_fun_vertex mock{}; + REQUIRE_CALL(mock, my_vertex()) + .RETURN(expected); + REQUIRE(expected == sg::node::vertex(std::as_const(mock))); + } + + SECTION("Access via custom function.") + { + custom_fun_vertex mock{}; + REQUIRE_CALL(mock, my_vertex()) + .RETURN(expected); + REQUIRE(expected == sg::node::vertex(std::as_const(mock))); + } +} + TEST_CASE("graph::node::rank serves as a customization point accessing the node rank.", "[graph][graph::node]") { const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); From 42539725d6c518bcd144a6d6481410f89e961dc2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 04:46:40 +0200 Subject: [PATCH 114/256] fix: correct param names --- include/Simple-Utility/graph/Edge.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index ec6a6979c..a1101c4e5 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -244,9 +244,9 @@ struct std::formatter // NOLINT(cert-dcl58-cpp) } template - auto format(const Edge& node, FormatContext& fc) const + auto format(const Edge& edge, FormatContext& ctx) const { - return std::format_to(fc.out(), "{}destination: {}{}", "{", sl::graph::edge::destination(node), "}"); + return std::format_to(ctx.out(), "{}destination: {}{}", "{", sl::graph::edge::destination(edge), "}"); } }; @@ -261,10 +261,10 @@ struct std::formatter // NOLINT(cert-dcl58-cpp) } template - auto format(const Edge& edge, FormatContext& fc) const + auto format(const Edge& edge, FormatContext& ctx) const { return std::format_to( - fc.out(), + ctx.out(), "{}destination: {}, weight: {}{}", "{", sl::graph::edge::destination(edge), From 1ceeee0e573fc3582acfbc7b693354fbbefd91d8 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 04:54:20 +0200 Subject: [PATCH 115/256] fix: correct formatting test case --- tests/graph/Edge.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 6f286996d..9b48c1b4b 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -235,14 +235,14 @@ TEST_CASE("edge types can be formatted.", "[graph][graph::edge]") { using TestType = GenericBasicEdge; - REQUIRE("{vertex: Hello, World!}" == std::format("{}", TestType{.destination = "Hello, World!"})); + REQUIRE("{destination: Hello, World!}" == std::format("{}", TestType{.destination = "Hello, World!"})); } TEST_CASE("weighted_edge types can be formatted.", "[graph][graph::edge]") { using TestType = GenericWeightedEdge; - REQUIRE("{vertex: Hello, World!, weight: 42}" == std::format("{}", TestType{.destination = "Hello, World!", .weight = 42})); + REQUIRE("{destination: Hello, World!, weight: 42}" == std::format("{}", TestType{.destination = "Hello, World!", .weight = 42})); } #endif From d1cf2c60ca1bd63fec3c5de29247cf6c111fc88e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 6 Sep 2023 05:08:00 +0200 Subject: [PATCH 116/256] refactor: cleanup graph::dfs namespace --- .../Simple-Utility/graph/DepthFirstSearch.hpp | 24 ++++--------------- tests/graph/DepthFirstSearch.cpp | 12 ++++++---- tests/graph/Edge.cpp | 5 +++- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index 3834f8ff5..cf82b11cb 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -11,22 +11,13 @@ namespace sl::graph::dfs { - template - struct Node - { - using vertex_type = Vertex; - vertex_type vertex{}; - - friend bool operator==(const Node&, const Node&) = default; - }; - template struct NodeFactory; - + template - struct NodeFactory> + struct NodeFactory> { - using node_type = Node; + using node_type = CommonBasicNode; using vertex_type = node::vertex_t; static constexpr node_type make_init_node(vertex_type origin) @@ -42,16 +33,9 @@ namespace sl::graph::dfs } }; - template - struct Edge - { - using vertex_type = Vertex; - vertex_type destination{}; - }; - template < class View, - concepts::node Node = Node>>, + concepts::node Node = CommonBasicNode>>, concepts::node_factory_for> NodeFactory = NodeFactory, concepts::tracker_for> Tracker = std::unordered_map, bool>> requires concepts::view_for diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 8fc174476..ec87d68e0 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -29,11 +29,13 @@ namespace {9, {4}} }; - using edge_type = sg::dfs::Edge; + using edge_type = sg::CommonBasicEdge; - static std::vector edges(const sg::dfs::Node current) + template + requires sg::concepts::edge_for + static std::vector edges(const Node& current) { - const auto& vertices = graph.at(current.vertex); + const auto& vertices = graph.at(sg::node::vertex(current)); std::vector infos{}; infos.reserve(std::ranges::size(vertices)); std::ranges::transform( @@ -48,7 +50,7 @@ namespace TEST_CASE("dfs::BasicTraverser visits all reachable vertices.", "[graph][graph::dfs]") { const auto& [expected, origin] = GENERATE( - (table>, int>)({ + (table>, int>)({ {{/*3,*/ {6}, {2}, {5}}, 3}, {{/*6,*/ {2}}, 6}, {{/*1,*/ {3}, {6}, {2}, {5}}, 1}, @@ -58,7 +60,7 @@ TEST_CASE("dfs::BasicTraverser visits all reachable vertices.", "[graph][graph:: sg::dfs::BasicTraverser traverser{graph, origin}; STATIC_CHECK(std::ranges::input_range); - std::vector> nodes{}; + std::vector> nodes{}; std::ranges::copy(traverser, std::back_inserter(nodes)); REQUIRE_THAT(nodes, Catch::Matchers::RangeEquals(expected)); diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 9b48c1b4b..6747ce587 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -223,7 +223,10 @@ TEMPLATE_TEST_CASE_SIG( (true, (GenericWeightedEdge), GenericBasicNode), (false, GenericBasicEdge, (GenericRankedNode)), - (true, (GenericWeightedEdge), (GenericRankedNode)) + (true, (GenericWeightedEdge), (GenericRankedNode)), + + (true, sg::CommonBasicEdge, sg::CommonBasicNode), + (true, sg::CommonWeightedEdge, sg::CommonRankedNode) ) { STATIC_REQUIRE(expected == sg::concepts::edge_for); From 2cb089197f6045603223598f2df0fbdc9e000bc4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 15:19:53 +0200 Subject: [PATCH 117/256] test: add queue protocol test case for std::stack --- tests/graph/Queue.cpp | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index e75e90393..070e4af6f 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -37,14 +37,7 @@ namespace MAKE_CONST_MOCK0(is_empty, bool()); }; - struct TestNode - { - // ReSharper disable once CppTypeAliasNeverUsed - using vertex_type = int; - int vertex{}; - - friend bool operator==(const TestNode&, const TestNode&) = default; - }; + using TestNode = GenericBasicNode; struct member_fun_insert { @@ -224,3 +217,31 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::queue_for); } + +TEST_CASE("std::stack follows the queue protocol.", "[graph][graph::queue]") +{ + std::stack queue{}; + + REQUIRE(sg::queue::empty(queue)); + + TestNode node{.vertex = 42}; + + SECTION("When a single node is inserted.") + { + sg::queue::insert(queue, std::views::single(node)); + } + + SECTION("When multiple nodes are inserted.") + { + sg::queue::insert(queue, std::array{TestNode{44}, node}); + } + + SECTION("When multiple nodes are inserted during multiple insertions.") + { + sg::queue::insert(queue, std::views::single(TestNode{41})); + sg::queue::insert(queue, std::array{TestNode{44}, node}); + } + + REQUIRE(!sg::queue::empty(queue)); + REQUIRE(node == sg::queue::next(queue)); +} From e9b326b2a76e596b4284c2ba8a3f8fa3fb96f8e9 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 15:21:42 +0200 Subject: [PATCH 118/256] feat: add std::queue queue mixin --- .../graph/mixins/queue/std_queue.hpp | 43 +++++++++++++++++++ tests/graph/Queue.cpp | 32 +++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 include/Simple-Utility/graph/mixins/queue/std_queue.hpp diff --git a/include/Simple-Utility/graph/mixins/queue/std_queue.hpp b/include/Simple-Utility/graph/mixins/queue/std_queue.hpp new file mode 100644 index 000000000..1347bbd09 --- /dev/null +++ b/include/Simple-Utility/graph/mixins/queue/std_queue.hpp @@ -0,0 +1,43 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STD_QUEUE_HPP +#define SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STD_QUEUE_HPP + +#pragma once + +#include +#include +#include +#include + +#include "Simple-Utility/graph/Queue.hpp" + +template +struct sl::graph::customize::insert_fn> +{ + template + requires std::convertible_to, T> + constexpr void operator ()(std::queue& container, Range&& elements) const + { + for (auto&& element : std::forward(elements)) + { + container.push(std::forward(element)); + } + } +}; + +template +struct sl::graph::customize::next_fn> +{ + constexpr T operator ()(std::queue& container) const + { + auto element = std::move(container.front()); + container.pop(); + return element; + } +}; + +#endif diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 070e4af6f..4b56d4b3a 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -4,6 +4,7 @@ // https://www.boost.org/LICENSE_1_0.txt) #include "Simple-Utility/graph/Queue.hpp" +#include "Simple-Utility/graph/mixins/queue/std_queue.hpp" #include "Simple-Utility/graph/mixins/queue/std_stack.hpp" #include @@ -212,7 +213,8 @@ TEMPLATE_TEST_CASE_SIG( (false, member_fun_next), (false, free_fun_next), (true, QueueMock), - (true, std::stack) + (true, std::stack), + (true, std::queue) ) { STATIC_REQUIRE(expected == sg::concepts::queue_for); @@ -245,3 +247,31 @@ TEST_CASE("std::stack follows the queue protocol.", "[graph][graph::queue]") REQUIRE(!sg::queue::empty(queue)); REQUIRE(node == sg::queue::next(queue)); } + +TEST_CASE("std::queue follows the queue protocol.", "[graph][graph::queue]") +{ + std::queue queue{}; + + REQUIRE(sg::queue::empty(queue)); + + TestNode node{.vertex = 42}; + + SECTION("When a single node is inserted.") + { + sg::queue::insert(queue, std::views::single(node)); + } + + SECTION("When multiple nodes are inserted.") + { + sg::queue::insert(queue, std::array{node, TestNode{44}}); + } + + SECTION("When multiple nodes are inserted during multiple insertions.") + { + sg::queue::insert(queue, std::array{node, TestNode{44}}); + sg::queue::insert(queue, std::views::single(TestNode{41})); + } + + REQUIRE(!sg::queue::empty(queue)); + REQUIRE(node == sg::queue::next(queue)); +} From 44f41ed052dd4368f8c570149fd71752bfdf42b1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 15:22:22 +0200 Subject: [PATCH 119/256] fix: missing include guards in DepthFirstSearch.hpp --- include/Simple-Utility/graph/DepthFirstSearch.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index cf82b11cb..1255acd0f 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -3,6 +3,9 @@ // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) +#ifndef SIMPLE_UTILITY_GRAPH_DEPTH_FIRST_SEARCH_HPP +#define SIMPLE_UTILITY_GRAPH_DEPTH_FIRST_SEARCH_HPP + #pragma once #include "Simple-Utility/graph/Traverse.hpp" @@ -47,3 +50,5 @@ namespace sl::graph::dfs Tracker, NodeFactory>>; } + +#endif From 742034d5eb5f34bf4dbf11ea41d482d2c9cb5ff6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 16:10:43 +0200 Subject: [PATCH 120/256] feat: std::priority_queue queue mixin --- .../graph/mixins/queue/std_priority_queue.hpp | 63 +++++++++++++++++++ tests/graph/Queue.cpp | 53 ++++++++++++---- 2 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp diff --git a/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp b/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp new file mode 100644 index 000000000..eb573e190 --- /dev/null +++ b/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp @@ -0,0 +1,63 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STD_PRIORITY_QUEUE_HPP +#define SIMPLE_UTILITY_GRAPH_MIXINS_QUEUE_STD_PRIORITY_QUEUE_HPP + +#pragma once + +#include +#include +#include +#include + +#include "Simple-Utility/graph/Node.hpp" +#include "Simple-Utility/graph/Queue.hpp" + +namespace sl::graph +{ + struct PriorityAfterRelation + { + template + [[nodiscard]] + constexpr bool operator ()(const Node& lhs, const Node& rhs) const + { + return node::rank(lhs) > node::rank(rhs); + } + }; +} + +template +struct sl::graph::customize::insert_fn> +{ + template + requires std::convertible_to, T> + constexpr void operator ()(std::priority_queue& container, Range&& elements) const + { + for (auto&& element : std::forward(elements)) + { + container.push(std::forward(element)); + } + } +}; + +template +struct sl::graph::customize::next_fn> +{ + constexpr T operator ()(std::priority_queue& container) const + { + auto element = container.top(); + container.pop(); + return element; + } +}; + +namespace sl::graph +{ + template > + using common_priority_queue = std::priority_queue; +} + +#endif diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 4b56d4b3a..745aaea1d 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -4,6 +4,7 @@ // https://www.boost.org/LICENSE_1_0.txt) #include "Simple-Utility/graph/Queue.hpp" +#include "Simple-Utility/graph/mixins/queue/std_priority_queue.hpp" #include "Simple-Utility/graph/mixins/queue/std_queue.hpp" #include "Simple-Utility/graph/mixins/queue/std_stack.hpp" @@ -39,6 +40,7 @@ namespace }; using TestNode = GenericBasicNode; + using TestRankedNode = GenericRankedNode; struct member_fun_insert { @@ -205,19 +207,20 @@ TEST_CASE("graph::queue::next serves as a customization point, retrieving the ne TEMPLATE_TEST_CASE_SIG( "concepts::queue_for determines, whether the given type satisfies the requirements of a queue for the specified node type.", "[graph][graph::concepts]", - ((bool expected, class T), expected, T), - (false, member_fun_empty), - (false, free_fun_empty), - (false, member_fun_insert), - (false, free_fun_insert), - (false, member_fun_next), - (false, free_fun_next), - (true, QueueMock), - (true, std::stack), - (true, std::queue) + ((bool expected, class Queue, class Node), expected, Queue, Node), + (false, member_fun_empty, TestNode), + (false, free_fun_empty, TestNode), + (false, member_fun_insert, TestNode), + (false, free_fun_insert, TestNode), + (false, member_fun_next, TestNode), + (false, free_fun_next, TestNode), + (true, QueueMock, TestNode), + (true, std::stack, TestNode), + (true, std::queue, TestNode), + (true, sg::common_priority_queue, TestRankedNode) ) { - STATIC_REQUIRE(expected == sg::concepts::queue_for); + STATIC_REQUIRE(expected == sg::concepts::queue_for); } TEST_CASE("std::stack follows the queue protocol.", "[graph][graph::queue]") @@ -275,3 +278,31 @@ TEST_CASE("std::queue follows the queue protocol.", "[graph][graph::queue]") REQUIRE(!sg::queue::empty(queue)); REQUIRE(node == sg::queue::next(queue)); } + +TEST_CASE("std::priority_queue follows the queue protocol.", "[graph][graph::queue]") +{ + sg::common_priority_queue queue{}; + + REQUIRE(sg::queue::empty(queue)); + + TestRankedNode node{.vertex = "V42", .rank = 1}; + + SECTION("When a single node is inserted.") + { + sg::queue::insert(queue, std::views::single(node)); + } + + SECTION("When multiple nodes are inserted.") + { + sg::queue::insert(queue, std::array{node, TestRankedNode{"V44", 2}}); + } + + SECTION("When multiple nodes are inserted during multiple insertions.") + { + sg::queue::insert(queue, std::array{node, TestRankedNode{"V44", 2}}); + sg::queue::insert(queue, std::views::single(TestRankedNode{"V41", 3})); + } + + REQUIRE(!sg::queue::empty(queue)); + REQUIRE(node == sg::queue::next(queue)); +} From 6d45410c89b2e63cc8048a64c007c7ea484be707 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 16:17:59 +0200 Subject: [PATCH 121/256] feat: common queue aliasses --- .../graph/mixins/queue/std_priority_queue.hpp | 25 ++++++++----------- .../graph/mixins/queue/std_queue.hpp | 7 ++++++ .../graph/mixins/queue/std_stack.hpp | 6 +++++ tests/graph/Queue.cpp | 18 ++++++------- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp b/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp index eb573e190..50189ddc8 100644 --- a/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp @@ -16,19 +16,6 @@ #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Queue.hpp" -namespace sl::graph -{ - struct PriorityAfterRelation - { - template - [[nodiscard]] - constexpr bool operator ()(const Node& lhs, const Node& rhs) const - { - return node::rank(lhs) > node::rank(rhs); - } - }; -} - template struct sl::graph::customize::insert_fn> { @@ -54,8 +41,18 @@ struct sl::graph::customize::next_fn + [[nodiscard]] + constexpr bool operator ()(const Node& lhs, const Node& rhs) const + { + return node::rank(lhs) > node::rank(rhs); + } + }; + template > using common_priority_queue = std::priority_queue; } diff --git a/include/Simple-Utility/graph/mixins/queue/std_queue.hpp b/include/Simple-Utility/graph/mixins/queue/std_queue.hpp index 1347bbd09..da69356f4 100644 --- a/include/Simple-Utility/graph/mixins/queue/std_queue.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_queue.hpp @@ -13,6 +13,7 @@ #include #include +#include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Queue.hpp" template @@ -40,4 +41,10 @@ struct sl::graph::customize::next_fn> } }; +namespace sl::graph::queue +{ + template > + using common_queue = std::queue; +} + #endif diff --git a/include/Simple-Utility/graph/mixins/queue/std_stack.hpp b/include/Simple-Utility/graph/mixins/queue/std_stack.hpp index 8003082db..91740138b 100644 --- a/include/Simple-Utility/graph/mixins/queue/std_stack.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_stack.hpp @@ -40,4 +40,10 @@ struct sl::graph::customize::next_fn> } }; +namespace sl::graph::queue +{ + template > + using common_stack = std::stack; +} + #endif diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 745aaea1d..c9141a465 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -215,17 +215,17 @@ TEMPLATE_TEST_CASE_SIG( (false, member_fun_next, TestNode), (false, free_fun_next, TestNode), (true, QueueMock, TestNode), - (true, std::stack, TestNode), - (true, std::queue, TestNode), - (true, sg::common_priority_queue, TestRankedNode) + (true, sg::queue::common_stack, TestNode), + (true, sg::queue::common_queue, TestNode), + (true, sg::queue::common_priority_queue, TestRankedNode) ) { STATIC_REQUIRE(expected == sg::concepts::queue_for); } -TEST_CASE("std::stack follows the queue protocol.", "[graph][graph::queue]") +TEST_CASE("graph::queue::common_stack follows the queue protocol.", "[graph][graph::queue]") { - std::stack queue{}; + sg::queue::common_stack queue{}; REQUIRE(sg::queue::empty(queue)); @@ -251,9 +251,9 @@ TEST_CASE("std::stack follows the queue protocol.", "[graph][graph::queue]") REQUIRE(node == sg::queue::next(queue)); } -TEST_CASE("std::queue follows the queue protocol.", "[graph][graph::queue]") +TEST_CASE("graph::queue::common_queue follows the queue protocol.", "[graph][graph::queue]") { - std::queue queue{}; + sg::queue::common_queue queue{}; REQUIRE(sg::queue::empty(queue)); @@ -279,9 +279,9 @@ TEST_CASE("std::queue follows the queue protocol.", "[graph][graph::queue]") REQUIRE(node == sg::queue::next(queue)); } -TEST_CASE("std::priority_queue follows the queue protocol.", "[graph][graph::queue]") +TEST_CASE("graph::queue::common_priority_queue follows the queue protocol.", "[graph][graph::queue]") { - sg::common_priority_queue queue{}; + sg::queue::common_priority_queue queue{}; REQUIRE(sg::queue::empty(queue)); From 64ca1a82ffb917ede9227f184602f3197e3bd44f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 16:30:03 +0200 Subject: [PATCH 122/256] feat: common tracker aliasses --- include/Simple-Utility/graph/mixins/tracker/std_map.hpp | 7 +++++++ .../graph/mixins/tracker/std_unordered_map.hpp | 8 ++++++++ tests/graph/Tracker.cpp | 8 ++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/graph/mixins/tracker/std_map.hpp b/include/Simple-Utility/graph/mixins/tracker/std_map.hpp index 62c5b4db3..e24f907b5 100644 --- a/include/Simple-Utility/graph/mixins/tracker/std_map.hpp +++ b/include/Simple-Utility/graph/mixins/tracker/std_map.hpp @@ -11,6 +11,7 @@ #include #include +#include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Tracker.hpp" template @@ -35,4 +36,10 @@ struct sl::graph::customize::set_visited_fn + using common_map = std::map; +} + #endif diff --git a/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp b/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp index e1b9c31d1..1121eeca0 100644 --- a/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp +++ b/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp @@ -11,6 +11,7 @@ #include #include +#include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Tracker.hpp" template @@ -35,4 +36,11 @@ struct sl::graph::customize::set_visited_fn> + requires std::is_invocable_r_v + using common_hash_map = std::unordered_map; +} + #endif diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index 1df3173bb..d81f3fd5d 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -154,8 +154,8 @@ TEMPLATE_TEST_CASE_SIG( (false, free_fun_set_visited, int), (false, TrackerMock, std::string), (true, TrackerMock, int), - (true, std::unordered_map, int), - (true, std::map, int) + (true, sg::tracker::common_hash_map, int), + (true, sg::tracker::common_map, int) ) { STATIC_REQUIRE(expected == sg::concepts::tracker_for); @@ -164,8 +164,8 @@ TEMPLATE_TEST_CASE_SIG( TEMPLATE_TEST_CASE( "Concrete tracker types behave as expected.", "[graph][graph::concepts][graph::tracker]", - (std::unordered_map), - (std::map) + (sg::tracker::common_hash_map), + (sg::tracker::common_map) ) { TestType tracker{}; From dcd66d02183dd5e98216eca69584d2d2c2ee3d1a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 16:45:34 +0200 Subject: [PATCH 123/256] refactor: rename several aliasses to PascalCase --- .../graph/mixins/queue/std_priority_queue.hpp | 2 +- .../graph/mixins/queue/std_queue.hpp | 2 +- .../graph/mixins/queue/std_stack.hpp | 2 +- .../graph/mixins/tracker/std_map.hpp | 2 +- .../graph/mixins/tracker/std_unordered_map.hpp | 2 +- tests/graph/Queue.cpp | 18 +++++++++--------- tests/graph/Tracker.cpp | 8 ++++---- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp b/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp index 50189ddc8..1685b8f69 100644 --- a/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_priority_queue.hpp @@ -54,7 +54,7 @@ namespace sl::graph::queue }; template > - using common_priority_queue = std::priority_queue; + using CommonPriorityQueue = std::priority_queue; } #endif diff --git a/include/Simple-Utility/graph/mixins/queue/std_queue.hpp b/include/Simple-Utility/graph/mixins/queue/std_queue.hpp index da69356f4..1cfa72d99 100644 --- a/include/Simple-Utility/graph/mixins/queue/std_queue.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_queue.hpp @@ -44,7 +44,7 @@ struct sl::graph::customize::next_fn> namespace sl::graph::queue { template > - using common_queue = std::queue; + using CommonQueue = std::queue; } #endif diff --git a/include/Simple-Utility/graph/mixins/queue/std_stack.hpp b/include/Simple-Utility/graph/mixins/queue/std_stack.hpp index 91740138b..422364ce5 100644 --- a/include/Simple-Utility/graph/mixins/queue/std_stack.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_stack.hpp @@ -43,7 +43,7 @@ struct sl::graph::customize::next_fn> namespace sl::graph::queue { template > - using common_stack = std::stack; + using CommonStack = std::stack; } #endif diff --git a/include/Simple-Utility/graph/mixins/tracker/std_map.hpp b/include/Simple-Utility/graph/mixins/tracker/std_map.hpp index e24f907b5..40e69c05a 100644 --- a/include/Simple-Utility/graph/mixins/tracker/std_map.hpp +++ b/include/Simple-Utility/graph/mixins/tracker/std_map.hpp @@ -39,7 +39,7 @@ struct sl::graph::customize::set_visited_fn - using common_map = std::map; + using CommonMap = std::map; } #endif diff --git a/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp b/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp index 1121eeca0..91a86393e 100644 --- a/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp +++ b/include/Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp @@ -40,7 +40,7 @@ namespace sl::graph::tracker { template > requires std::is_invocable_r_v - using common_hash_map = std::unordered_map; + using CommonHashMap = std::unordered_map; } #endif diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index c9141a465..6e0c67607 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -215,17 +215,17 @@ TEMPLATE_TEST_CASE_SIG( (false, member_fun_next, TestNode), (false, free_fun_next, TestNode), (true, QueueMock, TestNode), - (true, sg::queue::common_stack, TestNode), - (true, sg::queue::common_queue, TestNode), - (true, sg::queue::common_priority_queue, TestRankedNode) + (true, sg::queue::CommonStack, TestNode), + (true, sg::queue::CommonQueue, TestNode), + (true, sg::queue::CommonPriorityQueue, TestRankedNode) ) { STATIC_REQUIRE(expected == sg::concepts::queue_for); } -TEST_CASE("graph::queue::common_stack follows the queue protocol.", "[graph][graph::queue]") +TEST_CASE("graph::queue::CommonStack follows the queue protocol.", "[graph][graph::queue]") { - sg::queue::common_stack queue{}; + sg::queue::CommonStack queue{}; REQUIRE(sg::queue::empty(queue)); @@ -251,9 +251,9 @@ TEST_CASE("graph::queue::common_stack follows the queue protocol.", "[graph][gra REQUIRE(node == sg::queue::next(queue)); } -TEST_CASE("graph::queue::common_queue follows the queue protocol.", "[graph][graph::queue]") +TEST_CASE("graph::queue::CommonQueue follows the queue protocol.", "[graph][graph::queue]") { - sg::queue::common_queue queue{}; + sg::queue::CommonQueue queue{}; REQUIRE(sg::queue::empty(queue)); @@ -279,9 +279,9 @@ TEST_CASE("graph::queue::common_queue follows the queue protocol.", "[graph][gra REQUIRE(node == sg::queue::next(queue)); } -TEST_CASE("graph::queue::common_priority_queue follows the queue protocol.", "[graph][graph::queue]") +TEST_CASE("graph::queue::CommonPriorityQueue follows the queue protocol.", "[graph][graph::queue]") { - sg::queue::common_priority_queue queue{}; + sg::queue::CommonPriorityQueue queue{}; REQUIRE(sg::queue::empty(queue)); diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index d81f3fd5d..50c636edf 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -154,8 +154,8 @@ TEMPLATE_TEST_CASE_SIG( (false, free_fun_set_visited, int), (false, TrackerMock, std::string), (true, TrackerMock, int), - (true, sg::tracker::common_hash_map, int), - (true, sg::tracker::common_map, int) + (true, sg::tracker::CommonHashMap, int), + (true, sg::tracker::CommonMap, int) ) { STATIC_REQUIRE(expected == sg::concepts::tracker_for); @@ -164,8 +164,8 @@ TEMPLATE_TEST_CASE_SIG( TEMPLATE_TEST_CASE( "Concrete tracker types behave as expected.", "[graph][graph::concepts][graph::tracker]", - (sg::tracker::common_hash_map), - (sg::tracker::common_map) + (sg::tracker::CommonHashMap), + (sg::tracker::CommonMap) ) { TestType tracker{}; From 99e26e9290e8190130dacc93542d93118bc30011 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 16:45:58 +0200 Subject: [PATCH 124/256] refactor: utilize existing aliasses in dfs --- include/Simple-Utility/graph/DepthFirstSearch.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index 1255acd0f..4b17c1940 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -40,13 +40,13 @@ namespace sl::graph::dfs class View, concepts::node Node = CommonBasicNode>>, concepts::node_factory_for> NodeFactory = NodeFactory, - concepts::tracker_for> Tracker = std::unordered_map, bool>> + concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::view_for using BasicTraverser = Traverser< View, detail::BasicTraverseDriver< Node, - detail::BasicState>, + detail::BasicState>, Tracker, NodeFactory>>; } From 78ca12aa968c8e5d84a57edcc79d523775d0c4b2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 17:18:35 +0200 Subject: [PATCH 125/256] feat: UniformCostSearch --- .../graph/UniformCostSearch.hpp | 54 +++++++++++++ tests/graph/CMakeLists.txt | 1 + tests/graph/UniformCostSearch.cpp | 76 +++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 include/Simple-Utility/graph/UniformCostSearch.hpp create mode 100644 tests/graph/UniformCostSearch.cpp diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp new file mode 100644 index 000000000..849808486 --- /dev/null +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -0,0 +1,54 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_UNIFORM_COST_SEARCH_HPP +#define SIMPLE_UTILITY_GRAPH_UNIFORM_COST_SEARCH_HPP + +#pragma once + +#include "Simple-Utility/graph/Traverse.hpp" +#include "Simple-Utility/graph/mixins/queue/std_priority_queue.hpp" +#include "Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp" + +namespace sl::graph::ucs +{ + template + struct NodeFactory + { + using node_type = Node; + using vertex_type = node::vertex_t; + + static constexpr node_type make_init_node(vertex_type origin) + { + return node_type{.vertex = std::move(origin), .rank = 0}; + } + + template Edge> + [[nodiscard]] + static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) + { + return node_type{ + .vertex = edge::destination(edge), + .rank = node::rank(current) + edge::weight(edge) + }; + } + }; + + template < + class View, + concepts::ranked_node Node = CommonRankedNode>, edge::weight_t>>, + concepts::node_factory_for> NodeFactory = NodeFactory, + concepts::tracker_for> Tracker = tracker::CommonHashMap>> + requires concepts::view_for + using BasicTraverser = Traverser< + View, + detail::BasicTraverseDriver< + Node, + detail::BasicState>, + Tracker, + NodeFactory>>; +} + +#endif diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 4711bac14..c17e04040 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -9,5 +9,6 @@ target_sources( "Queue.cpp" "Tracker.cpp" "Traverse.cpp" + "UniformCostSearch.cpp" "View.cpp" ) diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp new file mode 100644 index 000000000..349304f2e --- /dev/null +++ b/tests/graph/UniformCostSearch.cpp @@ -0,0 +1,76 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/UniformCostSearch.hpp" + +#include +#include +#include + +#include "Defines.hpp" + +namespace +{ + using Node = sg::CommonRankedNode; + using Edge = sg::CommonWeightedEdge; + + struct View + { + using edge_type = Edge; + + inline static const std::unordered_map> graph{ + {"1", {2, 3}}, + {"2", {6}}, + {"3", {5, 6}}, + {"5", {5}}, + {"6", {2}}, + + // begin isolated sub-graph + {"4", {7}}, + {"7", {4, 7, 9}}, + {"8", {7, 9}}, + {"9", {4}} + }; + + template + requires sg::concepts::edge_for + static std::vector edges(const Node& current) + { + const auto& vertices = graph.at(sg::node::vertex(current)); + std::vector infos{}; + infos.reserve(std::ranges::size(vertices)); + std::ranges::transform( + vertices, + std::back_inserter(infos), + [&](const int v) + { + return edge_type{ + .destination = std::to_string(v), + .weight = std::abs(v - std::stoi(current.vertex)) + }; + }); + return infos; + } + } inline constexpr graph{}; +} + +TEST_CASE("ucs::BasicTraverser visits all reachable vertices.", "[graph][graph::ucs]") +{ + const auto& [expected, origin] = GENERATE( + (table, std::string>)({ + {{{"5", 2}, {"6", 3}, {"2", 7}}, "3"}, + {{{"2", 4}}, "6"}, + {{{"2", 1}, {"3", 2}, {"6", 5}, {"5", 4}}, "1"}, + {{{"7", 1}, {"4", 4}, {"9", 1}}, "8"}, + })); + + sg::ucs::BasicTraverser traverser{graph, origin}; + STATIC_CHECK(std::ranges::input_range); + + std::vector nodes{}; + std::ranges::copy(traverser, std::back_inserter(nodes)); + + REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); +} From 486d30b255118ccd9a9c3b179ecc3a83bf0327a6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 17:52:36 +0200 Subject: [PATCH 126/256] feat: PredecessorNodeDecorator --- include/Simple-Utility/graph/Node.hpp | 14 ++++++++++++++ tests/graph/Node.cpp | 8 ++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 5d956fd31..729db4d2d 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -13,6 +13,8 @@ #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" +#include + namespace sl::graph::customize { template @@ -215,6 +217,18 @@ namespace sl::graph [[nodiscard]] friend bool operator==(const CommonRankedNode&, const CommonRankedNode&) = default; }; + + template + struct PredecessorNodeDecorator + : public Node + { + using vertex_type = node::vertex_t; + + std::optional predecessor{}; + + [[nodiscard]] + friend bool operator==(const PredecessorNodeDecorator&, const PredecessorNodeDecorator&) = default; + }; } #ifdef SL_UTILITY_HAS_STD_FORMAT diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 28741ac94..24c92fa69 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -195,7 +195,9 @@ TEMPLATE_TEST_CASE_SIG( (true, GenericBasicNode), (true, GenericRankedNode), (true, sg::CommonBasicNode), - (true, sg::CommonRankedNode) + (true, sg::CommonRankedNode), + (true, sg::PredecessorNodeDecorator>), + (true, sg::PredecessorNodeDecorator>) ) { STATIC_REQUIRE(expected == sg::concepts::node); @@ -213,7 +215,9 @@ TEMPLATE_TEST_CASE_SIG( (false, GenericBasicNode), (true, GenericRankedNode), (false, sg::CommonBasicNode), - (true, sg::CommonRankedNode) + (false, sg::PredecessorNodeDecorator>), + (true, sg::CommonRankedNode), + (true, sg::PredecessorNodeDecorator>) ) { STATIC_REQUIRE(expected == sg::concepts::ranked_node); From f4df4a7d993cac70d8f695aa991c36ab92c72f86 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 17:54:32 +0200 Subject: [PATCH 127/256] feat: add dedicated ucs::NodeFactory for predecessor decorated nodes --- .../graph/UniformCostSearch.hpp | 27 +++++++++++++++++++ tests/graph/UniformCostSearch.cpp | 23 +++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp index 849808486..b15dac3a1 100644 --- a/include/Simple-Utility/graph/UniformCostSearch.hpp +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -20,6 +20,7 @@ namespace sl::graph::ucs using node_type = Node; using vertex_type = node::vertex_t; + [[nodiscard]] static constexpr node_type make_init_node(vertex_type origin) { return node_type{.vertex = std::move(origin), .rank = 0}; @@ -36,6 +37,32 @@ namespace sl::graph::ucs } }; + template + struct NodeFactory> + { + using node_type = PredecessorNodeDecorator; + using vertex_type = node::vertex_t; + + [[nodiscard]] + static constexpr node_type make_init_node(vertex_type origin) + { + return { + {NodeFactory{}.make_init_node(std::move(origin))}, + std::nullopt + }; + } + + template Edge> + [[nodiscard]] + static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) + { + return { + {NodeFactory{}.make_successor_node(current, edge)}, + node::vertex(current) + }; + } + }; + template < class View, concepts::ranked_node Node = CommonRankedNode>, edge::weight_t>>, diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp index 349304f2e..bb9a1969e 100644 --- a/tests/graph/UniformCostSearch.cpp +++ b/tests/graph/UniformCostSearch.cpp @@ -63,7 +63,7 @@ TEST_CASE("ucs::BasicTraverser visits all reachable vertices.", "[graph][graph:: {{{"5", 2}, {"6", 3}, {"2", 7}}, "3"}, {{{"2", 4}}, "6"}, {{{"2", 1}, {"3", 2}, {"6", 5}, {"5", 4}}, "1"}, - {{{"7", 1}, {"4", 4}, {"9", 1}}, "8"}, + {{{"7", 1}, {"4", 4}, {"9", 1}}, "8"} })); sg::ucs::BasicTraverser traverser{graph, origin}; @@ -74,3 +74,24 @@ TEST_CASE("ucs::BasicTraverser visits all reachable vertices.", "[graph][graph:: REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); } + +TEST_CASE("ucs::BasicTraverser node can be decorated with PredecessorNodeDecorator.", "[graph][graph::ucs]") +{ + using DecoratedNode = sg::PredecessorNodeDecorator<::Node>; + + const auto& [expected, origin] = GENERATE( + (table, std::string>)({ + {{{{"5", 2}, "3"}, {{"6", 3}, "3"}, {{"2", 7}, "6"}}, "3"}, + {{{{"2", 4}, "6"}}, "6"}, + //{{{{"2", 1}, "1"}, {{"3", 2}, "1"}, {{"6", 5}, "2"}, {{"5", 4}, "3"}}, "1"}, // non-deterministic as 6 may have the predecessor 2 or 3 + {{{{"7", 1}, "8"}, {{"4", 4}, "7"}, {{"9", 1}, "8"}}, "8"} + })); + + sg::ucs::BasicTraverser traverser{graph, origin}; + STATIC_CHECK(std::ranges::input_range); + + std::vector nodes{}; + std::ranges::copy(traverser, std::back_inserter(nodes)); + + REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); +} From fb5c6a066ea9cd9b231736ef90a669873b651e20 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 18:52:10 +0200 Subject: [PATCH 128/256] feat: make PredecessorNodeDecorator formattable --- include/Simple-Utility/graph/Node.hpp | 93 ++++++++++++++++++++------- tests/graph/Node.cpp | 8 +++ 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 729db4d2d..d96c8f8d7 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -235,43 +235,86 @@ namespace sl::graph #include -template - requires sl::concepts::formattable, Char> -struct std::formatter // NOLINT(cert-dcl58-cpp) +namespace sl::graph { - static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + template + requires sl::concepts::formattable, Char> + class NodeFormatter { - return ctx.begin(); - } + public: + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } - template - auto format(const Node& node, FormatContext& fc) const + template + static auto format(const Node& node, FormatContext& ctx) + { + return std::format_to(ctx.out(), "vertex: {}", node::vertex(node)); + } + }; + + template + requires sl::concepts::formattable, Char> + && sl::concepts::formattable, Char> + class NodeFormatter { - return std::format_to(fc.out(), "{}vertex: {}{}", "{", sl::graph::node::vertex(node), "}"); - } -}; + public: + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } -template - requires sl::concepts::formattable, Char> - && sl::concepts::formattable, Char> -struct std::formatter // NOLINT(cert-dcl58-cpp) + template + static auto format(const Node& node, FormatContext& ctx) + { + return std::format_to(ctx.out(), "vertex: {}, rank: {}", node::vertex(node), node::rank(node)); + } + }; + + template + class NodeFormatter, Char> + { + public: + constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return m_Formatter.parse(ctx); + } + + template + auto format(const PredecessorNodeDecorator& node, FormatContext& ctx) const + { + return std::format_to( + m_Formatter.format(node, ctx), + ", predecessor: {}", + node.predecessor ? std::format("{}", *node.predecessor) : "null"); + } + + private: + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFormatter m_Formatter{}; + }; +} + + +template +struct std::formatter // NOLINT(cert-dcl58-cpp) { - static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept +public: + constexpr auto parse(std::basic_format_parse_context& ctx) { - return ctx.begin(); + return m_Formatter.parse(ctx); } template - auto format(const Node& node, FormatContext& fc) const + auto format(const Node& node, FormatContext& ctx) const { - return std::format_to( - fc.out(), - "{}vertex: {}, rank: {}{}", - "{", - sl::graph::node::vertex(node), - sl::graph::node::rank(node), - "}"); + auto out = std::format_to(ctx.out(), "{}", "{"); + out = m_Formatter.format(node, ctx); + return std::format_to(out, "{}", "}"); } + +private: + SL_UTILITY_NO_UNIQUE_ADDRESS sl::graph::NodeFormatter m_Formatter{}; }; #endif diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 24c92fa69..eea830369 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -278,4 +278,12 @@ TEST_CASE("ranked_node types can be formatted.", "[graph][graph::node]") REQUIRE("{vertex: Hello, World!, rank: 42}" == std::format("{}", TestType{.vertex = "Hello, World!", .rank = 42})); } +TEST_CASE("decorated ranked_node types can be formatted.", "[graph][graph::node]") +{ + using TestType = sg::PredecessorNodeDecorator>; + + REQUIRE("{vertex: 42, rank: 1337, predecessor: 41}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, "41"})); + REQUIRE("{vertex: 42, rank: 1337, predecessor: null}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, std::nullopt})); +} + #endif From afe9ada862d4a875f7b96704527295625355c548 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 18:54:54 +0200 Subject: [PATCH 129/256] refactor: utilize CommonNodeFactory --- .../Simple-Utility/graph/DepthFirstSearch.hpp | 22 +---- include/Simple-Utility/graph/NodeFactory.hpp | 81 +++++++++++++++++++ .../graph/UniformCostSearch.hpp | 48 +---------- 3 files changed, 84 insertions(+), 67 deletions(-) diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index 4b17c1940..ddede1766 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -15,26 +15,8 @@ namespace sl::graph::dfs { template - struct NodeFactory; - - template - struct NodeFactory> - { - using node_type = CommonBasicNode; - using vertex_type = node::vertex_t; - - static constexpr node_type make_init_node(vertex_type origin) - { - return node_type{.vertex = std::move(origin)}; - } - - template Edge> - [[nodiscard]] - static constexpr node_type make_successor_node([[maybe_unused]] const node_type& current, const Edge& edge) - { - return node_type{.vertex = edge::destination(edge)}; - } - }; + requires (!concepts::ranked_node) + using NodeFactory = CommonNodeFactory; template < class View, diff --git a/include/Simple-Utility/graph/NodeFactory.hpp b/include/Simple-Utility/graph/NodeFactory.hpp index 4b8fa1ca0..65b900625 100644 --- a/include/Simple-Utility/graph/NodeFactory.hpp +++ b/include/Simple-Utility/graph/NodeFactory.hpp @@ -25,4 +25,85 @@ namespace sl::graph::concepts }; } +namespace sl::graph +{ + template + class CommonNodeFactory + { + public: + using node_type = Node; + using vertex_type = node::vertex_t; + + [[nodiscard]] + static constexpr node_type make_init_node(vertex_type origin) + { + return node_type{.vertex = std::move(origin)}; + } + + template Edge> + [[nodiscard]] + static constexpr node_type make_successor_node([[maybe_unused]] const node_type& current, const Edge& edge) + { + return node_type{.vertex = edge::destination(edge)}; + } + }; + + template + class CommonNodeFactory + { + public: + using node_type = Node; + using vertex_type = node::vertex_t; + + [[nodiscard]] + static constexpr node_type make_init_node(vertex_type origin) + { + return node_type{.vertex = std::move(origin), .rank = 0}; + } + + template Edge> + [[nodiscard]] + static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) + { + return node_type{ + .vertex = edge::destination(edge), + .rank = node::rank(current) + edge::weight(edge) + }; + } + }; + + template + class CommonNodeFactory> + : public CommonNodeFactory + { + private: + using Super = CommonNodeFactory; + + public: + using node_type = PredecessorNodeDecorator; + using vertex_type = node::vertex_t; + + using Super::Super; + + [[nodiscard]] + constexpr node_type make_init_node(vertex_type origin) + { + return { + {static_cast(*this).make_init_node(std::move(origin))}, + std::nullopt + }; + } + + template Edge> + [[nodiscard]] + constexpr node_type make_successor_node(const node_type& current, const Edge& edge) + { + return { + {static_cast(*this).make_successor_node(current, edge)}, + node::vertex(current) + }; + } + }; +} + #endif diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp index b15dac3a1..34240a9b0 100644 --- a/include/Simple-Utility/graph/UniformCostSearch.hpp +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -15,53 +15,7 @@ namespace sl::graph::ucs { template - struct NodeFactory - { - using node_type = Node; - using vertex_type = node::vertex_t; - - [[nodiscard]] - static constexpr node_type make_init_node(vertex_type origin) - { - return node_type{.vertex = std::move(origin), .rank = 0}; - } - - template Edge> - [[nodiscard]] - static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) - { - return node_type{ - .vertex = edge::destination(edge), - .rank = node::rank(current) + edge::weight(edge) - }; - } - }; - - template - struct NodeFactory> - { - using node_type = PredecessorNodeDecorator; - using vertex_type = node::vertex_t; - - [[nodiscard]] - static constexpr node_type make_init_node(vertex_type origin) - { - return { - {NodeFactory{}.make_init_node(std::move(origin))}, - std::nullopt - }; - } - - template Edge> - [[nodiscard]] - static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) - { - return { - {NodeFactory{}.make_successor_node(current, edge)}, - node::vertex(current) - }; - } - }; + using NodeFactory = CommonNodeFactory; template < class View, From 57d33d72046d01ae6f89f059d4609b2aab1ee557 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 20:59:21 +0200 Subject: [PATCH 130/256] refactor: move formatters into Formatter.hpp --- include/Simple-Utility/graph/Edge.hpp | 45 ------- include/Simple-Utility/graph/Formatter.hpp | 141 +++++++++++++++++++++ include/Simple-Utility/graph/Node.hpp | 88 ------------- tests/graph/CMakeLists.txt | 1 + tests/graph/Defines.hpp | 1 + tests/graph/Edge.cpp | 18 --- tests/graph/Formatter.cpp | 45 +++++++ tests/graph/Node.cpp | 26 ---- 8 files changed, 188 insertions(+), 177 deletions(-) create mode 100644 include/Simple-Utility/graph/Formatter.hpp create mode 100644 tests/graph/Formatter.cpp diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index a1101c4e5..a413b55db 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -230,49 +230,4 @@ namespace sl::graph }; } -#ifdef SL_UTILITY_HAS_STD_FORMAT - -#include - -template - requires sl::concepts::formattable, Char> -struct std::formatter // NOLINT(cert-dcl58-cpp) -{ - static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept - { - return ctx.begin(); - } - - template - auto format(const Edge& edge, FormatContext& ctx) const - { - return std::format_to(ctx.out(), "{}destination: {}{}", "{", sl::graph::edge::destination(edge), "}"); - } -}; - -template - requires sl::concepts::formattable, Char> - && sl::concepts::formattable, Char> -struct std::formatter // NOLINT(cert-dcl58-cpp) -{ - static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept - { - return ctx.begin(); - } - - template - auto format(const Edge& edge, FormatContext& ctx) const - { - return std::format_to( - ctx.out(), - "{}destination: {}, weight: {}{}", - "{", - sl::graph::edge::destination(edge), - sl::graph::edge::weight(edge), - "}"); - } -}; - -#endif - #endif diff --git a/include/Simple-Utility/graph/Formatter.hpp b/include/Simple-Utility/graph/Formatter.hpp new file mode 100644 index 000000000..2a3fdc971 --- /dev/null +++ b/include/Simple-Utility/graph/Formatter.hpp @@ -0,0 +1,141 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_FORMATTER_HPP +#define SIMPLE_UTILITY_GRAPH_FORMATTER_HPP + +#pragma once + +#include "Simple-Utility/Config.hpp" +#include "Simple-Utility/graph/Edge.hpp" +#include "Simple-Utility/graph/Node.hpp" + +#ifdef SL_UTILITY_HAS_STD_FORMAT + +#include + +namespace sl::graph +{ + template + requires sl::concepts::formattable, Char> + class NodeFormatter + { + public: + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } + + template + static auto format(const Node& node, FormatContext& ctx) + { + return std::format_to(ctx.out(), "vertex: {}", node::vertex(node)); + } + }; + + template + requires sl::concepts::formattable, Char> + && sl::concepts::formattable, Char> + class NodeFormatter + { + public: + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } + + template + static auto format(const Node& node, FormatContext& ctx) + { + return std::format_to(ctx.out(), "vertex: {}, rank: {}", node::vertex(node), node::rank(node)); + } + }; + + template + class NodeFormatter, Char> + { + public: + constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return m_Formatter.parse(ctx); + } + + template + auto format(const PredecessorNodeDecorator& node, FormatContext& ctx) const + { + return std::format_to( + m_Formatter.format(node, ctx), + ", predecessor: {}", + node.predecessor ? std::format("{}", *node.predecessor) : "null"); + } + + private: + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFormatter m_Formatter{}; + }; +} + +template +struct std::formatter // NOLINT(cert-dcl58-cpp) +{ +public: + constexpr auto parse(std::basic_format_parse_context& ctx) + { + return m_Formatter.parse(ctx); + } + + template + auto format(const Node& node, FormatContext& ctx) const + { + auto out = std::format_to(ctx.out(), "{}", "{"); + out = m_Formatter.format(node, ctx); + return std::format_to(out, "{}", "}"); + } + +private: + SL_UTILITY_NO_UNIQUE_ADDRESS sl::graph::NodeFormatter m_Formatter{}; +}; + +template + requires sl::concepts::formattable, Char> +struct std::formatter // NOLINT(cert-dcl58-cpp) +{ + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } + + template + auto format(const Edge& edge, FormatContext& ctx) const + { + return std::format_to(ctx.out(), "{}destination: {}{}", "{", sl::graph::edge::destination(edge), "}"); + } +}; + +template + requires sl::concepts::formattable, Char> + && sl::concepts::formattable, Char> +struct std::formatter // NOLINT(cert-dcl58-cpp) +{ + static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return ctx.begin(); + } + + template + auto format(const Edge& edge, FormatContext& ctx) const + { + return std::format_to( + ctx.out(), + "{}destination: {}, weight: {}{}", + "{", + sl::graph::edge::destination(edge), + sl::graph::edge::weight(edge), + "}"); + } +}; + +#endif + +#endif diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index d96c8f8d7..0991bdcc6 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -231,92 +231,4 @@ namespace sl::graph }; } -#ifdef SL_UTILITY_HAS_STD_FORMAT - -#include - -namespace sl::graph -{ - template - requires sl::concepts::formattable, Char> - class NodeFormatter - { - public: - static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept - { - return ctx.begin(); - } - - template - static auto format(const Node& node, FormatContext& ctx) - { - return std::format_to(ctx.out(), "vertex: {}", node::vertex(node)); - } - }; - - template - requires sl::concepts::formattable, Char> - && sl::concepts::formattable, Char> - class NodeFormatter - { - public: - static constexpr auto parse(std::basic_format_parse_context& ctx) noexcept - { - return ctx.begin(); - } - - template - static auto format(const Node& node, FormatContext& ctx) - { - return std::format_to(ctx.out(), "vertex: {}, rank: {}", node::vertex(node), node::rank(node)); - } - }; - - template - class NodeFormatter, Char> - { - public: - constexpr auto parse(std::basic_format_parse_context& ctx) noexcept - { - return m_Formatter.parse(ctx); - } - - template - auto format(const PredecessorNodeDecorator& node, FormatContext& ctx) const - { - return std::format_to( - m_Formatter.format(node, ctx), - ", predecessor: {}", - node.predecessor ? std::format("{}", *node.predecessor) : "null"); - } - - private: - SL_UTILITY_NO_UNIQUE_ADDRESS NodeFormatter m_Formatter{}; - }; -} - - -template -struct std::formatter // NOLINT(cert-dcl58-cpp) -{ -public: - constexpr auto parse(std::basic_format_parse_context& ctx) - { - return m_Formatter.parse(ctx); - } - - template - auto format(const Node& node, FormatContext& ctx) const - { - auto out = std::format_to(ctx.out(), "{}", "{"); - out = m_Formatter.format(node, ctx); - return std::format_to(out, "{}", "}"); - } - -private: - SL_UTILITY_NO_UNIQUE_ADDRESS sl::graph::NodeFormatter m_Formatter{}; -}; - -#endif - #endif diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index c17e04040..7e9fdd1d5 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources( "Common.cpp" "DepthFirstSearch.cpp" "Edge.cpp" + "Formatter.cpp" "Node.cpp" "NodeFactory.cpp" "Queue.cpp" diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 63a29d74e..c79d98101 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -10,6 +10,7 @@ #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Edge.hpp" +#include "Simple-Utility/graph/Formatter.hpp" #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Tracker.hpp" #include "Simple-Utility/graph/View.hpp" diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 6747ce587..406e38145 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -231,21 +231,3 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::edge_for); } - -#ifdef SL_UTILITY_HAS_STD_FORMAT - -TEST_CASE("edge types can be formatted.", "[graph][graph::edge]") -{ - using TestType = GenericBasicEdge; - - REQUIRE("{destination: Hello, World!}" == std::format("{}", TestType{.destination = "Hello, World!"})); -} - -TEST_CASE("weighted_edge types can be formatted.", "[graph][graph::edge]") -{ - using TestType = GenericWeightedEdge; - - REQUIRE("{destination: Hello, World!, weight: 42}" == std::format("{}", TestType{.destination = "Hello, World!", .weight = 42})); -} - -#endif diff --git a/tests/graph/Formatter.cpp b/tests/graph/Formatter.cpp new file mode 100644 index 000000000..8edacf911 --- /dev/null +++ b/tests/graph/Formatter.cpp @@ -0,0 +1,45 @@ +#include "Simple-Utility/graph/Formatter.hpp" + +#include + +#include "Defines.hpp" + +#ifdef SL_UTILITY_HAS_STD_FORMAT + +TEST_CASE("node types can be formatted.", "[graph][graph::node]") +{ + using TestType = GenericBasicNode; + + REQUIRE("{vertex: Hello, World!}" == std::format("{}", TestType{.vertex = "Hello, World!"})); +} + +TEST_CASE("ranked_node types can be formatted.", "[graph][graph::node]") +{ + using TestType = GenericRankedNode; + + REQUIRE("{vertex: Hello, World!, rank: 42}" == std::format("{}", TestType{.vertex = "Hello, World!", .rank = 42})); +} + +TEST_CASE("decorated ranked_node types can be formatted.", "[graph][graph::node]") +{ + using TestType = sg::PredecessorNodeDecorator>; + + REQUIRE("{vertex: 42, rank: 1337, predecessor: 41}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, "41"})); + REQUIRE("{vertex: 42, rank: 1337, predecessor: null}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, std::nullopt})); +} + +TEST_CASE("edge types can be formatted.", "[graph][graph::edge]") +{ + using TestType = GenericBasicEdge; + + REQUIRE("{destination: Hello, World!}" == std::format("{}", TestType{.destination = "Hello, World!"})); +} + +TEST_CASE("weighted_edge types can be formatted.", "[graph][graph::edge]") +{ + using TestType = GenericWeightedEdge; + + REQUIRE("{destination: Hello, World!, weight: 42}" == std::format("{}", TestType{.destination = "Hello, World!", .weight = 42})); +} + +#endif \ No newline at end of file diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index eea830369..0a821a33a 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -261,29 +261,3 @@ TEST_CASE( STATIC_REQUIRE(std::same_as::rank_type>); STATIC_REQUIRE(std::same_as>); } - -#ifdef SL_UTILITY_HAS_STD_FORMAT - -TEST_CASE("node types can be formatted.", "[graph][graph::node]") -{ - using TestType = GenericBasicNode; - - REQUIRE("{vertex: Hello, World!}" == std::format("{}", TestType{.vertex = "Hello, World!"})); -} - -TEST_CASE("ranked_node types can be formatted.", "[graph][graph::node]") -{ - using TestType = GenericRankedNode; - - REQUIRE("{vertex: Hello, World!, rank: 42}" == std::format("{}", TestType{.vertex = "Hello, World!", .rank = 42})); -} - -TEST_CASE("decorated ranked_node types can be formatted.", "[graph][graph::node]") -{ - using TestType = sg::PredecessorNodeDecorator>; - - REQUIRE("{vertex: 42, rank: 1337, predecessor: 41}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, "41"})); - REQUIRE("{vertex: 42, rank: 1337, predecessor: null}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, std::nullopt})); -} - -#endif From 0acbbf59946586132465fe32636d4aa9740b28b5 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 21:12:52 +0200 Subject: [PATCH 131/256] fix: please clang --- tests/graph/Queue.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/graph/Queue.cpp b/tests/graph/Queue.cpp index 6e0c67607..e89857008 100644 --- a/tests/graph/Queue.cpp +++ b/tests/graph/Queue.cpp @@ -15,6 +15,8 @@ #include "Defines.hpp" +#include + // ReSharper disable CppDeclaratorNeverUsed namespace @@ -233,7 +235,7 @@ TEST_CASE("graph::queue::CommonStack follows the queue protocol.", "[graph][grap SECTION("When a single node is inserted.") { - sg::queue::insert(queue, std::views::single(node)); + sg::queue::insert(queue, std::array{node}); } SECTION("When multiple nodes are inserted.") @@ -243,7 +245,7 @@ TEST_CASE("graph::queue::CommonStack follows the queue protocol.", "[graph][grap SECTION("When multiple nodes are inserted during multiple insertions.") { - sg::queue::insert(queue, std::views::single(TestNode{41})); + sg::queue::insert(queue, std::array{TestNode{41}}); sg::queue::insert(queue, std::array{TestNode{44}, node}); } @@ -261,7 +263,7 @@ TEST_CASE("graph::queue::CommonQueue follows the queue protocol.", "[graph][grap SECTION("When a single node is inserted.") { - sg::queue::insert(queue, std::views::single(node)); + sg::queue::insert(queue, std::array{node}); } SECTION("When multiple nodes are inserted.") @@ -272,7 +274,7 @@ TEST_CASE("graph::queue::CommonQueue follows the queue protocol.", "[graph][grap SECTION("When multiple nodes are inserted during multiple insertions.") { sg::queue::insert(queue, std::array{node, TestNode{44}}); - sg::queue::insert(queue, std::views::single(TestNode{41})); + sg::queue::insert(queue, std::array{TestNode{41}}); } REQUIRE(!sg::queue::empty(queue)); @@ -289,7 +291,7 @@ TEST_CASE("graph::queue::CommonPriorityQueue follows the queue protocol.", "[gra SECTION("When a single node is inserted.") { - sg::queue::insert(queue, std::views::single(node)); + sg::queue::insert(queue, std::array{node}); } SECTION("When multiple nodes are inserted.") @@ -300,7 +302,7 @@ TEST_CASE("graph::queue::CommonPriorityQueue follows the queue protocol.", "[gra SECTION("When multiple nodes are inserted during multiple insertions.") { sg::queue::insert(queue, std::array{node, TestRankedNode{"V44", 2}}); - sg::queue::insert(queue, std::views::single(TestRankedNode{"V41", 3})); + sg::queue::insert(queue, std::array{TestRankedNode{"V41", 3}}); } REQUIRE(!sg::queue::empty(queue)); From 254b35e7af8ac5929517b25db535c860b2619e1a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 21:35:06 +0200 Subject: [PATCH 132/256] fix: correct expected node type of ucs example view --- tests/graph/UniformCostSearch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp index bb9a1969e..9404394d4 100644 --- a/tests/graph/UniformCostSearch.cpp +++ b/tests/graph/UniformCostSearch.cpp @@ -34,7 +34,7 @@ namespace {"9", {4}} }; - template + template requires sg::concepts::edge_for static std::vector edges(const Node& current) { From 35b9bd398c3cb24d513ace7dcfcdc2853c2b3983 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 21:35:12 +0200 Subject: [PATCH 133/256] fix: please gcc --- include/Simple-Utility/graph/Edge.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index a413b55db..3bb6c654d 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -200,7 +200,11 @@ namespace sl::graph::concepts && edge && std::same_as, node::vertex_t> && (!ranked_node - || weighted_edge && std::convertible_to, node::rank_t>); + || requires(const Edge& edge) + { + requires weighted_edge; + { edge::weight(edge) } -> std::convertible_to>; + }); } namespace sl::graph From 2d68fb0310f0d7270c0cf6914f236c498215e300 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 21:51:04 +0200 Subject: [PATCH 134/256] fix: please gcc-10 --- include/Simple-Utility/graph/NodeFactory.hpp | 22 ++++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/include/Simple-Utility/graph/NodeFactory.hpp b/include/Simple-Utility/graph/NodeFactory.hpp index 65b900625..89f99d73c 100644 --- a/include/Simple-Utility/graph/NodeFactory.hpp +++ b/include/Simple-Utility/graph/NodeFactory.hpp @@ -40,9 +40,11 @@ namespace sl::graph return node_type{.vertex = std::move(origin)}; } - template Edge> [[nodiscard]] - static constexpr node_type make_successor_node([[maybe_unused]] const node_type& current, const Edge& edge) + static constexpr node_type make_successor_node( + [[maybe_unused]] const node_type& current, + const concepts::edge_for auto& edge + ) { return node_type{.vertex = edge::destination(edge)}; } @@ -58,12 +60,11 @@ namespace sl::graph [[nodiscard]] static constexpr node_type make_init_node(vertex_type origin) { - return node_type{.vertex = std::move(origin), .rank = 0}; + return node_type{.vertex = std::move(origin), .rank = {}}; } - template Edge> [[nodiscard]] - static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) + static constexpr node_type make_successor_node(const node_type& current, const concepts::edge_for auto& edge) { return node_type{ .vertex = edge::destination(edge), @@ -88,20 +89,23 @@ namespace sl::graph [[nodiscard]] constexpr node_type make_init_node(vertex_type origin) { - return { + // leave code as-is, because directly returning the temporary results in an ICE on gcc10 + node_type node{ {static_cast(*this).make_init_node(std::move(origin))}, std::nullopt }; + return node; } - template Edge> [[nodiscard]] - constexpr node_type make_successor_node(const node_type& current, const Edge& edge) + constexpr node_type make_successor_node(const node_type& current, const concepts::edge_for auto& edge) { - return { + // leave code as-is, because directly returning the temporary results in an ICE on gcc10 + node_type node{ {static_cast(*this).make_successor_node(current, edge)}, node::vertex(current) }; + return node; } }; } From bb8f5fe82aaf175100ac26acde1f6f717ae1101c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 21:52:29 +0200 Subject: [PATCH 135/256] env: tell cmake that clangs c compiler just works --- CMakeSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 5cff49154..70a4889da 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -30,7 +30,7 @@ "inheritEnvironments": [ "clang_cl_x64_x64" ], "variables": [ { - "name": "Simple-Utility_BUILD_TESTS", + "name": "CMAKE_C_COMPILER_WORKS", "value": "True", "type": "BOOL" } From 40b80fda282f66ef8efbd5875aff90dc532c69c4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 21:52:41 +0200 Subject: [PATCH 136/256] env: add x64-Debug-v142 config --- CMakeSettings.json | 300 ++++++++++++++++++++++++--------------------- 1 file changed, 159 insertions(+), 141 deletions(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 70a4889da..bc43b563e 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -1,144 +1,162 @@ { - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "variables": [ - { - "name": "CMAKE_EXPORT_COMPILE_COMMANDS", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "clang-x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "clang_cl_x64_x64" ], - "variables": [ - { + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [ + { + "name": "CMAKE_EXPORT_COMPILE_COMMANDS", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "clang-x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "variables": [ + { "name": "CMAKE_C_COMPILER_WORKS", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "Linux-GCC-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "cmakeExecutable": "cmake", - "remoteCopySourcesExclusionList": [ ".vs", ".git", "out" ], - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "linux_x64" ], - "remoteMachineName": "${defaultRemoteMachineName}", - "remoteCMakeListsRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/src", - "remoteBuildRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/build/${name}", - "remoteInstallRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/install/${name}", - "remoteCopySources": true, - "rsyncCommandArgs": "-t --delete --delete-excluded", - "remoteCopyBuildOutput": false, - "remoteCopySourcesMethod": "rsync", - "variables": [ - { - "name": "CATCH_BUILD_TESTING", - "value": "False", - "type": "BOOL" - } - ] - }, - { - "name": "Linux-Clang-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "cmakeExecutable": "cmake", - "remoteCopySourcesExclusionList": [ ".vs", ".git", "out" ], - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "linux_clang_x64" ], - "variables": [ - { - "name": "CATCH_BUILD_TESTING", - "value": "False", - "type": "BOOL" - }, - { - "name": "CMAKE_C_COMPILER_WORKS", - "value": "True", - "type": "BOOL" - } - ], - "remoteMachineName": "${defaultRemoteMachineName}", - "remoteCMakeListsRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/src", - "remoteBuildRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/build/${name}", - "remoteInstallRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/install/${name}", - "remoteCopySources": true, - "rsyncCommandArgs": "-t --delete --delete-excluded", - "remoteCopyBuildOutput": false, - "remoteCopySourcesMethod": "rsync" - }, - { - "name": "GenerateDocs", - "generator": "Ninja", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "buildCommandArgs": "GenerateDocs", - "ctestCommandArgs": "", - "inheritEnvironments": [ "msvc_x64_x64" ], - "variables": [ - { - "name": "SIMPLE_UTILITY_GEN_DOCS_ENABLED", - "value": "True", - "type": "BOOL" - } - ] - }, - { - "name": "Linux-GCC-12-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "cmakeExecutable": "cmake", - "remoteCopySourcesExclusionList": [ ".vs", ".git", "out" ], - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "linux_x64" ], - "variables": [ - { - "name": "CATCH_BUILD_TESTING", - "value": "False", - "type": "BOOL" - }, - { - "name": "CMAKE_CXX_COMPILER", - "value": "/usr/bin/g++-12", - "type": "FILEPATH" - } - ], - "remoteMachineName": "${defaultRemoteMachineName}", - "remoteCMakeListsRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/src", - "remoteBuildRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/build/${name}", - "remoteInstallRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/install/${name}", - "remoteCopySources": true, - "rsyncCommandArgs": "-t --delete --delete-excluded", - "remoteCopyBuildOutput": false, - "remoteCopySourcesMethod": "rsync" - } - ] + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "Linux-GCC-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "cmakeExecutable": "cmake", + "remoteCopySourcesExclusionList": [ ".vs", ".git", "out" ], + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "linux_x64" ], + "remoteMachineName": "${defaultRemoteMachineName}", + "remoteCMakeListsRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/src", + "remoteBuildRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/build/${name}", + "remoteInstallRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/install/${name}", + "remoteCopySources": true, + "rsyncCommandArgs": "-t --delete --delete-excluded", + "remoteCopyBuildOutput": false, + "remoteCopySourcesMethod": "rsync", + "variables": [ + { + "name": "CATCH_BUILD_TESTING", + "value": "False", + "type": "BOOL" + } + ] + }, + { + "name": "Linux-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "cmakeExecutable": "cmake", + "remoteCopySourcesExclusionList": [ ".vs", ".git", "out" ], + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "linux_clang_x64" ], + "variables": [ + { + "name": "CATCH_BUILD_TESTING", + "value": "False", + "type": "BOOL" + }, + { + "name": "CMAKE_C_COMPILER_WORKS", + "value": "True", + "type": "BOOL" + } + ], + "remoteMachineName": "${defaultRemoteMachineName}", + "remoteCMakeListsRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/src", + "remoteBuildRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/build/${name}", + "remoteInstallRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/install/${name}", + "remoteCopySources": true, + "rsyncCommandArgs": "-t --delete --delete-excluded", + "remoteCopyBuildOutput": false, + "remoteCopySourcesMethod": "rsync" + }, + { + "name": "GenerateDocs", + "generator": "Ninja", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "buildCommandArgs": "GenerateDocs", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [ + { + "name": "SIMPLE_UTILITY_GEN_DOCS_ENABLED", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "Linux-GCC-12-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "cmakeExecutable": "cmake", + "remoteCopySourcesExclusionList": [ ".vs", ".git", "out" ], + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "linux_x64" ], + "variables": [ + { + "name": "CATCH_BUILD_TESTING", + "value": "False", + "type": "BOOL" + }, + { + "name": "CMAKE_CXX_COMPILER", + "value": "/usr/bin/g++-12", + "type": "FILEPATH" + } + ], + "remoteMachineName": "${defaultRemoteMachineName}", + "remoteCMakeListsRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/src", + "remoteBuildRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/build/${name}", + "remoteInstallRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/install/${name}", + "remoteCopySources": true, + "rsyncCommandArgs": "-t --delete --delete-excluded", + "remoteCopyBuildOutput": false, + "remoteCopySourcesMethod": "rsync" + }, + { + "name": "x64-Debug-v142", + "generator": "Visual Studio 17 2022 Win64", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "-Tv142", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [ + { + "name": "CMAKE_EXPORT_COMPILE_COMMANDS", + "value": "True", + "type": "BOOL" + } + ] + } + ] } \ No newline at end of file From 22e2436349ea58b61525893c85d461adb2cbe72e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 7 Sep 2023 22:19:45 +0200 Subject: [PATCH 137/256] fix: please msvc v142 --- include/Simple-Utility/graph/NodeFactory.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/Simple-Utility/graph/NodeFactory.hpp b/include/Simple-Utility/graph/NodeFactory.hpp index 89f99d73c..2bcc4971b 100644 --- a/include/Simple-Utility/graph/NodeFactory.hpp +++ b/include/Simple-Utility/graph/NodeFactory.hpp @@ -40,11 +40,9 @@ namespace sl::graph return node_type{.vertex = std::move(origin)}; } + template Edge> [[nodiscard]] - static constexpr node_type make_successor_node( - [[maybe_unused]] const node_type& current, - const concepts::edge_for auto& edge - ) + static constexpr node_type make_successor_node([[maybe_unused]] const node_type& current,const Edge& edge) { return node_type{.vertex = edge::destination(edge)}; } @@ -63,8 +61,9 @@ namespace sl::graph return node_type{.vertex = std::move(origin), .rank = {}}; } + template Edge> [[nodiscard]] - static constexpr node_type make_successor_node(const node_type& current, const concepts::edge_for auto& edge) + static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) { return node_type{ .vertex = edge::destination(edge), @@ -97,8 +96,9 @@ namespace sl::graph return node; } + template Edge> [[nodiscard]] - constexpr node_type make_successor_node(const node_type& current, const concepts::edge_for auto& edge) + constexpr node_type make_successor_node(const node_type& current, const Edge& edge) { // leave code as-is, because directly returning the temporary results in an ICE on gcc10 node_type node{ From 4a9d516b8ae1f22ceb4000394223c3b8812913ee Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 8 Sep 2023 03:36:38 +0200 Subject: [PATCH 138/256] ci: gather gcov artifacts --- .github/workflows/coverage.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 3e8e97d15..b1b9160ca 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -23,7 +23,8 @@ jobs: - name: Compile tests env: - CXXFLAGS: "--coverage -fno-inline -fprofile-abs-path" + LDFLAGS: "-fprofile-arcs" + CXXFLAGS: "-g -O0 --coverage -fno-inline -fprofile-abs-path -fkeep-inline-functions -fkeep-static-functions" CC: gcc CXX: g++ run: | @@ -41,6 +42,12 @@ jobs: lcov --capture -d ${{env.TESTS_DIR}} -o ${{env.COVERAGE_FILE}} --include "$PWD/include/*" lcov -l ${{env.COVERAGE_FILE}} + - name: Upload gcov artifacts + uses: actions/upload-artifact@v3 + with: + name: gcov-reports + path: ./*.gcov + - name: Upload artifacts uses: actions/upload-artifact@v3 with: From 33467a3cd7542a2c2d45a95f1f7d1adaae1ad513 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 8 Sep 2023 03:46:57 +0200 Subject: [PATCH 139/256] ci: fix gcov file path --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b1b9160ca..8fa81b741 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -46,7 +46,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: gcov-reports - path: ./*.gcov + path: ${{env.TESTS_DIR}}/*.gcov - name: Upload artifacts uses: actions/upload-artifact@v3 From 9e261f5f87c5bc1a8684068e3167f332e2a4da51 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 8 Sep 2023 04:06:30 +0200 Subject: [PATCH 140/256] fix: remove unnecessary operator == from Traverser::Iterator --- include/Simple-Utility/graph/Traverse.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index ee862e6da..b604ae3bd 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -262,10 +262,7 @@ namespace sl::graph } [[nodiscard]] - constexpr bool operator==(const Iterator&) const = default; - - [[nodiscard]] - constexpr bool operator==([[maybe_unused]] const Sentinel) const + constexpr bool operator==([[maybe_unused]] const Sentinel) const noexcept { return !m_Value; } From edb998743db5cb5600b28b5cc78682520b1e65c8 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 00:39:17 +0200 Subject: [PATCH 141/256] ci: limit workflow run --- .github/workflows/coverage.yml | 15 ++++++++++++++- .github/workflows/run_tests.yml | 13 +++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8fa81b741..11b140952 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,7 +6,20 @@ env: COVERAGE_FILE: coverage.info COVERAGE_ARTIFACT_NAME: coverage-report -on: [push, pull_request] +on: + push: + branches: + - master + - development + paths-ignore: + - 'README.md' + - 'docs/' + pull_request: + branches: + - '**' + paths-ignore: + - 'README.md' + - 'docs/' jobs: create-coverage-report: diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 88ecae715..454e4b6fb 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -6,9 +6,18 @@ env: on: push: - branches: [master, development] + branches: + - master + - development + paths-ignore: + - 'README.md' + - 'docs/' pull_request: - branches: [master, development] + branches: + - '**' + paths-ignore: + - 'README.md' + - 'docs/' jobs: ubuntu-22_04: From 43ef257ba0562a5cda082f339ef62d0d425951a9 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 00:39:34 +0200 Subject: [PATCH 142/256] ci: add html report --- .github/workflows/coverage.yml | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 11b140952..bd9c50c48 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -52,14 +52,15 @@ jobs: - name: Collect data run: | sudo apt-get install lcov -y - lcov --capture -d ${{env.TESTS_DIR}} -o ${{env.COVERAGE_FILE}} --include "$PWD/include/*" - lcov -l ${{env.COVERAGE_FILE}} + lcov --capture -d ${{env.TESTS_DIR}} -o ${{env.COVERAGE_FILE}} --include "$PWD/include/*" --branch-coverage - - name: Upload gcov artifacts + - name: Upload raw coverage artifacts uses: actions/upload-artifact@v3 with: - name: gcov-reports - path: ${{env.TESTS_DIR}}/*.gcov + name: raw-coverage-reports + path: | + ${{env.TESTS_DIR}}/*.gcno + ${{env.TESTS_DIR}}/*.gcda - name: Upload artifacts uses: actions/upload-artifact@v3 @@ -67,6 +68,27 @@ jobs: name: ${{env.COVERAGE_ARTIFACT_NAME}} path: ${{env.COVERAGE_FILE}} + generate-html-report: + needs: create-coverage-report + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v3 + with: + name: ${{env.COVERAGE_ARTIFACT_NAME}} + + - name: Generate html + run: | + sudo apt-get install genhtml -y + genhtml --title "Simple-Utility Coverage Report" --prefix "$PWD/include/" --output-directory html ${{env.COVERAGE_FILE}} + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: Simple-Utility-HTML-Report + path: html/ + codacy-report: needs: create-coverage-report runs-on: ubuntu-latest @@ -81,7 +103,7 @@ jobs: uses: codacy/codacy-coverage-reporter-action@v1 with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - coverage-reports: ${{ env.COVERAGE_FILE }} + coverage-reports: ${{env.COVERAGE_FILE}} codecov-report: needs: create-coverage-report From 22c2ee3828c9166db1fadccd7b4dc52181fe0828 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 00:51:02 +0200 Subject: [PATCH 143/256] ci: remove branch-coverage flag from lcov --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bd9c50c48..eec7112e9 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -52,7 +52,7 @@ jobs: - name: Collect data run: | sudo apt-get install lcov -y - lcov --capture -d ${{env.TESTS_DIR}} -o ${{env.COVERAGE_FILE}} --include "$PWD/include/*" --branch-coverage + lcov --capture -d ${{env.TESTS_DIR}} -o ${{env.COVERAGE_FILE}} --include "$PWD/include/*" - name: Upload raw coverage artifacts uses: actions/upload-artifact@v3 From 5249090e8ad8b106e873dfc2beb6bf37f6c3385d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 01:01:01 +0200 Subject: [PATCH 144/256] ci: tweak raw file upload --- .github/workflows/coverage.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index eec7112e9..8019a687a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -58,9 +58,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: raw-coverage-reports - path: | - ${{env.TESTS_DIR}}/*.gcno - ${{env.TESTS_DIR}}/*.gcda + path: Simple-Utility-Tests.dir/ - name: Upload artifacts uses: actions/upload-artifact@v3 From 06bf29032f353b5554cd6950d2c56da9727ee873 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 01:02:45 +0200 Subject: [PATCH 145/256] ci: fix genrate html step --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8019a687a..0178a5f2f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -78,7 +78,7 @@ jobs: - name: Generate html run: | - sudo apt-get install genhtml -y + sudo apt-get install lcov -y genhtml --title "Simple-Utility Coverage Report" --prefix "$PWD/include/" --output-directory html ${{env.COVERAGE_FILE}} - name: Upload artifacts From 2c9411114c91553f2b1c744e0adbd3bbc84295da Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 01:21:29 +0200 Subject: [PATCH 146/256] ci: fix raw coverage file path --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0178a5f2f..d91f749cc 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -58,7 +58,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: raw-coverage-reports - path: Simple-Utility-Tests.dir/ + path: "${{env.TESTS_DIR}}/Simple-Utility-Tests.dir/" - name: Upload artifacts uses: actions/upload-artifact@v3 From 1b79ac6f49b0cd725998aabd85b121618e3e87fb Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 03:25:22 +0200 Subject: [PATCH 147/256] ci: rework coverage reporting --- .github/workflows/coverage.yml | 64 ++++++++++++++-------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d91f749cc..1f6b02a9d 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -3,7 +3,9 @@ name: coverage env: GXX_VERSION: 12 TESTS_DIR: out/tests - COVERAGE_FILE: coverage.info + COBERTURA_REPORT: cobertura.xml + COVERALLS_REPORT: coveralls.json + HTML_REPORT_DIR: html/ COVERAGE_ARTIFACT_NAME: coverage-report on: @@ -27,9 +29,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install compiler + - name: Install prerequisites run: | sudo apt-get update + sudo apt-get install pip -y + sudo pip install gcovr sudo apt-get install g++-${{ env.GXX_VERSION }} -y sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-${{ env.GXX_VERSION }} ${{ env.GXX_VERSION }}00 --slave /usr/bin/gcc gcc /usr/bin/gcc-${{ env.GXX_VERSION }} sudo update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-${{ env.GXX_VERSION }} ${{ env.GXX_VERSION }}00 @@ -47,45 +51,29 @@ jobs: - name: Run tests env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.TESTS_DIR }} --timeout 30 -C Debug -j4 + run: ctest --test-dir ${{env.TESTS_DIR}} -C Debug -j4 - - name: Collect data + - name: Run gcovr run: | - sudo apt-get install lcov -y - lcov --capture -d ${{env.TESTS_DIR}} -o ${{env.COVERAGE_FILE}} --include "$PWD/include/*" + gcovr -root ${{env.TESTS_DIR}} --filter include/Simple-Utility --keep -j 4 \ + --cobertura ${{env.COBERTURA_REPORT}} --cobertura-pretty \ + --html-nested ${{env.HTML_OUTPUT_DIR}} --html-title "Simple-Utility Coverage Report" \ + --coveralls ${{env.COVERALLS_REPORT}} --coveralls-pretty - - name: Upload raw coverage artifacts + - name: Upload gcov coverage report artifacts uses: actions/upload-artifact@v3 with: - name: raw-coverage-reports - path: "${{env.TESTS_DIR}}/Simple-Utility-Tests.dir/" + name: gcov-files + path: "${{env.TESTS_DIR}}/*.gcov" - - name: Upload artifacts + - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: name: ${{env.COVERAGE_ARTIFACT_NAME}} - path: ${{env.COVERAGE_FILE}} - - generate-html-report: - needs: create-coverage-report - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/download-artifact@v3 - with: - name: ${{env.COVERAGE_ARTIFACT_NAME}} - - - name: Generate html - run: | - sudo apt-get install lcov -y - genhtml --title "Simple-Utility Coverage Report" --prefix "$PWD/include/" --output-directory html ${{env.COVERAGE_FILE}} - - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - name: Simple-Utility-HTML-Report - path: html/ + path: | + ${{env.COBERTURA_REPORT}} + ${{env.COVERALLS_REPORT}} + ${{env.HTML_REPORT_DIR}} codacy-report: needs: create-coverage-report @@ -100,8 +88,8 @@ jobs: - name: Upload coverage to Codacy uses: codacy/codacy-coverage-reporter-action@v1 with: - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - coverage-reports: ${{env.COVERAGE_FILE}} + project-token: ${{secrets.CODACY_PROJECT_TOKEN}} + coverage-reports: ${{env.COBERTURA_REPORT}} codecov-report: needs: create-coverage-report @@ -117,8 +105,8 @@ jobs: uses: codecov/codecov-action@v3 with: name: $GITHUB_REPOSITORY - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ env.COVERAGE_FILE }} + token: ${{secrets.CODECOV_TOKEN}} + files: ${{env.COBERTURA_REPORT}} fail_ci_if_error: true verbose: true @@ -136,6 +124,6 @@ jobs: uses: coverallsapp/github-action@v2 with: github-token: ${{secrets.GITHUB_TOKEN}} - file: ${{env.COVERAGE_FILE}} - format: lcov + file: ${{env.COVERALLS_REPORT}} + format: coveralls fail-on-error: true From 720ecb64ac46bfc8ea7e81a56b905976bb5b4beb Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 03:37:34 +0200 Subject: [PATCH 148/256] ci: fix gcovr arguments --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1f6b02a9d..7ce1c7bcd 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -57,7 +57,7 @@ jobs: run: | gcovr -root ${{env.TESTS_DIR}} --filter include/Simple-Utility --keep -j 4 \ --cobertura ${{env.COBERTURA_REPORT}} --cobertura-pretty \ - --html-nested ${{env.HTML_OUTPUT_DIR}} --html-title "Simple-Utility Coverage Report" \ + --html-nested ${{env.HTML_REPORT_DIR}} --html-title "Simple-Utility Coverage Report" \ --coveralls ${{env.COVERALLS_REPORT}} --coveralls-pretty - name: Upload gcov coverage report artifacts From b78e5ca60931bd92d6b0466443c75c0f25ba1d0d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 03:48:44 +0200 Subject: [PATCH 149/256] ci: fix gcovr arguments #2 --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7ce1c7bcd..0b01df01e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -55,7 +55,7 @@ jobs: - name: Run gcovr run: | - gcovr -root ${{env.TESTS_DIR}} --filter include/Simple-Utility --keep -j 4 \ + gcovr --root ${{env.TESTS_DIR}} --filter include/Simple-Utility --keep -j 4 \ --cobertura ${{env.COBERTURA_REPORT}} --cobertura-pretty \ --html-nested ${{env.HTML_REPORT_DIR}} --html-title "Simple-Utility Coverage Report" \ --coveralls ${{env.COVERALLS_REPORT}} --coveralls-pretty From addbea84c1135d3883ff3bc43ab77dc2f500c68a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 10 Sep 2023 04:25:46 +0200 Subject: [PATCH 150/256] ci: let gcovr exclude several branch types --- .github/workflows/coverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0b01df01e..ece1470c1 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -56,6 +56,7 @@ jobs: - name: Run gcovr run: | gcovr --root ${{env.TESTS_DIR}} --filter include/Simple-Utility --keep -j 4 \ + --exclude-lines-by-pattern "\s*assert\(" --exclude-unreachable-branches --exclude-noncode-lines --exclude-throw-branches \ --cobertura ${{env.COBERTURA_REPORT}} --cobertura-pretty \ --html-nested ${{env.HTML_REPORT_DIR}} --html-title "Simple-Utility Coverage Report" \ --coveralls ${{env.COVERALLS_REPORT}} --coveralls-pretty From f588e161167d1c7accd5de22b563cd7446a097b1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 17:39:03 +0200 Subject: [PATCH 151/256] feat: tracker::Null --- .../graph/mixins/tracker/Null.hpp | 35 +++++++++++++++++++ tests/graph/Tracker.cpp | 4 ++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 include/Simple-Utility/graph/mixins/tracker/Null.hpp diff --git a/include/Simple-Utility/graph/mixins/tracker/Null.hpp b/include/Simple-Utility/graph/mixins/tracker/Null.hpp new file mode 100644 index 000000000..7f98dea48 --- /dev/null +++ b/include/Simple-Utility/graph/mixins/tracker/Null.hpp @@ -0,0 +1,35 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_TRACKER_NULL_HPP +#define SIMPLE_UTILITY_GRAPH_MIXINS_TRACKER_NULL_HPP + +#pragma once + +#include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Tracker.hpp" + +namespace sl::graph::tracker +{ + class Null + { + public: + template + [[nodiscard]] + static constexpr bool set_discovered([[maybe_unused]] const Vertex& vertex) noexcept + { + return true; + } + + template + [[nodiscard]] + static constexpr bool set_visited([[maybe_unused]] const Vertex& vertex) noexcept + { + return true; + } + }; +} + +#endif diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index 50c636edf..173ca0d90 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -12,6 +12,7 @@ #include "Defines.hpp" +#include "Simple-Utility/graph/mixins/tracker/Null.hpp" #include "Simple-Utility/graph/mixins/tracker/std_map.hpp" #include "Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp" @@ -155,7 +156,8 @@ TEMPLATE_TEST_CASE_SIG( (false, TrackerMock, std::string), (true, TrackerMock, int), (true, sg::tracker::CommonHashMap, int), - (true, sg::tracker::CommonMap, int) + (true, sg::tracker::CommonMap, int), + (true, sg::tracker::Null, int) ) { STATIC_REQUIRE(expected == sg::concepts::tracker_for); From 3651f6cee7c8c1d21c8b33946c48886c09205634 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 18:50:42 +0200 Subject: [PATCH 152/256] rework: node concepts and general traverser internals --- include/Simple-Utility/graph/Edge.hpp | 15 - include/Simple-Utility/graph/Node.hpp | 100 ++++-- include/Simple-Utility/graph/NodeFactory.hpp | 113 ------ include/Simple-Utility/graph/Queue.hpp | 10 +- include/Simple-Utility/graph/Traverse.hpp | 331 +++++++++++------- include/Simple-Utility/graph/View.hpp | 2 +- .../graph/mixins/queue/std_queue.hpp | 2 +- .../graph/mixins/queue/std_stack.hpp | 2 +- tests/graph/CMakeLists.txt | 2 +- tests/graph/Defines.hpp | 26 +- tests/graph/Node.cpp | 2 +- tests/graph/NodeFactory.cpp | 27 -- tests/graph/Tracker.cpp | 39 ++- tests/graph/Traverse.cpp | 181 ++++------ tests/graph/View.cpp | 4 +- 15 files changed, 413 insertions(+), 443 deletions(-) delete mode 100644 include/Simple-Utility/graph/NodeFactory.hpp delete mode 100644 tests/graph/NodeFactory.cpp diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index 3bb6c654d..4944373de 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -11,7 +11,6 @@ #include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" -#include "Simple-Utility/graph/Node.hpp" namespace sl::graph::customize { @@ -193,20 +192,6 @@ namespace sl::graph::edge using weight_t = typename traits::weight_type; } -namespace sl::graph::concepts -{ - template - concept edge_for = node - && edge - && std::same_as, node::vertex_t> - && (!ranked_node - || requires(const Edge& edge) - { - requires weighted_edge; - { edge::weight(edge) } -> std::convertible_to>; - }); -} - namespace sl::graph { template diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 0991bdcc6..c26ed8c5e 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -12,6 +12,7 @@ #include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Edge.hpp" #include @@ -37,9 +38,9 @@ namespace sl::graph::detail } template - requires requires(const Node& node) + requires requires { - requires concepts::vertex>; + requires concepts::vertex().vertex)>>; } constexpr auto& vertex(const Node& node, const priority_tag<2>) noexcept { @@ -141,6 +142,12 @@ namespace sl::graph::node template struct traits; + template + using vertex_t = typename traits::vertex_type; + + template + using rank_t = typename traits::rank_type; + template requires concepts::readable_vertex_type struct traits @@ -161,35 +168,35 @@ namespace sl::graph::node namespace sl::graph::concepts { template - concept node = sl::concepts::unqualified - && std::copyable - && std::destructible - && vertex::vertex_type> - && requires(const T& node) - { - // fixes compile error on msvc v142 - // ReSharper disable once CppRedundantTemplateKeyword - { node::vertex(node) } -> std::convertible_to::vertex_type>; - }; - - template - concept ranked_node = node - && rank::rank_type> + concept basic_node = sl::concepts::unqualified + && std::copyable + && std::destructible + && requires { typename node::traits::vertex_type; } + && vertex> && requires(const T& node) { - // fixes compile error on msvc v142 - // ReSharper disable once CppRedundantTemplateKeyword - { node::rank(node) } -> std::convertible_to::rank_type>; + { node::vertex(node) } -> std::convertible_to>; }; -} -namespace sl::graph::node -{ - template - using vertex_t = typename traits::vertex_type; + template + concept ranked_node = basic_node + && requires { typename node::traits::rank_type; } + && rank> + && requires(const Node& node) + { + { node::rank(node) } -> std::convertible_to>; + }; - template - using rank_t = typename traits::rank_type; + template + concept edge_for = basic_node + && edge + && std::same_as, node::vertex_t> + && (!ranked_node + || requires(const Edge& edge) + { + requires weighted_edge; + { edge::weight(edge) } -> std::convertible_to>; + }); } namespace sl::graph @@ -218,7 +225,7 @@ namespace sl::graph friend bool operator==(const CommonRankedNode&, const CommonRankedNode&) = default; }; - template + template struct PredecessorNodeDecorator : public Node { @@ -229,6 +236,45 @@ namespace sl::graph [[nodiscard]] friend bool operator==(const PredecessorNodeDecorator&, const PredecessorNodeDecorator&) = default; }; + + template typename BaseNodeFactory> + class NodeFactoryDecorator; + + template typename BaseNodeFactory> + class NodeFactoryDecorator, BaseNodeFactory> + : private BaseNodeFactory + { + private: + using Super = BaseNodeFactory; + + public: + using node_type = PredecessorNodeDecorator; + using vertex_type = node::vertex_t; + + template + [[nodiscard]] + constexpr node_type operator ()(vertex_type origin, Args&&... args) const + { + // leave code as-is, because directly returning the temporary results in an ICE on gcc10 + node_type node{ + {std::invoke(static_cast(*this), std::move(origin), std::forward(args)...)}, + std::nullopt + }; + return node; + } + + template Edge, typename... Args> + [[nodiscard]] + constexpr node_type operator ()(const node_type& current, const Edge& edge, Args&&... args) const + { + // leave code as-is, because directly returning the temporary results in an ICE on gcc10 + node_type node{ + {std::invoke(static_cast(*this), current, edge, std::forward(args)...)}, + node::vertex(current) + }; + return node; + } + }; } #endif diff --git a/include/Simple-Utility/graph/NodeFactory.hpp b/include/Simple-Utility/graph/NodeFactory.hpp deleted file mode 100644 index 2bcc4971b..000000000 --- a/include/Simple-Utility/graph/NodeFactory.hpp +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright Dominic Koepke 2019 - 2023. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// https://www.boost.org/LICENSE_1_0.txt) - -#ifndef SIMPLE_UTILITY_GRAPH_NODE_FACTORY_HPP -#define SIMPLE_UTILITY_GRAPH_NODE_FACTORY_HPP - -#pragma once - -#include "Simple-Utility/graph/Edge.hpp" -#include "Simple-Utility/graph/Node.hpp" - -namespace sl::graph::concepts -{ - template - concept node_factory_for = sl::concepts::unqualified - && node - && edge_for - && std::destructible - && requires(T& factory, const Node& node, const Edge& edge) - { - { factory.make_init_node(node::vertex(node)) } -> std::convertible_to; - { factory.make_successor_node(node, edge) } -> std::convertible_to; - }; -} - -namespace sl::graph -{ - template - class CommonNodeFactory - { - public: - using node_type = Node; - using vertex_type = node::vertex_t; - - [[nodiscard]] - static constexpr node_type make_init_node(vertex_type origin) - { - return node_type{.vertex = std::move(origin)}; - } - - template Edge> - [[nodiscard]] - static constexpr node_type make_successor_node([[maybe_unused]] const node_type& current,const Edge& edge) - { - return node_type{.vertex = edge::destination(edge)}; - } - }; - - template - class CommonNodeFactory - { - public: - using node_type = Node; - using vertex_type = node::vertex_t; - - [[nodiscard]] - static constexpr node_type make_init_node(vertex_type origin) - { - return node_type{.vertex = std::move(origin), .rank = {}}; - } - - template Edge> - [[nodiscard]] - static constexpr node_type make_successor_node(const node_type& current, const Edge& edge) - { - return node_type{ - .vertex = edge::destination(edge), - .rank = node::rank(current) + edge::weight(edge) - }; - } - }; - - template - class CommonNodeFactory> - : public CommonNodeFactory - { - private: - using Super = CommonNodeFactory; - - public: - using node_type = PredecessorNodeDecorator; - using vertex_type = node::vertex_t; - - using Super::Super; - - [[nodiscard]] - constexpr node_type make_init_node(vertex_type origin) - { - // leave code as-is, because directly returning the temporary results in an ICE on gcc10 - node_type node{ - {static_cast(*this).make_init_node(std::move(origin))}, - std::nullopt - }; - return node; - } - - template Edge> - [[nodiscard]] - constexpr node_type make_successor_node(const node_type& current, const Edge& edge) - { - // leave code as-is, because directly returning the temporary results in an ICE on gcc10 - node_type node{ - {static_cast(*this).make_successor_node(current, edge)}, - node::vertex(current) - }; - return node; - } - }; -} - -#endif diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 8c7b41b5e..61cff830c 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -109,7 +109,7 @@ namespace sl::graph::queue::detail template requires requires(Queue& queue) { - requires concepts::node{}(queue))>>; + requires concepts::basic_node{}(queue))>>; } constexpr decltype(auto) next(Queue& queue, const priority_tag<2>) noexcept(noexcept(customize::next_fn{}(queue))) { @@ -119,7 +119,7 @@ namespace sl::graph::queue::detail template requires requires(Queue& queue) { - requires concepts::node>; + requires concepts::basic_node>; } constexpr decltype(auto) next(Queue& queue, const priority_tag<1>) noexcept(noexcept(queue.next())) { @@ -129,7 +129,7 @@ namespace sl::graph::queue::detail template requires requires(Queue& queue) { - requires concepts::node>; + requires concepts::basic_node>; } constexpr decltype(auto) next(Queue& queue, const priority_tag<0>) noexcept(noexcept(next(queue))) { @@ -141,7 +141,7 @@ namespace sl::graph::queue::detail template requires requires(Queue& queue) { - requires concepts::node{}))>>; + requires concepts::basic_node{}))>>; } constexpr decltype(auto) operator ()(Queue& queue) const noexcept(noexcept(detail::next(queue, priority_tag<2>{}))) { @@ -211,7 +211,7 @@ namespace sl::graph::concepts { template concept queue_for = sl::concepts::unqualified - && node + && basic_node // ReSharper disable once CppRedundantTemplateKeyword && requires(T& queue, queue::detail::template dummy_input_range inputRange) { diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index b604ae3bd..563212cea 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -12,8 +12,8 @@ #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/functional/Tuple.hpp" #include "Simple-Utility/graph/Common.hpp" +#include "Simple-Utility/graph/Edge.hpp" #include "Simple-Utility/graph/Node.hpp" -#include "Simple-Utility/graph/NodeFactory.hpp" #include "Simple-Utility/graph/Queue.hpp" #include "Simple-Utility/graph/Tracker.hpp" #include "Simple-Utility/graph/View.hpp" @@ -21,214 +21,296 @@ #include #include #include -#include #include #include #include +#include namespace sl::graph::detail { - template QueuingStrategy> - class BasicState + struct LazyExplorer { - public: - using node_type = Node; - using queue_type = QueuingStrategy; - - ~BasicState() = default; - BasicState(const BasicState&) = delete; - BasicState& operator =(const BasicState&) = delete; - BasicState(BasicState&&) = default; - BasicState& operator =(BasicState&&) = default; + template + [[nodiscard]] + constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) const + { + return graph.edges(current) + | std::views::filter([&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); + } + }; - template - requires std::constructible_from + struct BufferedExplorer + { + template [[nodiscard]] - constexpr explicit BasicState(QueueArgs&&... queueArgs) noexcept(std::is_nothrow_constructible_v) - : m_Queue{std::forward(queueArgs)...} + constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) { - assert(queue::empty(m_Queue) && "Queue already contains elements."); + auto edges = graph.edges(current); + + std::vector results{}; + if constexpr (std::ranges::sized_range) + { + results.reserve(std::ranges::size(edges)); + } + + std::ranges::copy_if( + std::move(edges), + std::back_inserter(results), + [&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); + + return results; } + }; + template + struct NodeFactory; + + template + struct NodeFactory + { [[nodiscard]] - constexpr const queue_type& queue() const noexcept + constexpr Node operator ()(const node::vertex_t& vertex) const { - return m_Queue; + return Node{vertex}; } - template - requires std::convertible_to, node_type> + template Edge> [[nodiscard]] - constexpr std::optional next(Neighbors&& neighbors) + constexpr Node operator ()([[maybe_unused]] const Node& predecessor, const Edge& edge) const { - queue::insert(m_Queue, std::forward(neighbors)); - if (!queue::empty(m_Queue)) - { - return queue::next(m_Queue); - } + return Node{edge::destination(edge)}; + } + }; - return std::nullopt; + template + struct NodeFactory + { + [[nodiscard]] + constexpr Node operator ()(const node::vertex_t& vertex) const + { + return Node{vertex}; } - private: - QueuingStrategy m_Queue; + template Edge> + [[nodiscard]] + constexpr Node operator ()(const Node& predecessor, const Edge& edge) const + { + return Node{ + edge::destination(edge), + node::rank(predecessor) + edge::weight(edge) + }; + } }; - template - concept state_for = sl::concepts::unqualified - && std::destructible - && concepts::node - && requires(T& state, const std::forward_list& inputRange) - { - { !state.next(inputRange) } -> std::convertible_to; - { *state.next(inputRange) } -> std::convertible_to; - }; - -#if (defined(__clang__) && __clang_major__ < 16) \ - || (defined(__GNUG__) && __GNUG__ < 12) + template + struct NodeFactory> + : public NodeFactoryDecorator, NodeFactory> + { + }; - template - [[nodiscard]] - constexpr std::vector filter_transform(const Edges& edges, auto& tracker, auto& nodeFactory, const Node& currentNode) + template + class LazyKernel { - std::vector nodes{}; - if constexpr (std::ranges::sized_range) + public: + [[nodiscard]] + explicit constexpr LazyKernel( + NodeFactory nodeFactory = NodeFactory{} + ) noexcept(std::is_nothrow_move_constructible_v) + : m_NodeFactory{std::move(nodeFactory)} { - nodes.reserve(std::ranges::size(edges)); } - for (const auto& edge : edges) + constexpr Node operator ()(const node::vertex_t& vertex) const { - if (tracker::set_discovered(tracker, edge::destination(edge))) - { - nodes.emplace_back(nodeFactory.make_successor_node(currentNode, edge)); - } + return std::invoke(m_NodeFactory, vertex); + } + + template + requires std::convertible_to< + std::invoke_result_t< + NodeFactory, + const Node&, + std::ranges::range_reference_t>, + Node> + [[nodiscard]] + constexpr auto operator ()(const Node& current, Edges&& edges) const + { + return std::forward(edges) + | std::views::transform([&](const auto& edge) { return std::invoke(m_NodeFactory, current, edge); }); } - return nodes; + private: + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; }; -#else +#if (defined(__clang__) && __clang_major__ < 16) \ + || (defined(__GNUG__) && __GNUG__ < 12) + using default_explorer_t = BufferedExplorer; - template - [[nodiscard]] - constexpr auto filter_transform(Edges&& edges, auto& tracker, auto& nodeFactory, const Node& currentNode) - { - return std::forward(edges) - | std::views::filter([&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }) - | std::views::transform([&](const auto& edge) { return nodeFactory.make_successor_node(currentNode, edge); }); - }; + template + using default_kernel_t = LazyKernel; +#else + using default_explorer_t = LazyExplorer; + template + using default_kernel_t = LazyKernel; #endif template < - concepts::node Node, - state_for StateStrategy, + concepts::basic_node Node, + concepts::view_for View, + concepts::queue_for QueueStrategy, concepts::tracker_for> TrackingStrategy, - class NodeFactoryStrategy> - class BasicTraverseDriver + typename KernelStrategy = default_kernel_t>, + typename ExplorationStrategy = default_explorer_t> + requires concepts::edge_for, Node> + class BasicTraverser { public: using node_type = Node; + using edge_type = view::edge_t; using vertex_type = node::vertex_t; - using state_type = StateStrategy; + using view_type = View; + using queue_type = QueueStrategy; using tracker_type = TrackingStrategy; - using node_factory_type = NodeFactoryStrategy; - template - requires std::constructible_from + ~BasicTraverser() = default; + + BasicTraverser(const BasicTraverser&) = delete; + BasicTraverser& operator =(const BasicTraverser&) = delete; + BasicTraverser(BasicTraverser&&) = default; + BasicTraverser& operator =(BasicTraverser&&) = default; + + template < + typename... ViewArgs, + typename... QueueArgs, + typename... TrackerArgs, + typename... KernelArgs, + typename... ExplorerArgs> + requires std::constructible_from + && std::constructible_from && std::constructible_from - && std::constructible_from - && std::constructible_from + && std::constructible_from + && std::constructible_from [[nodiscard]] - explicit BasicTraverseDriver( - Origin&& origin, + explicit constexpr BasicTraverser( + const vertex_type& origin, + std::tuple viewArgs, + std::tuple queueArgs, std::tuple trackerArgs, - std::tuple nodeFactoryArgs, - std::tuple stateArgs + std::tuple kernelArgs, + std::tuple explorerArgs ) - : m_Tracker{std::make_from_tuple(std::move(trackerArgs))}, - m_NodeFactory{std::make_from_tuple(std::move(nodeFactoryArgs))}, - m_State{std::make_from_tuple(std::move(stateArgs))}, - m_Current{m_NodeFactory.make_init_node(std::forward(origin))} + : m_Explorer{std::make_from_tuple(std::move(explorerArgs))}, + m_Kernel{std::make_from_tuple(std::move(kernelArgs))}, + m_Queue{std::make_from_tuple(std::move(queueArgs))}, + m_Tracker{std::make_from_tuple(std::move(trackerArgs))}, + m_View{std::make_from_tuple(std::move(viewArgs))} { - const bool result = tracker::set_discovered(m_Tracker, node::vertex(m_Current)) - && tracker::set_visited(m_Tracker, node::vertex(m_Current)); + assert(queue::empty(m_Queue) && "Queue already contains elements."); + + const bool result = tracker::set_discovered(m_Tracker, origin); assert(result && "Tracker returned false (already visited) for the origin node."); + + queue::insert(m_Queue, std::array{std::invoke(m_Kernel, origin)}); } - template View> // let the concept here, because otherwise it results in an ICE on msvc v142 - requires concepts::node_factory_for> [[nodiscard]] - constexpr std::optional next(const View& graph) + constexpr std::optional next() { - std::optional result = m_State.next( - filter_transform(graph.edges(m_Current), m_Tracker, m_NodeFactory, m_Current)); - for (; result; result = m_State.next(std::array{})) + const auto queueNext = [&]() -> std::optional { - if (tracker::set_visited(m_Tracker, node::vertex(*result))) + if (!queue::empty(m_Queue)) { - m_Current = *result; - return result; + return {queue::next(m_Queue)}; } + + return std::nullopt; + }; + + std::optional result = queueNext(); + for (; + result && !tracker::set_visited(m_Tracker, node::vertex(*result)); + result = queueNext()) + { } - return std::nullopt; - } + if (result) + { + queue::insert( + m_Queue, + std::invoke( + m_Kernel, + *result, + std::invoke(m_Explorer, *result, m_View, m_Tracker))); + } - [[nodiscard]] - constexpr const Node& current_node() const noexcept - { - return m_Current; + return result; } [[nodiscard]] - constexpr const TrackingStrategy& tracker() const noexcept + constexpr const queue_type& queue() const noexcept { - return m_Tracker; + return m_Queue; } [[nodiscard]] - constexpr const NodeFactoryStrategy& node_factory() const noexcept + constexpr const tracker_type& tracker() const noexcept { - return m_NodeFactory; + return m_Tracker; } [[nodiscard]] - constexpr const StateStrategy& state() const noexcept + constexpr const view_type& view() const noexcept { - return m_State; + return m_View; } private: - // do not reorder. Seems to somehow cause segment faults, when ordered differently - SL_UTILITY_NO_UNIQUE_ADDRESS TrackingStrategy m_Tracker; - SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactoryStrategy m_NodeFactory; - StateStrategy m_State; - Node m_Current; + SL_UTILITY_NO_UNIQUE_ADDRESS ExplorationStrategy m_Explorer{}; + KernelStrategy m_Kernel{}; + queue_type m_Queue; + tracker_type m_Tracker; + View m_View; }; } +namespace sl::graph::concepts +{ + template + concept traverser = std::destructible + && requires(T& traverser) + { + typename T::node_type; + {!traverser.next()} -> std::convertible_to; + {*traverser.next()} -> std::convertible_to; + }; +} + namespace sl::graph { - template - class Traverser final + template + class IterableTraverser final { public: - using node_type = typename Driver::node_type; + using node_type = typename Traverser::node_type; using vertex_type = node::vertex_t; - [[nodiscard]] - constexpr explicit Traverser(View view, vertex_type origin) - : m_View{std::move(view)}, - m_Driver{std::move(origin), std::tuple{}, std::tuple{}, std::tuple{}} - { - } + ~IterableTraverser() = default; + + IterableTraverser(const IterableTraverser&) = delete; + IterableTraverser& operator =(const IterableTraverser&) = delete; + IterableTraverser(IterableTraverser&&) = default; + IterableTraverser& operator =(IterableTraverser&&) = default; + template + requires std::constructible_from [[nodiscard]] - std::optional next() + constexpr explicit IterableTraverser( + TraverserArgs&&... traverserArgs + ) noexcept(std::is_nothrow_constructible_v) + : m_Traverser{std::forward(traverserArgs)...} { - return m_Driver.next(m_View); } struct Sentinel final @@ -237,7 +319,7 @@ namespace sl::graph struct Iterator final { - friend Traverser; + friend IterableTraverser; public: using iterator_concept = std::input_iterator_tag; @@ -282,7 +364,7 @@ namespace sl::graph [[nodiscard]] constexpr Iterator begin() & { - return Iterator{*this}; + return Iterator{m_Traverser}; } [[nodiscard]] @@ -292,8 +374,7 @@ namespace sl::graph } private: - View m_View{}; - Driver m_Driver{}; + Traverser m_Traverser; }; } diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp index e5ce698d4..faa2e9763 100644 --- a/include/Simple-Utility/graph/View.hpp +++ b/include/Simple-Utility/graph/View.hpp @@ -24,7 +24,7 @@ namespace sl::graph::view namespace sl::graph::concepts { template - concept view_for = node + concept view_for = basic_node && sl::concepts::unqualified && requires(const T& view, const Node& node) { diff --git a/include/Simple-Utility/graph/mixins/queue/std_queue.hpp b/include/Simple-Utility/graph/mixins/queue/std_queue.hpp index 1cfa72d99..3d15fee5a 100644 --- a/include/Simple-Utility/graph/mixins/queue/std_queue.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_queue.hpp @@ -43,7 +43,7 @@ struct sl::graph::customize::next_fn> namespace sl::graph::queue { - template > + template > using CommonQueue = std::queue; } diff --git a/include/Simple-Utility/graph/mixins/queue/std_stack.hpp b/include/Simple-Utility/graph/mixins/queue/std_stack.hpp index 422364ce5..e26c6d4ac 100644 --- a/include/Simple-Utility/graph/mixins/queue/std_stack.hpp +++ b/include/Simple-Utility/graph/mixins/queue/std_stack.hpp @@ -42,7 +42,7 @@ struct sl::graph::customize::next_fn> namespace sl::graph::queue { - template > + template > using CommonStack = std::stack; } diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 7e9fdd1d5..0219c76e5 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -1,12 +1,12 @@ target_sources( Simple-Utility-Tests PRIVATE + "AStarSearch.cpp" "Common.cpp" "DepthFirstSearch.cpp" "Edge.cpp" "Formatter.cpp" "Node.cpp" - "NodeFactory.cpp" "Queue.cpp" "Tracker.cpp" "Traverse.cpp" diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index c79d98101..fa6af05e2 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -63,7 +63,7 @@ struct GenericWeightedEdge friend bool operator==(const GenericWeightedEdge&, const GenericWeightedEdge&) = default; }; -template +template class QueueMock { public: @@ -84,7 +84,7 @@ class QueueMock } }; -template +template struct EmptyQueueStub { [[nodiscard]] @@ -110,7 +110,7 @@ class TrackerMock MAKE_MOCK1(set_visited, bool(const Vertex&)); }; -template +template class GenericBasicNodeFactoryMock { public: @@ -152,12 +152,30 @@ template class BasicViewMock { public: + inline static constexpr bool trompeloeil_movable_mock = true; + using edge_type = GenericBasicEdge; MAKE_CONST_MOCK1(edges, std::vector(const GenericBasicNode&)); - std::vector edges(const sg::concepts::node auto& node) const + template + requires sg::concepts::edge_for + std::vector edges(const Node& node) const { return edges(GenericBasicNode{.vertex = sg::node::vertex(node)}); } }; + +template +class EmptyViewStub +{ +public: + using edge_type = GenericBasicEdge; + + template + requires sg::concepts::edge_for + static constexpr std::array edges([[maybe_unused]] const Node& node) noexcept + { + return {}; + } +}; diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 0a821a33a..3d8e4f770 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -200,7 +200,7 @@ TEMPLATE_TEST_CASE_SIG( (true, sg::PredecessorNodeDecorator>) ) { - STATIC_REQUIRE(expected == sg::concepts::node); + STATIC_REQUIRE(expected == sg::concepts::basic_node); } TEMPLATE_TEST_CASE_SIG( diff --git a/tests/graph/NodeFactory.cpp b/tests/graph/NodeFactory.cpp deleted file mode 100644 index 2d423574c..000000000 --- a/tests/graph/NodeFactory.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright Dominic Koepke 2019 - 2023. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// https://www.boost.org/LICENSE_1_0.txt) - -#include "Simple-Utility/graph/NodeFactory.hpp" - -#include - -#include "Defines.hpp" - -TEMPLATE_TEST_CASE_SIG( - "concepts::node_factory_for determines, whether the given type satisfies the requirements of a node_factory for the specified node type.", - "[graph][graph::concepts]", - ((bool expected, class Factory, class Node, class Edge), expected, Factory, Node, Edge), - (false, GenericBasicNodeFactoryMock>, GenericBasicNode, GenericBasicEdge), - (true, GenericBasicNodeFactoryMock>, GenericBasicNode, GenericBasicEdge), - - (false, GenericRankedNodeFactoryMock>, GenericRankedNode, GenericBasicEdge), - (true, GenericRankedNodeFactoryMock>, GenericRankedNode, GenericWeightedEdge) -) -{ - STATIC_CHECK(sg::concepts::node); - STATIC_CHECK(sg::concepts::edge); - - STATIC_REQUIRE(expected == sg::concepts::node_factory_for); -} diff --git a/tests/graph/Tracker.cpp b/tests/graph/Tracker.cpp index 173ca0d90..604cd778b 100644 --- a/tests/graph/Tracker.cpp +++ b/tests/graph/Tracker.cpp @@ -165,7 +165,7 @@ TEMPLATE_TEST_CASE_SIG( TEMPLATE_TEST_CASE( "Concrete tracker types behave as expected.", - "[graph][graph::concepts][graph::tracker]", + "[graph][graph::tracker]", (sg::tracker::CommonHashMap), (sg::tracker::CommonMap) ) @@ -204,3 +204,40 @@ TEMPLATE_TEST_CASE( } } } + +TEST_CASE("tracker::Null always returns true.", "[graph][graph::tracker]") +{ + sg::tracker::Null tracker{}; + + SECTION("Discovering a new vertex yields true.") + { + const int vertex = GENERATE(take(5, random(std::numeric_limits::min() + 1, std::numeric_limits::max()))); + + REQUIRE(sg::tracker::set_discovered(tracker, vertex)); + + SECTION("Discovering the same vertex again, yields also true.") + { + REQUIRE(sg::tracker::set_discovered(tracker, vertex)); + } + + SECTION("Discovering another vertex yields true.") + { + REQUIRE(sg::tracker::set_discovered(tracker, -vertex)); + } + + SECTION("Visiting a discovered vertex yields true.") + { + REQUIRE(sg::tracker::set_visited(tracker, vertex)); + + SECTION("Discovering an already visited vertex, yields true.") + { + REQUIRE(sg::tracker::set_discovered(tracker, vertex)); + } + + SECTION("Visiting an already visited vertex, yields true.") + { + REQUIRE(sg::tracker::set_visited(tracker, vertex)); + } + } + } +} diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 1ca7dbc25..38b1b7e88 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -13,6 +13,7 @@ #include "Defines.hpp" +#include "Simple-Utility/graph/mixins/tracker/Null.hpp" #include "Simple-Utility/test_util/Catch2Ext.hpp" #include "Simple-Utility/test_util/TrompeloeilExt.hpp" @@ -21,188 +22,130 @@ // ReSharper disable CppClangTidyClangDiagnosticUnneededMemberFunction using DefaultNode = GenericBasicNode; +using DefaultEdge = GenericBasicEdge; using DefaultQueue = QueueMock; -using DefaultState = sg::detail::BasicState; using DefaultTracker = TrackerMock>; using DefaultView = BasicViewMock>; using DefaultNodeFactory = GenericBasicNodeFactoryMock; -using DefaultDriver = sg::detail::BasicTraverseDriver< +using DefaultTraverser = sg::detail::BasicTraverser< DefaultNode, - DefaultState, - DefaultTracker, - DefaultNodeFactory>; + DefaultView, + DefaultQueue, + DefaultTracker>; -TEST_CASE("BasicState is constructible.", "[graph][graph::traverse][graph::detail]") -{ - using State = sg::detail::BasicState>; - - SECTION("Default constructible.") - { - constexpr State state{}; - } +using MovableTraverser = sg::detail::BasicTraverser< + DefaultNode, + EmptyViewStub, + EmptyQueueStub, + sg::tracker::Null>; - SECTION("Constructible with queue object as argument.") - { - constexpr State state{EmptyQueueStub{}}; - } +TEST_CASE("detail::BasicTraverser is not copyable but movable, when strategies support it.", "[graph][graph::traverser][graph::detail]") +{ + STATIC_REQUIRE(!std::is_copy_constructible_v); + STATIC_REQUIRE(!std::is_copy_assignable_v); + STATIC_REQUIRE(std::is_move_constructible_v); + STATIC_REQUIRE(std::is_move_assignable_v); } -TEST_CASE( - "BasicState::next accepts current neighbors and returns the next node, if present.", - "[graph][graph::traverse][graph::detail]") +TEMPLATE_TEST_CASE( + "detail::BasicTraverser satisfies graph::concepts::traverser.", + "[graph][graph::traverser][graph::concepts]", + MovableTraverser, + DefaultTraverser +) { - using trompeloeil::_; - using namespace trompeloeil_ext; - using namespace Catch::Matchers; - using namespace catch_ext; - - DefaultQueue queue{}; - ALLOW_CALL(queue, empty()) // allow debug assertion - .RETURN(true); - DefaultState state{std::move(queue)}; - - SECTION("Providing neighbor infos on next call.") - { - std::vector neighbors{{44}, {45}, {46}}; - auto& queueMember = const_cast(state.queue()); - - trompeloeil::sequence seq{}; - REQUIRE_CALL(queueMember, do_insert(matches(UnorderedRangeEquals(neighbors)))) - .IN_SEQUENCE(seq); - - REQUIRE_CALL(queueMember, empty()) - .RETURN(false) - .IN_SEQUENCE(seq); - - REQUIRE_CALL(queueMember, next()) - .RETURN(DefaultNode{44}) - .IN_SEQUENCE(seq); - - REQUIRE(DefaultNode{44} == state.next(neighbors)); - - SECTION("And when the internal queue depletes.") - { - REQUIRE_CALL(queueMember, do_insert(matches(RangesEmpty{}))) - .IN_SEQUENCE(seq); - - REQUIRE_CALL(queueMember, empty()) - .RETURN(true) - .IN_SEQUENCE(seq); - - REQUIRE(std::nullopt == state.next(std::array{})); - } - } + STATIC_REQUIRE(sg::concepts::traverser); } -TEST_CASE("BasicTraverseDriver can be constructed with an origin.", "[graph][graph::traverse][graph::detail]") +TEST_CASE("detail::BasicTraverser can be constructed with an origin.", "[graph][graph::traverser][graph::detail]") { using namespace trompeloeil_ext; using namespace Catch::Matchers; const auto origin = GENERATE(take(5, random(std::numeric_limits::min(), std::numeric_limits::max()))); - DefaultNodeFactory nodeFactoryMock{}; - REQUIRE_CALL(nodeFactoryMock, make_init_node(origin)) - .RETURN(DefaultNode{.vertex = origin}); - - DefaultTracker trackerMock{}; - REQUIRE_CALL(trackerMock, set_discovered(origin)) + DefaultQueue queue{}; + ALLOW_CALL(queue, empty()) // internal assertion .RETURN(true); - REQUIRE_CALL(trackerMock, set_visited(origin)) + REQUIRE_CALL(queue, do_insert(matches(RangeEquals(std::array{DefaultNode{origin}})))); + + DefaultTracker tracker{}; + REQUIRE_CALL(tracker, set_discovered(origin)) .RETURN(true); - const sg::detail::BasicTraverseDriver< - DefaultNode, - sg::detail::BasicState>, - DefaultTracker, - DefaultNodeFactory> driver{ + const DefaultTraverser traverser{ origin, - std::forward_as_tuple(std::move(trackerMock)), - std::forward_as_tuple(std::move(nodeFactoryMock)), + std::forward_as_tuple(DefaultView{}), + std::forward_as_tuple(std::move(queue)), + std::forward_as_tuple(std::move(tracker)), + std::tuple{}, std::tuple{} }; - - REQUIRE(DefaultNode{.vertex = origin} == driver.current_node()); } -TEST_CASE("BasicTraverseDriver::next returns the current node, or std::nullopt.", "[graph][graph::traverse][graph::detail]") +TEST_CASE("detail::BasicTraverser::next returns the current node, or std::nullopt.", "[graph][graph::traverser][graph::detail]") { using trompeloeil::_; using namespace trompeloeil_ext; using namespace catch_ext; using namespace Catch::Matchers; - constexpr DefaultNode originNode{.vertex = 42}; - auto driver = [&] + constexpr DefaultNode originNode{42}; + auto traverser = [&] { - DefaultNodeFactory nodeFactoryMock{}; - REQUIRE_CALL(nodeFactoryMock, make_init_node(42)) - .RETURN(originNode); - DefaultTracker trackerMock{}; REQUIRE_CALL(trackerMock, set_discovered(42)) .RETURN(true); - REQUIRE_CALL(trackerMock, set_visited(42)) - .RETURN(true); DefaultQueue queue{}; - ALLOW_CALL(queue, empty()) + ALLOW_CALL(queue, empty()) // internal assertion .RETURN(true); + REQUIRE_CALL(queue, do_insert(matches(RangeEquals(std::array{originNode})))); - return DefaultDriver{ + return DefaultTraverser{ originNode.vertex, + std::forward_as_tuple(DefaultView{}), + std::forward_as_tuple(std::move(queue)), std::forward_as_tuple(std::move(trackerMock)), - std::forward_as_tuple(std::move(nodeFactoryMock)), - std::forward_as_tuple(std::move(queue)) + std::tuple{}, + std::tuple{} }; }(); - CHECK(driver.current_node() == originNode); - using VertexInfo = DefaultView::edge_type; - DefaultView view{}; - auto& nodeFactoryMock = const_cast(driver.node_factory()); - auto& queueMock = const_cast(driver.state().queue()); - auto& trackerMock = const_cast(driver.tracker()); + auto& view = const_cast(traverser.view()); + auto& queue = const_cast(traverser.queue()); + auto& tracker = const_cast(traverser.tracker()); - SECTION("Next returns a node.") + SECTION("Next returns a node, when queue contains elements.") { // vertex 43 will be skipped on purpose REQUIRE_CALL(view, edges(originNode)) .RETURN(std::vector{{41}, {43}, {44}}); - REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{41})) - .RETURN(DefaultNode{.vertex = 41}); - REQUIRE_CALL(nodeFactoryMock, make_successor_node(originNode, VertexInfo{44})) - .RETURN(DefaultNode{.vertex = 44}); - - REQUIRE_CALL(queueMock, do_insert(matches(RangeEquals(std::vector{{41}, {44}})))); - REQUIRE_CALL(queueMock, empty()) + REQUIRE_CALL(queue, do_insert(matches(RangeEquals(std::vector{{41}, {44}})))); + REQUIRE_CALL(queue, empty()) .RETURN(false); - REQUIRE_CALL(queueMock, next()) - .RETURN(DefaultNode{.vertex = 41}); + REQUIRE_CALL(queue, next()) + .RETURN(DefaultNode{.vertex = 42}); - REQUIRE_CALL(trackerMock, set_discovered(41)) + REQUIRE_CALL(tracker, set_discovered(41)) .RETURN(true); - REQUIRE_CALL(trackerMock, set_discovered(43)) + REQUIRE_CALL(tracker, set_discovered(43)) .RETURN(false); - REQUIRE_CALL(trackerMock, set_discovered(44)) + REQUIRE_CALL(tracker, set_discovered(44)) .RETURN(true); - REQUIRE_CALL(trackerMock, set_visited(41)) + REQUIRE_CALL(tracker, set_visited(42)) .RETURN(true); - REQUIRE(DefaultNode{.vertex = 41} == driver.next(std::as_const(view))); + REQUIRE(DefaultNode{.vertex = 42} == traverser.next()); } - SECTION("Next returns std::nullopt.") + SECTION("Next returns std::nullopt, when queue is empty.") { - REQUIRE_CALL(view, edges(originNode)) - .RETURN(std::vector>{}); - - REQUIRE_CALL(queueMock, do_insert(matches(RangesEmpty{}))); - REQUIRE_CALL(queueMock, empty()) + REQUIRE_CALL(queue, empty()) .RETURN(true); - REQUIRE(std::nullopt == driver.next(std::as_const(view))); + REQUIRE(std::nullopt == traverser.next()); } } diff --git a/tests/graph/View.cpp b/tests/graph/View.cpp index 64f444016..69a59452c 100644 --- a/tests/graph/View.cpp +++ b/tests/graph/View.cpp @@ -16,7 +16,7 @@ namespace { using edge_type = GenericBasicEdge; - template + template requires sg::concepts::edge_for // ReSharper disable once CppFunctionIsNotImplemented static std::vector edges(const Node&); @@ -27,7 +27,7 @@ namespace { using edge_type = GenericWeightedEdge; - template + template requires sg::concepts::edge_for // ReSharper disable once CppFunctionIsNotImplemented static std::vector edges(const Node&); From 5dd1d5b671b66c1012d4c2d7a1a36eb923fe88d1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 19:03:57 +0200 Subject: [PATCH 153/256] fix: bring Formatter.hpp in sync --- include/Simple-Utility/graph/Formatter.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/Simple-Utility/graph/Formatter.hpp b/include/Simple-Utility/graph/Formatter.hpp index 2a3fdc971..7c5d82bf4 100644 --- a/include/Simple-Utility/graph/Formatter.hpp +++ b/include/Simple-Utility/graph/Formatter.hpp @@ -18,7 +18,7 @@ namespace sl::graph { - template + template requires sl::concepts::formattable, Char> class NodeFormatter { @@ -53,7 +53,7 @@ namespace sl::graph } }; - template + template class NodeFormatter, Char> { public: @@ -76,7 +76,7 @@ namespace sl::graph }; } -template +template struct std::formatter // NOLINT(cert-dcl58-cpp) { public: From e4c0848f60fecf79728cf3bd6191c78b5b14e1a2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 19:04:55 +0200 Subject: [PATCH 154/256] fix: bring dfs namespace in sync --- .../Simple-Utility/graph/DepthFirstSearch.hpp | 22 ++++++++++--------- tests/graph/DepthFirstSearch.cpp | 20 ++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index ddede1766..9dde3bdd0 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -14,23 +14,25 @@ namespace sl::graph::dfs { - template - requires (!concepts::ranked_node) - using NodeFactory = CommonNodeFactory; + template + struct NodeFactory + : public detail::NodeFactory + { + }; template < class View, - concepts::node Node = CommonBasicNode>>, - concepts::node_factory_for> NodeFactory = NodeFactory, + concepts::basic_node Node = CommonBasicNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::view_for - using BasicTraverser = Traverser< - View, - detail::BasicTraverseDriver< + && (!concepts::ranked_node) + using Range = IterableTraverser< + detail::BasicTraverser< Node, - detail::BasicState>, + View, + queue::CommonStack, Tracker, - NodeFactory>>; + detail::default_kernel_t>>>; } #endif diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index ec87d68e0..0f2101815 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -31,7 +31,7 @@ namespace using edge_type = sg::CommonBasicEdge; - template + template requires sg::concepts::edge_for static std::vector edges(const Node& current) { @@ -44,24 +44,24 @@ namespace [](const int v) { return edge_type{.destination = v}; }); return infos; } - } inline constexpr graph{}; + }; } -TEST_CASE("dfs::BasicTraverser visits all reachable vertices.", "[graph][graph::dfs]") +TEST_CASE("dfs::Range visits all reachable vertices.", "[graph][graph::dfs]") { const auto& [expected, origin] = GENERATE( (table>, int>)({ - {{/*3,*/ {6}, {2}, {5}}, 3}, - {{/*6,*/ {2}}, 6}, - {{/*1,*/ {3}, {6}, {2}, {5}}, 1}, - {{/*8,*/ {9}, {4}, {7}}, 8}, + {{{3}, {6}, {2}, {5}}, 3}, + {{{6}, {2}}, 6}, + {{{1}, {3}, {6}, {2}, {5}}, 1}, + {{{8}, {9}, {4}, {7}}, 8}, })); - sg::dfs::BasicTraverser traverser{graph, origin}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Range range{origin, std::tuple{View{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); std::vector> nodes{}; - std::ranges::copy(traverser, std::back_inserter(nodes)); + std::ranges::copy(range, std::back_inserter(nodes)); REQUIRE_THAT(nodes, Catch::Matchers::RangeEquals(expected)); } From a36cc78b93c76be03151386e85caeb74b28217cd Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 19:06:05 +0200 Subject: [PATCH 155/256] fix: bring ucs namespace in sync --- .../graph/UniformCostSearch.hpp | 16 +++++---- tests/graph/UniformCostSearch.cpp | 35 ++++++++++--------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp index 34240a9b0..f8121f99d 100644 --- a/include/Simple-Utility/graph/UniformCostSearch.hpp +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -15,21 +15,23 @@ namespace sl::graph::ucs { template - using NodeFactory = CommonNodeFactory; + struct NodeFactory + : public detail::NodeFactory + { + }; template < class View, concepts::ranked_node Node = CommonRankedNode>, edge::weight_t>>, - concepts::node_factory_for> NodeFactory = NodeFactory, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::view_for - using BasicTraverser = Traverser< - View, - detail::BasicTraverseDriver< + using Range = IterableTraverser< + detail::BasicTraverser< Node, - detail::BasicState>, + View, + queue::CommonPriorityQueue, Tracker, - NodeFactory>>; + detail::default_kernel_t>>>; } #endif diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp index 9404394d4..84c7cf5fa 100644 --- a/tests/graph/UniformCostSearch.cpp +++ b/tests/graph/UniformCostSearch.cpp @@ -53,45 +53,46 @@ namespace }); return infos; } - } inline constexpr graph{}; + }; } -TEST_CASE("ucs::BasicTraverser visits all reachable vertices.", "[graph][graph::ucs]") +TEST_CASE("ucs::Range visits all reachable vertices.", "[graph][graph::ucs]") { const auto& [expected, origin] = GENERATE( (table, std::string>)({ - {{{"5", 2}, {"6", 3}, {"2", 7}}, "3"}, - {{{"2", 4}}, "6"}, - {{{"2", 1}, {"3", 2}, {"6", 5}, {"5", 4}}, "1"}, - {{{"7", 1}, {"4", 4}, {"9", 1}}, "8"} + {{{"3", 0}, {"5", 2}, {"6", 3}, {"2", 7}}, "3"}, + {{{"6", 0}, {"2", 4}}, "6"}, + {{{"1", 0}, {"2", 1}, {"3", 2}, {"6", 5}, {"5", 4}}, "1"}, + {{{"8", 0}, {"7", 1}, {"4", 4}, {"9", 1}}, "8"} })); - sg::ucs::BasicTraverser traverser{graph, origin}; - STATIC_CHECK(std::ranges::input_range); + sg::ucs::Range range{origin, std::tuple{View{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); std::vector nodes{}; - std::ranges::copy(traverser, std::back_inserter(nodes)); + std::ranges::copy(range, std::back_inserter(nodes)); REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); } -TEST_CASE("ucs::BasicTraverser node can be decorated with PredecessorNodeDecorator.", "[graph][graph::ucs]") +TEST_CASE("ucs::Range node can be decorated with PredecessorNodeDecorator.", "[graph][graph::ucs]") { using DecoratedNode = sg::PredecessorNodeDecorator<::Node>; const auto& [expected, origin] = GENERATE( (table, std::string>)({ - {{{{"5", 2}, "3"}, {{"6", 3}, "3"}, {{"2", 7}, "6"}}, "3"}, - {{{{"2", 4}, "6"}}, "6"}, - //{{{{"2", 1}, "1"}, {{"3", 2}, "1"}, {{"6", 5}, "2"}, {{"5", 4}, "3"}}, "1"}, // non-deterministic as 6 may have the predecessor 2 or 3 - {{{{"7", 1}, "8"}, {{"4", 4}, "7"}, {{"9", 1}, "8"}}, "8"} + {{{{"3", 0}, std::nullopt}, {{"5", 2}, "3"}, {{"6", 3}, "3"}, {{"2", 7}, "6"}}, "3"}, + {{{{"6", 0}, std::nullopt}, {{"2", 4}, "6"}}, "6"}, + // non-deterministic, as 6 may have the predecessor 2 or 3 + //{{{{"1", 0}, std::nullopt}, {{"2", 1}, "1"}, {{"3", 2}, "1"}, {{"6", 5}, "2"}, {{"5", 4}, "3"}}, "1"}, + {{{{"8", 0}, std::nullopt}, {{"7", 1}, "8"}, {{"4", 4}, "7"}, {{"9", 1}, "8"}}, "8"} })); - sg::ucs::BasicTraverser traverser{graph, origin}; - STATIC_CHECK(std::ranges::input_range); + sg::ucs::Range range{origin, std::tuple{View{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); std::vector nodes{}; - std::ranges::copy(traverser, std::back_inserter(nodes)); + std::ranges::copy(range, std::back_inserter(nodes)); REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); } From 06cf09bfc714b8428ab8d734a1d2b572c43ec051 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 19:06:37 +0200 Subject: [PATCH 156/256] feat: add astar namespace --- include/Simple-Utility/graph/AStarSearch.hpp | 214 +++++++++++++++++++ tests/graph/AStarSearch.cpp | 121 +++++++++++ 2 files changed, 335 insertions(+) create mode 100644 include/Simple-Utility/graph/AStarSearch.hpp create mode 100644 tests/graph/AStarSearch.cpp diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp new file mode 100644 index 000000000..8b9642a5d --- /dev/null +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -0,0 +1,214 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_A_STAR_SEARCH_HPP +#define SIMPLE_UTILITY_GRAPH_A_STAR_SEARCH_HPP + +#pragma once + +#include "Simple-Utility/graph/Traverse.hpp" +#include "Simple-Utility/graph/mixins/queue/std_priority_queue.hpp" +#include "Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp" + +namespace sl::graph::concepts +{ + template + concept heuristic_for = ranked_node + && sl::concepts::unqualified + && std::movable + && std::invocable> + && std::convertible_to>, node::rank_t>; +} + +namespace sl::graph::astar::detail +{ + template + constexpr void check_bounds(const Rank& base, const Rank& increase) noexcept + { + assert(0 <= base && "base must be non-negative."); + assert(0 <= increase && "increase must be non-negative."); + assert(increase <= std::numeric_limits::max() - base && "Rank is about to overflow."); + } + + template Heuristic, typename NodeFactory> + class LazyKernel + { + public: + [[nodiscard]] + explicit constexpr LazyKernel( + Heuristic heuristic = Heuristic{}, + NodeFactory nodeFactory = NodeFactory{} + ) noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_move_constructible_v) + : m_Heuristic{std::move(heuristic)}, + m_NodeFactory{std::move(nodeFactory)} + { + } + + constexpr Node operator ()(const node::vertex_t& vertex) const + { + return std::invoke( + m_NodeFactory, + vertex, + std::invoke(m_Heuristic, vertex)); + } + + template + requires std::convertible_to< + std::invoke_result_t< + NodeFactory, + const Node&, + std::ranges::range_reference_t, + std::invoke_result_t>>, + Node> + [[nodiscard]] + constexpr auto operator ()(const Node& current, Edges&& edges) const + { + return std::forward(edges) + | std::views::transform( + [&](const auto& edge) + { + return std::invoke( + m_NodeFactory, + current, + edge, + std::invoke(m_Heuristic, edge::destination(edge))); + }); + } + + private: + SL_UTILITY_NO_UNIQUE_ADDRESS Heuristic m_Heuristic{}; + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; + }; +} + +namespace sl::graph::astar +{ + template + struct Node + { + using vertex_type = Vertex; + using rank_type = Rank; + + vertex_type vertex{}; + rank_type cost{}; + rank_type estimatedPendingCost{}; + + [[nodiscard]] + friend constexpr rank_type rank(const Node& node) noexcept(noexcept(node.cost + node.cost)) + { + detail::check_bounds(node.cost, node.estimatedPendingCost); + + return node.cost + node.estimatedPendingCost; + } + + [[nodiscard]] + friend bool operator==(const Node&, const Node&) = default; + }; + + template + struct NodeFactory; + + template + struct NodeFactory> + { + public: + using node_type = Node; + using vertex_type = Vertex; + using rank_type = Rank; + + [[nodiscard]] + constexpr node_type operator ()(vertex_type origin, rank_type estimatedPendingCost) const + { + node_type node{ + .vertex = std::move(origin), + .cost = {}, + .estimatedPendingCost = std::move(estimatedPendingCost) + }; + detail::check_bounds(node.cost, node.estimatedPendingCost); + + return node; + } + + template Edge> + [[nodiscard]] + constexpr node_type operator ()(const node_type& current, const Edge& edge, rank_type estimatedPendingCost) const + { + detail::check_bounds(current.cost, edge::weight(edge)); + + node_type node{ + .vertex = edge::destination(edge), + .cost = current.cost + edge::weight(edge), + .estimatedPendingCost = std::move(estimatedPendingCost) + }; + detail::check_bounds(node.cost, node.estimatedPendingCost); + + return node; + } + }; + + template + struct NodeFactory> + : public NodeFactoryDecorator, NodeFactory> + { + }; + + template Strategy> + class SingleDestinationHeuristic + { + public: + using vertex_type = Vertex; + + [[nodiscard]] + explicit constexpr SingleDestinationHeuristic( + Vertex destination + ) noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_default_constructible_v) + : m_Destination{std::move(destination)} + { + } + + [[nodiscard]] + explicit constexpr SingleDestinationHeuristic( + Vertex destination, + Strategy strategy + ) noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_move_constructible_v) + : m_Destination{std::move(destination)}, + m_Strategy{std::move(strategy)} + { + } + + [[nodiscard]] + constexpr auto operator ()(const Vertex& current) const noexcept(std::is_nothrow_invocable_v) + { + return std::invoke(m_Strategy, m_Destination, current); + } + + private: + Vertex m_Destination{}; + Strategy m_Strategy{}; + }; + + template < + typename View, + typename Heuristic, + concepts::ranked_node Node = Node>, edge::weight_t>>, + concepts::tracker_for> Tracker = tracker::CommonHashMap>> + requires concepts::view_for + && concepts::heuristic_for + using Range = IterableTraverser< + graph::detail::BasicTraverser< + Node, + View, + queue::CommonPriorityQueue, + Tracker, + detail::LazyKernel< + Node, + Heuristic, + NodeFactory>>>; +} + +#endif diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp new file mode 100644 index 000000000..ccbf73362 --- /dev/null +++ b/tests/graph/AStarSearch.cpp @@ -0,0 +1,121 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/AStarSearch.hpp" + +#include +#include +#include + +#include "Defines.hpp" + +namespace +{ + using Node = sg::astar::Node; + using Edge = sg::CommonWeightedEdge; + + struct View + { + using edge_type = Edge; + + inline static const std::unordered_map> graph{ + {"1", {2, 3}}, + {"2", {6}}, + {"3", {5, 6}}, + {"5", {5}}, + {"6", {2}}, + + // begin isolated sub-graph + {"4", {7}}, + {"7", {4, 7, 9}}, + {"8", {7, 9}}, + {"9", {4}} + }; + + template + requires sg::concepts::edge_for + static std::vector edges(const Node& current) + { + const auto& vertices = graph.at(sg::node::vertex(current)); + std::vector infos{}; + infos.reserve(std::ranges::size(vertices)); + std::ranges::transform( + vertices, + std::back_inserter(infos), + [&](const int v) + { + return edge_type{ + .destination = std::to_string(v), + .weight = std::abs(v - std::stoi(current.vertex)) + }; + }); + return infos; + } + }; + + struct manhattan_distance + { + [[nodiscard]] + int operator ()(const std::string& dest, const std::string& cur) const noexcept + { + return std::abs(std::stoi(dest) - std::stoi(cur)); + } + }; + + using Heuristic = sg::astar::SingleDestinationHeuristic; +} + +TEST_CASE("astar::Range visits all reachable vertices.", "[graph][graph::astar]") +{ + const auto& [expected, origin, destination] = GENERATE( + (table, std::string, std::string>)({ + {{{"3", 0, 6}, {"5", 2, 4}, {"6", 3, 3}, {"2", 7, 7}}, "3", "9"}, + {{{"6", 0, 3}, {"2", 4, 7}}, "6", "9"}, + {{{"1", 0, 8}, {"2", 1, 7}, {"3", 2, 6}, {"6", 5, 3}, {"5", 4, 4}}, "1", "9"}, + {{{"8", 0, 1}, {"7", 1, 2}, {"4", 4, 5}, {"9", 1, 0}}, "8", "9"} + })); + + sg::astar::Range range{ + origin, + std::tuple{View{}}, + std::tuple{}, + std::tuple{}, + std::tuple{Heuristic{destination}}, + std::tuple{} + }; + STATIC_CHECK(std::ranges::input_range); + + std::vector nodes{}; + std::ranges::copy(range, std::back_inserter(nodes)); + + REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); +} + +TEST_CASE("astar::Range node can be decorated with PredecessorNodeDecorator.", "[graph][graph::ucs]") +{ + using DecoratedNode = sg::PredecessorNodeDecorator<::Node>; + + const auto& [expected, origin, destination] = GENERATE( + (table, std::string, std::string>)({ + {{{{"3", 0, 6}, std::nullopt}, {{"5", 2, 4}, "3"}, {{"6", 3, 3}, "3"}, {{"2", 7, 7}, "6"}}, "3", "9"}, + {{{{"6", 0, 3}, std::nullopt}, {{"2", 4, 7}, "6"}}, "6", "9"}, + {{{{"8", 0, 1}, std::nullopt}, {{"7", 1, 2}, "8"}, {{"4", 4, 5}, "7"}, {{"9", 1, 0}, "8"}}, "8", "9"} + })); + + sg::astar::Range range{ + origin, + std::tuple{View{}}, + std::tuple{}, + std::tuple{}, + std::tuple{Heuristic{destination}}, + std::tuple{} + }; + STATIC_CHECK(std::ranges::input_range); + + std::vector nodes{}; + std::ranges::copy(range, std::back_inserter(nodes)); + + REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); +} From caee6e1bef7ad8d3d4a02242dd92dac8d4484018 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 19:07:04 +0200 Subject: [PATCH 157/256] env: tweak resharper syntax style --- Folder.DotSettings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Folder.DotSettings b/Folder.DotSettings index 6bd663a9b..b384d7f5c 100644 --- a/Folder.DotSettings +++ b/Folder.DotSettings @@ -1,6 +1,8 @@  True True + SUGGESTION + SUGGESTION HINT HINT SUGGESTION From 4825b78e5be900878fc95e1b14c45f9fe5573619 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 19:31:26 +0200 Subject: [PATCH 158/256] fix: graph::detail::BufferedExplorer --- include/Simple-Utility/graph/Traverse.hpp | 56 +++++++++++++++++++++-- tests/graph/Traverse.cpp | 28 ++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 563212cea..8b21720dc 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -47,7 +47,7 @@ namespace sl::graph::detail { auto edges = graph.edges(current); - std::vector results{}; + std::vector> results{}; if constexpr (std::ranges::sized_range) { results.reserve(std::ranges::size(edges)); @@ -109,11 +109,11 @@ namespace sl::graph::detail }; template - class LazyKernel + class BaseKernel { public: [[nodiscard]] - explicit constexpr LazyKernel( + explicit constexpr BaseKernel( NodeFactory nodeFactory = NodeFactory{} ) noexcept(std::is_nothrow_move_constructible_v) : m_NodeFactory{std::move(nodeFactory)} @@ -125,6 +125,50 @@ namespace sl::graph::detail return std::invoke(m_NodeFactory, vertex); } + protected: + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; + }; + + template + class LazyKernel + : public BaseKernel + { + private: + using Super = BaseKernel; + + public: + using Super::Super; + using Super::operator(); + + template + requires std::convertible_to< + std::invoke_result_t< + NodeFactory, + const Node&, + std::ranges::range_reference_t>, + Node> + [[nodiscard]] + constexpr auto operator ()(const Node& current, Edges&& edges) const + { + return std::forward(edges) + | std::views::transform([&](const auto& edge) { return std::invoke(m_NodeFactory, current, edge); }); + } + + private: + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; + }; + + template + class BufferedKernel + : public BaseKernel + { + private: + using Super = BaseKernel; + + public: + using Super::Super; + using Super::operator(); + template requires std::convertible_to< std::invoke_result_t< @@ -135,6 +179,12 @@ namespace sl::graph::detail [[nodiscard]] constexpr auto operator ()(const Node& current, Edges&& edges) const { + std::vector results{}; + if constexpr (std::ranges::sized_range) + { + results.reserve(std::ranges::size(edges)); + } + return std::forward(edges) | std::views::transform([&](const auto& edge) { return std::invoke(m_NodeFactory, current, edge); }); } diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 38b1b7e88..1c721b941 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -39,6 +39,34 @@ using MovableTraverser = sg::detail::BasicTraverser< EmptyQueueStub, sg::tracker::Null>; +TEMPLATE_TEST_CASE( + "Explorer implementations behave as expected.", + "[graph][graph::detail]", + sg::detail::LazyExplorer, + sg::detail::BufferedExplorer +) +{ + using namespace Catch::Matchers; + + constexpr DefaultNode current{.vertex = 42}; + const BasicViewMock view{}; + REQUIRE_CALL(view, edges(current)) + .RETURN(std::vector{{41}, {43}, {44}, {45}}); + + TrackerMock tracker{}; + REQUIRE_CALL(tracker, set_discovered(41)) + .RETURN(false); + REQUIRE_CALL(tracker, set_discovered(43)) + .RETURN(false); + REQUIRE_CALL(tracker, set_discovered(44)) + .RETURN(true); + REQUIRE_CALL(tracker, set_discovered(45)) + .RETURN(false); + + TestType explorer{}; + REQUIRE_THAT(std::invoke(explorer, current, view, tracker), RangeEquals(std::array{DefaultEdge{44}})); +} + TEST_CASE("detail::BasicTraverser is not copyable but movable, when strategies support it.", "[graph][graph::traverser][graph::detail]") { STATIC_REQUIRE(!std::is_copy_constructible_v); From ab84ad82eadb030ded3a997d8ddcf9cab85c0ca6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 20:46:28 +0200 Subject: [PATCH 159/256] cleanup: remove obsolute node factory mocks --- tests/graph/Defines.hpp | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index fa6af05e2..de6a7b053 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -110,44 +110,6 @@ class TrackerMock MAKE_MOCK1(set_visited, bool(const Vertex&)); }; -template -class GenericBasicNodeFactoryMock -{ -public: - inline static constexpr bool trompeloeil_movable_mock = true; - - using node_type = Node; - using vertex_type = sg::node::vertex_t; - - MAKE_MOCK1(make_init_node, Node(const vertex_type&)); - MAKE_MOCK2(make_successor_node, node_type(const node_type&, const GenericBasicEdge&)); - - template Edge> - node_type make_successor_node(const node_type& current, const Edge& edge) - { - return make_successor_node(current, GenericBasicEdge{.vertex = sg::edge::destination(edge)}); - } -}; - -template -class GenericRankedNodeFactoryMock -{ -public: - inline static constexpr bool trompeloeil_movable_mock = true; - - using node_type = Node; - using vertex_type = sg::node::vertex_t; - - MAKE_MOCK1(make_init_node, Node(const vertex_type&)); - MAKE_MOCK2(make_successor_node, node_type(const node_type&, const GenericBasicEdge&)); - - template Edge> - node_type make_successor_node(const node_type& current, const Edge& edge) - { - return make_successor_node(current, GenericBasicEdge{.vertex = sg::edge::destination(edge)}); - } -}; - template class BasicViewMock { From 0fb495b82561d5242473d466d19da78cd06cf94e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 20:47:27 +0200 Subject: [PATCH 160/256] fix: graph::detail::BufferedKernel --- include/Simple-Utility/graph/Traverse.hpp | 29 ++++++---- tests/graph/Traverse.cpp | 64 ++++++++++++++++++++++- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 8b21720dc..d7bd55d41 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -112,9 +112,12 @@ namespace sl::graph::detail class BaseKernel { public: + [[nodiscard]] + explicit BaseKernel() = default; + [[nodiscard]] explicit constexpr BaseKernel( - NodeFactory nodeFactory = NodeFactory{} + NodeFactory nodeFactory ) noexcept(std::is_nothrow_move_constructible_v) : m_NodeFactory{std::move(nodeFactory)} { @@ -125,6 +128,12 @@ namespace sl::graph::detail return std::invoke(m_NodeFactory, vertex); } + [[nodiscard]] + constexpr const NodeFactory& node_factory() const & noexcept + { + return m_NodeFactory; + } + protected: SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; }; @@ -151,11 +160,8 @@ namespace sl::graph::detail constexpr auto operator ()(const Node& current, Edges&& edges) const { return std::forward(edges) - | std::views::transform([&](const auto& edge) { return std::invoke(m_NodeFactory, current, edge); }); + | std::views::transform([&](const auto& edge) { return std::invoke(Super::m_NodeFactory, current, edge); }); } - - private: - SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; }; template @@ -185,12 +191,13 @@ namespace sl::graph::detail results.reserve(std::ranges::size(edges)); } - return std::forward(edges) - | std::views::transform([&](const auto& edge) { return std::invoke(m_NodeFactory, current, edge); }); - } + std::ranges::transform( + std::forward(edges), + std::back_inserter(results), + [&](const auto& edge) { return std::invoke(Super::m_NodeFactory, current, edge); }); - private: - SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; + return results; + } }; #if (defined(__clang__) && __clang_major__ < 16) \ @@ -198,7 +205,7 @@ namespace sl::graph::detail using default_explorer_t = BufferedExplorer; template - using default_kernel_t = LazyKernel; + using default_kernel_t = BufferedKernel; #else using default_explorer_t = LazyExplorer; diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 1c721b941..a08fbdfa0 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -26,7 +26,6 @@ using DefaultEdge = GenericBasicEdge; using DefaultQueue = QueueMock; using DefaultTracker = TrackerMock>; using DefaultView = BasicViewMock>; -using DefaultNodeFactory = GenericBasicNodeFactoryMock; using DefaultTraverser = sg::detail::BasicTraverser< DefaultNode, DefaultView, @@ -67,6 +66,69 @@ TEMPLATE_TEST_CASE( REQUIRE_THAT(std::invoke(explorer, current, view, tracker), RangeEquals(std::array{DefaultEdge{44}})); } +namespace +{ + template + struct NodeFactoryMock + { + MAKE_CONST_MOCK1(MakeOrigin, Node(const sg::node::vertex_t& vertex)); + MAKE_CONST_MOCK2(MakeSuccessor, Node(const Node& current, const Edge& edge)); + + [[nodiscard]] + constexpr Node operator ()(const sg::node::vertex_t& vertex) const + { + return MakeOrigin(vertex); + } + + [[nodiscard]] + constexpr Node operator ()(const Node& current, const Edge& edge) const + { + return MakeSuccessor(current, edge); + } + }; +} + +TEMPLATE_TEST_CASE( + "Kernel implementations behave as expected.", + "[graph][graph::detail]", + (sg::detail::LazyKernel>), + (sg::detail::BufferedKernel>) +) +{ + using namespace Catch::Matchers; + + TestType kernel{}; + + SECTION("When creating origin.") + { + REQUIRE_CALL(kernel.node_factory(), MakeOrigin(42)) + .RETURN(DefaultNode{.vertex = 42}); + + REQUIRE(DefaultNode{.vertex = 42} == std::invoke(kernel, 42)); + } + + SECTION("When creating successor(s).") + { + constexpr DefaultNode current{.vertex = 42}; + const auto& [expected, edges] = GENERATE( + (table, std::vector>)({ + {{}, {}}, + {{{.vertex = 43}}, {{.destination = 43}}}, + {{{.vertex = 43}, {.vertex = 41}}, {{.destination = 43}, {.destination = 41}}} + })); + + std::vector> expectations{}; + for (const auto& edge : edges) + { + expectations.emplace_back( + NAMED_REQUIRE_CALL(kernel.node_factory(), MakeSuccessor(current, edge)) + .RETURN(DefaultNode{.vertex = sg::edge::destination(edge)})); + } + + REQUIRE_THAT(std::invoke(kernel, current, edges), RangeEquals(expected)); + } +} + TEST_CASE("detail::BasicTraverser is not copyable but movable, when strategies support it.", "[graph][graph::traverser][graph::detail]") { STATIC_REQUIRE(!std::is_copy_constructible_v); From 53fe14514bc5a52853ae540579afe89a3953f121 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 20:51:52 +0200 Subject: [PATCH 161/256] feat: SL_UTILITY_HAS_RANGES_VIEWS macro --- include/Simple-Utility/Config.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/Simple-Utility/Config.hpp b/include/Simple-Utility/Config.hpp index 0cd521887..65d5d4981 100644 --- a/include/Simple-Utility/Config.hpp +++ b/include/Simple-Utility/Config.hpp @@ -35,4 +35,9 @@ #define SL_UTILITY_HAS_STD_FORMAT #endif +#if not (defined(__clang__) && __clang_major__ < 16) \ + || (defined(__GNUG__) && __GNUG__ < 12) + #define SL_UTILITY_HAS_RANGES_VIEWS +#endif + #endif From 098cb22dc0f6d3b631d28b4e27636c883c1235f3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 20:52:06 +0200 Subject: [PATCH 162/256] test: enable test case if feature exist --- include/Simple-Utility/graph/Traverse.hpp | 11 +++++------ tests/graph/Traverse.cpp | 12 +++++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index d7bd55d41..b4e543ecd 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -200,17 +200,16 @@ namespace sl::graph::detail } }; -#if (defined(__clang__) && __clang_major__ < 16) \ - || (defined(__GNUG__) && __GNUG__ < 12) - using default_explorer_t = BufferedExplorer; +#ifdef SL_UTILITY_HAS_RANGES_VIEWS + using default_explorer_t = LazyExplorer; template - using default_kernel_t = BufferedKernel; + using default_kernel_t = LazyKernel; #else - using default_explorer_t = LazyExplorer; + using default_explorer_t = BufferedExplorer; template - using default_kernel_t = LazyKernel; + using default_kernel_t = BufferedKernel; #endif template < diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index a08fbdfa0..73fb709ff 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -88,11 +88,17 @@ namespace }; } -TEMPLATE_TEST_CASE( +using TestKernels = std::tuple< + sg::detail::LazyKernel> +#ifdef SL_UTILITY_HAS_RANGES_VIEWS + ,sg::detail::BufferedKernel> +#endif +>; + +TEMPLATE_LIST_TEST_CASE( "Kernel implementations behave as expected.", "[graph][graph::detail]", - (sg::detail::LazyKernel>), - (sg::detail::BufferedKernel>) + TestKernels ) { using namespace Catch::Matchers; From 25ff611459cd8bf83164f12cfd0162eba9323e79 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 20:56:51 +0200 Subject: [PATCH 163/256] fix: SL_UTILITY_HAS_RANGES_VIEWS macro --- include/Simple-Utility/Config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Simple-Utility/Config.hpp b/include/Simple-Utility/Config.hpp index 65d5d4981..785e6c81c 100644 --- a/include/Simple-Utility/Config.hpp +++ b/include/Simple-Utility/Config.hpp @@ -36,7 +36,7 @@ #endif #if not (defined(__clang__) && __clang_major__ < 16) \ - || (defined(__GNUG__) && __GNUG__ < 12) + || not (defined(__GNUG__) && __GNUG__ < 12) #define SL_UTILITY_HAS_RANGES_VIEWS #endif From 47a598928bce7d04b996bf61e0cf6ddd6a648c9a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 21:13:47 +0200 Subject: [PATCH 164/256] fix: add missing include --- include/Simple-Utility/graph/Node.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index c26ed8c5e..9f798aff6 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -14,6 +14,7 @@ #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Edge.hpp" +#include #include namespace sl::graph::customize From 4626b0e4cebf9f0e1ea631b42b5d06b63b0afc6a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 21:14:27 +0200 Subject: [PATCH 165/256] fix: add astar::detail::BufferedKernel and select appropriate --- include/Simple-Utility/graph/AStarSearch.hpp | 97 ++++++++++++++++++-- tests/graph/AStarSearch.cpp | 6 +- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index 8b9642a5d..64c731945 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -33,13 +33,16 @@ namespace sl::graph::astar::detail } template Heuristic, typename NodeFactory> - class LazyKernel + class BaseKernel { public: [[nodiscard]] - explicit constexpr LazyKernel( - Heuristic heuristic = Heuristic{}, - NodeFactory nodeFactory = NodeFactory{} + explicit BaseKernel() = default; + + [[nodiscard]] + explicit constexpr BaseKernel( + Heuristic heuristic, + NodeFactory nodeFactory ) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v) : m_Heuristic{std::move(heuristic)}, @@ -55,6 +58,34 @@ namespace sl::graph::astar::detail std::invoke(m_Heuristic, vertex)); } + [[nodiscard]] + constexpr const Heuristic& heuristic() const & noexcept + { + return m_Heuristic; + } + + [[nodiscard]] + constexpr const NodeFactory& node_factory() const & noexcept + { + return m_NodeFactory; + } + + protected: + SL_UTILITY_NO_UNIQUE_ADDRESS Heuristic m_Heuristic{}; + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; + }; + + template Heuristic, typename NodeFactory> + class LazyKernel + : public BaseKernel + { + private: + using Super = BaseKernel; + + public: + using Super::Super; + using Super::operator(); + template requires std::convertible_to< std::invoke_result_t< @@ -71,17 +102,65 @@ namespace sl::graph::astar::detail [&](const auto& edge) { return std::invoke( - m_NodeFactory, + Super::m_NodeFactory, current, edge, - std::invoke(m_Heuristic, edge::destination(edge))); + std::invoke(Super::m_Heuristic, edge::destination(edge))); }); } + }; + template Heuristic, typename NodeFactory> + class BufferedKernel + : public BaseKernel + { private: - SL_UTILITY_NO_UNIQUE_ADDRESS Heuristic m_Heuristic{}; - SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; + using Super = BaseKernel; + + public: + using Super::Super; + using Super::operator(); + + template + requires std::convertible_to< + std::invoke_result_t< + NodeFactory, + const Node&, + std::ranges::range_reference_t, + std::invoke_result_t>>, + Node> + [[nodiscard]] + constexpr auto operator ()(const Node& current, Edges&& edges) const + { + std::vector results{}; + if constexpr (std::ranges::sized_range) + { + results.reserve(std::ranges::size(edges)); + } + + std::ranges::transform( + std::forward(edges), + std::back_inserter(results), + [&](const auto& edge) + { + return std::invoke( + Super::m_NodeFactory, + current, + edge, + std::invoke(Super::m_Heuristic, edge::destination(edge))); + }); + + return results; + } }; + +#ifdef SL_UTILITY_HAS_RANGES_VIEWS + template + using default_kernel_t = LazyKernel; +#else + template + using default_kernel_t = BufferedKernel; +#endif } namespace sl::graph::astar @@ -205,7 +284,7 @@ namespace sl::graph::astar View, queue::CommonPriorityQueue, Tracker, - detail::LazyKernel< + detail::default_kernel_t< Node, Heuristic, NodeFactory>>>; diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index ccbf73362..44a48d822 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -82,10 +82,10 @@ TEST_CASE("astar::Range visits all reachable vertices.", "[graph][graph::astar]" std::tuple{View{}}, std::tuple{}, std::tuple{}, - std::tuple{Heuristic{destination}}, + std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, std::tuple{} }; - STATIC_CHECK(std::ranges::input_range); + //STATIC_CHECK(std::ranges::input_range); std::vector nodes{}; std::ranges::copy(range, std::back_inserter(nodes)); @@ -109,7 +109,7 @@ TEST_CASE("astar::Range node can be decorated with PredecessorNodeDecorator.", " std::tuple{View{}}, std::tuple{}, std::tuple{}, - std::tuple{Heuristic{destination}}, + std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, std::tuple{} }; STATIC_CHECK(std::ranges::input_range); From 04805242450f0cf52383ac37a4b529bb635467bb Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 21:19:05 +0200 Subject: [PATCH 166/256] test: disable test cases correctly --- tests/graph/AStarSearch.cpp | 2 +- tests/graph/Traverse.cpp | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index 44a48d822..fc7e947bd 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -85,7 +85,7 @@ TEST_CASE("astar::Range visits all reachable vertices.", "[graph][graph::astar]" std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, std::tuple{} }; - //STATIC_CHECK(std::ranges::input_range); + STATIC_CHECK(std::ranges::input_range); std::vector nodes{}; std::ranges::copy(range, std::back_inserter(nodes)); diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 73fb709ff..690fa89b3 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -38,11 +38,16 @@ using MovableTraverser = sg::detail::BasicTraverser< EmptyQueueStub, sg::tracker::Null>; -TEMPLATE_TEST_CASE( +using TestExplorers = std::tuple< +#ifdef SL_UTILITY_HAS_RANGES_VIEWS + sg::detail::LazyExplorer, +#endif + sg::detail::BufferedExplorer>; + +TEMPLATE_LIST_TEST_CASE( "Explorer implementations behave as expected.", "[graph][graph::detail]", - sg::detail::LazyExplorer, - sg::detail::BufferedExplorer + TestExplorers ) { using namespace Catch::Matchers; @@ -89,11 +94,10 @@ namespace } using TestKernels = std::tuple< - sg::detail::LazyKernel> #ifdef SL_UTILITY_HAS_RANGES_VIEWS - ,sg::detail::BufferedKernel> + sg::detail::LazyKernel>, #endif ->; + sg::detail::BufferedKernel>>; TEMPLATE_LIST_TEST_CASE( "Kernel implementations behave as expected.", From 956a0068cc74b2d3b9b1d9473ac89f7ce9d73237 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 21:38:41 +0200 Subject: [PATCH 167/256] fix: reformulate SL_UTILITY_HAS_RANGES_VIEWS --- include/Simple-Utility/Config.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/Simple-Utility/Config.hpp b/include/Simple-Utility/Config.hpp index 785e6c81c..01b33b0ba 100644 --- a/include/Simple-Utility/Config.hpp +++ b/include/Simple-Utility/Config.hpp @@ -35,8 +35,7 @@ #define SL_UTILITY_HAS_STD_FORMAT #endif -#if not (defined(__clang__) && __clang_major__ < 16) \ - || not (defined(__GNUG__) && __GNUG__ < 12) +#if defined(_MSC_VER) || (__cpp_lib_ranges >= 202110L) #define SL_UTILITY_HAS_RANGES_VIEWS #endif From 6e8412c5324861a2dc49549aead170ff5dea7bd0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 21:39:03 +0200 Subject: [PATCH 168/256] fix: actually remove disabled kernels and explorers --- include/Simple-Utility/graph/AStarSearch.hpp | 64 ++++++------ include/Simple-Utility/graph/Traverse.hpp | 103 ++++++++++--------- 2 files changed, 84 insertions(+), 83 deletions(-) diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index 64c731945..73817ad72 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -76,7 +76,7 @@ namespace sl::graph::astar::detail }; template Heuristic, typename NodeFactory> - class LazyKernel + class BufferedKernel : public BaseKernel { private: @@ -97,21 +97,31 @@ namespace sl::graph::astar::detail [[nodiscard]] constexpr auto operator ()(const Node& current, Edges&& edges) const { - return std::forward(edges) - | std::views::transform( - [&](const auto& edge) - { - return std::invoke( - Super::m_NodeFactory, - current, - edge, - std::invoke(Super::m_Heuristic, edge::destination(edge))); - }); + std::vector results{}; + if constexpr (std::ranges::sized_range) + { + results.reserve(std::ranges::size(edges)); + } + + std::ranges::transform( + std::forward(edges), + std::back_inserter(results), + [&](const auto& edge) + { + return std::invoke( + Super::m_NodeFactory, + current, + edge, + std::invoke(Super::m_Heuristic, edge::destination(edge))); + }); + + return results; } }; +#ifdef SL_UTILITY_HAS_RANGES_VIEWS template Heuristic, typename NodeFactory> - class BufferedKernel + class LazyKernel : public BaseKernel { private: @@ -132,29 +142,19 @@ namespace sl::graph::astar::detail [[nodiscard]] constexpr auto operator ()(const Node& current, Edges&& edges) const { - std::vector results{}; - if constexpr (std::ranges::sized_range) - { - results.reserve(std::ranges::size(edges)); - } - - std::ranges::transform( - std::forward(edges), - std::back_inserter(results), - [&](const auto& edge) - { - return std::invoke( - Super::m_NodeFactory, - current, - edge, - std::invoke(Super::m_Heuristic, edge::destination(edge))); - }); - - return results; + return std::forward(edges) + | std::views::transform( + [&](const auto& edge) + { + return std::invoke( + Super::m_NodeFactory, + current, + edge, + std::invoke(Super::m_Heuristic, edge::destination(edge))); + }); } }; -#ifdef SL_UTILITY_HAS_RANGES_VIEWS template using default_kernel_t = LazyKernel; #else diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index b4e543ecd..9d95f8cd7 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -28,40 +28,7 @@ namespace sl::graph::detail { - struct LazyExplorer - { - template - [[nodiscard]] - constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) const - { - return graph.edges(current) - | std::views::filter([&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); - } - }; - - struct BufferedExplorer - { - template - [[nodiscard]] - constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) - { - auto edges = graph.edges(current); - - std::vector> results{}; - if constexpr (std::ranges::sized_range) - { - results.reserve(std::ranges::size(edges)); - } - - std::ranges::copy_if( - std::move(edges), - std::back_inserter(results), - [&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); - - return results; - } - }; - + template struct NodeFactory; @@ -139,7 +106,7 @@ namespace sl::graph::detail }; template - class LazyKernel + class BufferedKernel : public BaseKernel { private: @@ -159,13 +126,58 @@ namespace sl::graph::detail [[nodiscard]] constexpr auto operator ()(const Node& current, Edges&& edges) const { - return std::forward(edges) - | std::views::transform([&](const auto& edge) { return std::invoke(Super::m_NodeFactory, current, edge); }); + std::vector results{}; + if constexpr (std::ranges::sized_range) + { + results.reserve(std::ranges::size(edges)); + } + + std::ranges::transform( + std::forward(edges), + std::back_inserter(results), + [&](const auto& edge) { return std::invoke(Super::m_NodeFactory, current, edge); }); + + return results; + } + }; + + struct BufferedExplorer + { + template + [[nodiscard]] + constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) + { + auto edges = graph.edges(current); + + std::vector> results{}; + if constexpr (std::ranges::sized_range) + { + results.reserve(std::ranges::size(edges)); + } + + std::ranges::copy_if( + std::move(edges), + std::back_inserter(results), + [&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); + + return results; + } + }; + +#ifdef SL_UTILITY_HAS_RANGES_VIEWS + struct LazyExplorer + { + template + [[nodiscard]] + constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) const + { + return graph.edges(current) + | std::views::filter([&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); } }; template - class BufferedKernel + class LazyKernel : public BaseKernel { private: @@ -185,22 +197,11 @@ namespace sl::graph::detail [[nodiscard]] constexpr auto operator ()(const Node& current, Edges&& edges) const { - std::vector results{}; - if constexpr (std::ranges::sized_range) - { - results.reserve(std::ranges::size(edges)); - } - - std::ranges::transform( - std::forward(edges), - std::back_inserter(results), - [&](const auto& edge) { return std::invoke(Super::m_NodeFactory, current, edge); }); - - return results; + return std::forward(edges) + | std::views::transform([&](const auto& edge) { return std::invoke(Super::m_NodeFactory, current, edge); }); } }; -#ifdef SL_UTILITY_HAS_RANGES_VIEWS using default_explorer_t = LazyExplorer; template From d781332c42e7f0e1546a47af5e8f236fe996a74c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 22:35:56 +0200 Subject: [PATCH 169/256] test: move concepts::edge_for test into Node.cpp --- tests/graph/Edge.cpp | 18 ------------------ tests/graph/Node.cpp | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 406e38145..3f84435da 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -213,21 +213,3 @@ TEMPLATE_TEST_CASE_SIG( { STATIC_REQUIRE(expected == sg::concepts::weighted_edge); } - -TEMPLATE_TEST_CASE_SIG( - "concepts::edge_for determines, whether the Edge type satisfies the minimal requirements of the Node type.", - "[graph][graph::concepts]", - ((bool expected, class Edge, class Node), expected, Edge, Node), - (false, GenericBasicEdge, GenericBasicNode), - (true, GenericBasicEdge, GenericBasicNode), - (true, (GenericWeightedEdge), GenericBasicNode), - - (false, GenericBasicEdge, (GenericRankedNode)), - (true, (GenericWeightedEdge), (GenericRankedNode)), - - (true, sg::CommonBasicEdge, sg::CommonBasicNode), - (true, sg::CommonWeightedEdge, sg::CommonRankedNode) -) -{ - STATIC_REQUIRE(expected == sg::concepts::edge_for); -} diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index 3d8e4f770..fbf77c1d2 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -261,3 +261,21 @@ TEST_CASE( STATIC_REQUIRE(std::same_as::rank_type>); STATIC_REQUIRE(std::same_as>); } + +TEMPLATE_TEST_CASE_SIG( + "concepts::edge_for determines, whether the Edge type satisfies the minimal requirements of the Node type.", + "[graph][graph::concepts]", + ((bool expected, class Edge, class Node), expected, Edge, Node), + (false, GenericBasicEdge, GenericBasicNode), + (true, GenericBasicEdge, GenericBasicNode), + (true, (GenericWeightedEdge), GenericBasicNode), + + (false, GenericBasicEdge, (GenericRankedNode)), + (true, (GenericWeightedEdge), (GenericRankedNode)), + + (true, sg::CommonBasicEdge, sg::CommonBasicNode), + (true, sg::CommonWeightedEdge, sg::CommonRankedNode) +) +{ + STATIC_REQUIRE(expected == sg::concepts::edge_for); +} From de4dde964eca16ebf6a119ab9710db7476aa6885 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 24 Sep 2023 22:36:09 +0200 Subject: [PATCH 170/256] fix: please msvc v142 --- include/Simple-Utility/graph/Node.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 9f798aff6..510a04ace 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -176,7 +176,9 @@ namespace sl::graph::concepts && vertex> && requires(const T& node) { - { node::vertex(node) } -> std::convertible_to>; + // ReSharper disable once CppRedundantTemplateKeyword + // ReSharper disable once CppRedundantTypenameKeyword + { node::vertex(node) } -> std::convertible_to>; // pleases msvc v142 }; template @@ -185,7 +187,9 @@ namespace sl::graph::concepts && rank> && requires(const Node& node) { - { node::rank(node) } -> std::convertible_to>; + // ReSharper disable once CppRedundantTemplateKeyword + // ReSharper disable once CppRedundantTypenameKeyword + { node::rank(node) } -> std::convertible_to>; // pleases msvc v142 }; template From 88bef8cf9723155ab802d6ce3e4b229372cd6ecd Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 19:01:02 +0200 Subject: [PATCH 171/256] feat: add explicit deduction guide for IterableTraverser --- include/Simple-Utility/graph/Traverse.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 9d95f8cd7..582236af7 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -433,6 +433,10 @@ namespace sl::graph private: Traverser m_Traverser; }; + + template + requires concepts::traverser> + IterableTraverser(Traverser&&) -> IterableTraverser; } #endif From 181e4c31198dcb83d545454c551da436e94048d0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 19:01:26 +0200 Subject: [PATCH 172/256] fix: explicity delete other begin overloads for IterableTraverser --- include/Simple-Utility/graph/Traverse.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 582236af7..746000afb 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -424,6 +424,8 @@ namespace sl::graph return Iterator{m_Traverser}; } + void begin() const & = delete; + [[nodiscard]] constexpr Sentinel end() const noexcept { From d9047bb6616fc1b9512e54e5cd3d85d44fe3a195 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 19:01:41 +0200 Subject: [PATCH 173/256] apply code style on Traverser.hpp --- include/Simple-Utility/graph/Traverse.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 746000afb..1e66f5bc1 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -28,7 +28,6 @@ namespace sl::graph::detail { - template struct NodeFactory; @@ -339,15 +338,15 @@ namespace sl::graph::concepts && requires(T& traverser) { typename T::node_type; - {!traverser.next()} -> std::convertible_to; - {*traverser.next()} -> std::convertible_to; + { !traverser.next() } -> std::convertible_to; + { *traverser.next() } -> std::convertible_to; }; } namespace sl::graph { template - class IterableTraverser final + class IterableTraverser { public: using node_type = typename Traverser::node_type; @@ -363,7 +362,7 @@ namespace sl::graph template requires std::constructible_from [[nodiscard]] - constexpr explicit IterableTraverser( + explicit constexpr IterableTraverser( TraverserArgs&&... traverserArgs ) noexcept(std::is_nothrow_constructible_v) : m_Traverser{std::forward(traverserArgs)...} From d33d5d7e00346d830c1a449edb2d872288b5c21e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 19:02:19 +0200 Subject: [PATCH 174/256] test: add IterableTraverser test cases --- tests/graph/Traverse.cpp | 89 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 690fa89b3..0bf1cf480 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -33,10 +33,23 @@ using DefaultTraverser = sg::detail::BasicTraverser< DefaultTracker>; using MovableTraverser = sg::detail::BasicTraverser< - DefaultNode, - EmptyViewStub, - EmptyQueueStub, - sg::tracker::Null>; + DefaultNode, + EmptyViewStub, + EmptyQueueStub, + sg::tracker::Null>; + +namespace +{ + template + struct TraverserMock + { + inline static constexpr bool trompeloeil_movable_mock = true; + + using node_type = Node; + + MAKE_MOCK0(next, std::optional()); + }; +} using TestExplorers = std::tuple< #ifdef SL_UTILITY_HAS_RANGES_VIEWS @@ -125,7 +138,7 @@ TEMPLATE_LIST_TEST_CASE( {{}, {}}, {{{.vertex = 43}}, {{.destination = 43}}}, {{{.vertex = 43}, {.vertex = 41}}, {{.destination = 43}, {.destination = 41}}} - })); + })); std::vector> expectations{}; for (const auto& edge : edges) @@ -139,7 +152,10 @@ TEMPLATE_LIST_TEST_CASE( } } -TEST_CASE("detail::BasicTraverser is not copyable but movable, when strategies support it.", "[graph][graph::traverser][graph::detail]") +TEST_CASE( + "detail::BasicTraverser is not copyable but movable, when strategies support it.", + "[graph][graph::traverser][graph::detail]" +) { STATIC_REQUIRE(!std::is_copy_constructible_v); STATIC_REQUIRE(!std::is_copy_assignable_v); @@ -151,7 +167,8 @@ TEMPLATE_TEST_CASE( "detail::BasicTraverser satisfies graph::concepts::traverser.", "[graph][graph::traverser][graph::concepts]", MovableTraverser, - DefaultTraverser + DefaultTraverser, + TraverserMock ) { STATIC_REQUIRE(sg::concepts::traverser); @@ -165,7 +182,7 @@ TEST_CASE("detail::BasicTraverser can be constructed with an origin.", "[graph][ const auto origin = GENERATE(take(5, random(std::numeric_limits::min(), std::numeric_limits::max()))); DefaultQueue queue{}; - ALLOW_CALL(queue, empty()) // internal assertion + ALLOW_CALL(queue, empty()) // internal assertion .RETURN(true); REQUIRE_CALL(queue, do_insert(matches(RangeEquals(std::array{DefaultNode{origin}})))); @@ -198,7 +215,7 @@ TEST_CASE("detail::BasicTraverser::next returns the current node, or std::nullop .RETURN(true); DefaultQueue queue{}; - ALLOW_CALL(queue, empty()) // internal assertion + ALLOW_CALL(queue, empty()) // internal assertion .RETURN(true); REQUIRE_CALL(queue, do_insert(matches(RangeEquals(std::array{originNode})))); @@ -249,3 +266,57 @@ TEST_CASE("detail::BasicTraverser::next returns the current node, or std::nullop REQUIRE(std::nullopt == traverser.next()); } } + +TEST_CASE("graph::IterableTraverser can be used as a range.", "[graph][graph::traverser]") +{ + SECTION("Is empty, when traverser returned std::nullopt.") + { + TraverserMock traverser{}; + REQUIRE_CALL(traverser, next()) + .RETURN(std::nullopt); + + sg::IterableTraverser range{std::move(traverser)}; + STATIC_REQUIRE(std::ranges::input_range); + + REQUIRE(0 == std::ranges::distance(range)); + } + + SECTION("Returns nodes as given from traverser.") + { + TraverserMock traverser{}; + trompeloeil::sequence seq{}; + REQUIRE_CALL(traverser, next()) + .RETURN(DefaultNode{.vertex = 42}) + .IN_SEQUENCE(seq); + + REQUIRE_CALL(traverser, next()) + .RETURN(DefaultNode{.vertex = 41}) + .IN_SEQUENCE(seq); + + REQUIRE_CALL(traverser, next()) + .RETURN(DefaultNode{.vertex = -42}) + .IN_SEQUENCE(seq); + + REQUIRE_CALL(traverser, next()) + .RETURN(std::nullopt) + .IN_SEQUENCE(seq); + + sg::IterableTraverser range{std::move(traverser)}; + STATIC_REQUIRE(std::ranges::input_range); + + REQUIRE_THAT(range, Catch::Matchers::RangeEquals(std::to_array({{42}, {41}, {-42}}))); + } +} + +// Test case needs to be a template, because the negative requires statements will fail. +TEMPLATE_TEST_CASE( + "graph::IterableTraverser::begin can only be called from an lvalue-ref.", + "[graph][graph::traverser]", + sg::IterableTraverser> +) +{ + STATIC_REQUIRE(requires{ {std::declval().begin()} -> std::input_iterator; }); + STATIC_REQUIRE(!requires{std::declval().begin(); }); + STATIC_REQUIRE(!requires{std::declval().begin(); }); + STATIC_REQUIRE(!requires{std::declval().begin(); }); +} From c27b0a78a74dd0842d1a75abdd5a026d5e36cef1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 19:13:09 +0200 Subject: [PATCH 175/256] refactor: graph::concepts::queue_for test range --- include/Simple-Utility/graph/Queue.hpp | 47 ++++++-------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 61cff830c..facef55c9 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -157,63 +157,38 @@ namespace sl::graph::queue inline constexpr detail::next_fn next{}; } -#if __clang__ && __clang_major__ < 16 - -#include - -namespace sl::graph::queue::detail -{ - template - using dummy_input_range = std::forward_list; -} - -#else - namespace sl::graph::queue::detail { - /* This defines a dummy input_iterator, which we need to declare a input_range, which is then used - * to validate the graph::insert conformity in the queue_for concept. - */ template - struct input_range_maker + struct dummy_input_range { - struct input_iterator + // ReSharper disable CppFunctionIsNotImplemented + struct iterator { using iterator_concept = std::input_iterator_tag; using element_type = T; using difference_type = std::ptrdiff_t; - T* dummy{}; - - // ReSharper disable once CppFunctionIsNotImplemented - T& operator *() const { return *dummy; } - - // ReSharper disable once CppFunctionIsNotImplemented - input_iterator& operator ++(); - // ReSharper disable once CppFunctionIsNotImplemented + T& operator *() const; + iterator& operator ++(); void operator ++(int); - bool operator==(const input_iterator&) const = default; + bool operator==(const iterator&) const; }; - static_assert(std::input_iterator); - static_assert(!std::forward_iterator); - - using type = std::ranges::subrange; + iterator begin(); + iterator end(); + // ReSharper restore CppFunctionIsNotImplemented }; - template - using dummy_input_range = typename input_range_maker::type; + static_assert(std::ranges::input_range>); } -#endif - namespace sl::graph::concepts { template concept queue_for = sl::concepts::unqualified && basic_node - // ReSharper disable once CppRedundantTemplateKeyword - && requires(T& queue, queue::detail::template dummy_input_range inputRange) + && requires(T& queue, queue::detail::dummy_input_range inputRange) { { queue::empty(std::as_const(queue)) } -> std::convertible_to; queue::insert(queue, inputRange); From 2c28c3840490f25de8880d3ae0b765756259809a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 19:19:07 +0200 Subject: [PATCH 176/256] fix: please msvc v142 --- include/Simple-Utility/graph/Queue.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index facef55c9..596228d5e 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -188,7 +188,8 @@ namespace sl::graph::concepts template concept queue_for = sl::concepts::unqualified && basic_node - && requires(T& queue, queue::detail::dummy_input_range inputRange) + // ReSharper disable once CppRedundantTemplateKeyword // pleases msvc v142 + && requires(T& queue, queue::detail::template dummy_input_range inputRange) { { queue::empty(std::as_const(queue)) } -> std::convertible_to; queue::insert(queue, inputRange); From dfe45b0b018d5cb4fa3c9734374a4c4ea0e3c547 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 19:23:58 +0200 Subject: [PATCH 177/256] fix: please test workflow --- include/Simple-Utility/graph/Queue.hpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 596228d5e..911d2a1cf 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -159,6 +159,7 @@ namespace sl::graph::queue namespace sl::graph::queue::detail { + // GCOVR_EXCL_START template struct dummy_input_range { @@ -169,10 +170,14 @@ namespace sl::graph::queue::detail using element_type = T; using difference_type = std::ptrdiff_t; - T& operator *() const; - iterator& operator ++(); - void operator ++(int); - bool operator==(const iterator&) const; + T operator *() const { throw std::runtime_error{"Dummy"}; } + + iterator& operator ++() { return *this; } + + // ReSharper disable once CppDiscardedPostfixOperatorResult + void operator ++(int) { (*this)++; } + + bool operator==(const iterator&) const = default; }; iterator begin(); @@ -181,6 +186,8 @@ namespace sl::graph::queue::detail }; static_assert(std::ranges::input_range>); + + // GCOVR_EXCL_END } namespace sl::graph::concepts From b8d5045a09db04f3498df8b5c53369111e989304 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 19:32:28 +0200 Subject: [PATCH 178/256] fix: please test workflow no. 2 --- include/Simple-Utility/graph/Queue.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 911d2a1cf..4b9e75d61 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -163,7 +163,6 @@ namespace sl::graph::queue::detail template struct dummy_input_range { - // ReSharper disable CppFunctionIsNotImplemented struct iterator { using iterator_concept = std::input_iterator_tag; @@ -180,9 +179,9 @@ namespace sl::graph::queue::detail bool operator==(const iterator&) const = default; }; - iterator begin(); - iterator end(); - // ReSharper restore CppFunctionIsNotImplemented + static iterator begin() { return {}; } + + static iterator end() { return {}; } }; static_assert(std::ranges::input_range>); From 989c78b79e02ee96fd9619e2ae48e19e3350e05e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 20:08:08 +0200 Subject: [PATCH 179/256] fix: try please coverage reporter workflow --- include/Simple-Utility/graph/Queue.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 4b9e75d61..6f935187f 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -159,7 +159,8 @@ namespace sl::graph::queue namespace sl::graph::queue::detail { - // GCOVR_EXCL_START + // LCOV_EXCL_START + template struct dummy_input_range { @@ -186,7 +187,7 @@ namespace sl::graph::queue::detail static_assert(std::ranges::input_range>); - // GCOVR_EXCL_END + // LCOV_EXCL_END } namespace sl::graph::concepts From 63b4a37984aba48b60726855eca63df82f0113e3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 20:32:42 +0200 Subject: [PATCH 180/256] fix: define exclude-pattern-prefix in coverage workflow --- .github/workflows/coverage.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ece1470c1..e84ae81b4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -56,7 +56,11 @@ jobs: - name: Run gcovr run: | gcovr --root ${{env.TESTS_DIR}} --filter include/Simple-Utility --keep -j 4 \ - --exclude-lines-by-pattern "\s*assert\(" --exclude-unreachable-branches --exclude-noncode-lines --exclude-throw-branches \ + --exclude-lines-by-pattern "\s*assert\(" \ + --exclude-pattern-prefix "\s*(LCOV|GCOV|GCOVR)" \ + --exclude-unreachable-branches \ + --exclude-noncode-lines \ + --exclude-throw-branches \ --cobertura ${{env.COBERTURA_REPORT}} --cobertura-pretty \ --html-nested ${{env.HTML_REPORT_DIR}} --html-title "Simple-Utility Coverage Report" \ --coveralls ${{env.COVERALLS_REPORT}} --coveralls-pretty From 65620fdfe0461f00e553128597634932331acfb1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 30 Sep 2023 20:39:03 +0200 Subject: [PATCH 181/256] fix: revert previous change and fix the damn stop token --- .github/workflows/coverage.yml | 1 - include/Simple-Utility/graph/Queue.hpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index e84ae81b4..8e1379021 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -57,7 +57,6 @@ jobs: run: | gcovr --root ${{env.TESTS_DIR}} --filter include/Simple-Utility --keep -j 4 \ --exclude-lines-by-pattern "\s*assert\(" \ - --exclude-pattern-prefix "\s*(LCOV|GCOV|GCOVR)" \ --exclude-unreachable-branches \ --exclude-noncode-lines \ --exclude-throw-branches \ diff --git a/include/Simple-Utility/graph/Queue.hpp b/include/Simple-Utility/graph/Queue.hpp index 6f935187f..28a345793 100644 --- a/include/Simple-Utility/graph/Queue.hpp +++ b/include/Simple-Utility/graph/Queue.hpp @@ -187,7 +187,7 @@ namespace sl::graph::queue::detail static_assert(std::ranges::input_range>); - // LCOV_EXCL_END + // LCOV_EXCL_STOP } namespace sl::graph::concepts From ed21b89dac8e55bd84b84fb4bd8d9eacfcd0ac44 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 1 Oct 2023 13:08:43 +0200 Subject: [PATCH 182/256] ci: add decisions flag to gcovr --- .github/workflows/coverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8e1379021..5dd2bcbf6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -60,6 +60,7 @@ jobs: --exclude-unreachable-branches \ --exclude-noncode-lines \ --exclude-throw-branches \ + --decisions \ --cobertura ${{env.COBERTURA_REPORT}} --cobertura-pretty \ --html-nested ${{env.HTML_REPORT_DIR}} --html-title "Simple-Utility Coverage Report" \ --coveralls ${{env.COVERALLS_REPORT}} --coveralls-pretty From 3608af6ba275bf3f2fbe44b801066b4906eb3361 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 1 Oct 2023 13:49:53 +0200 Subject: [PATCH 183/256] feat: DepthNodeDecorator --- include/Simple-Utility/graph/Formatter.hpp | 22 +++++++++ include/Simple-Utility/graph/Node.hpp | 52 +++++++++++++++++++++- tests/graph/Formatter.cpp | 27 ++++++++--- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/include/Simple-Utility/graph/Formatter.hpp b/include/Simple-Utility/graph/Formatter.hpp index 7c5d82bf4..dd01008f9 100644 --- a/include/Simple-Utility/graph/Formatter.hpp +++ b/include/Simple-Utility/graph/Formatter.hpp @@ -74,6 +74,28 @@ namespace sl::graph private: SL_UTILITY_NO_UNIQUE_ADDRESS NodeFormatter m_Formatter{}; }; + + template + class NodeFormatter, Char> + { + public: + constexpr auto parse(std::basic_format_parse_context& ctx) noexcept + { + return m_Formatter.parse(ctx); + } + + template + auto format(const DepthNodeDecorator& node, FormatContext& ctx) const + { + return std::format_to( + m_Formatter.format(node, ctx), + ", depth: {}", + node.depth); + } + + private: + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFormatter m_Formatter{}; + }; } template diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 510a04ace..0e3300fca 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -227,7 +227,7 @@ namespace sl::graph rank_type rank; [[nodiscard]] - friend bool operator==(const CommonRankedNode&, const CommonRankedNode&) = default; + friend bool operator ==(const CommonRankedNode&, const CommonRankedNode&) = default; }; template @@ -239,7 +239,7 @@ namespace sl::graph std::optional predecessor{}; [[nodiscard]] - friend bool operator==(const PredecessorNodeDecorator&, const PredecessorNodeDecorator&) = default; + friend bool operator ==(const PredecessorNodeDecorator&, const PredecessorNodeDecorator&) = default; }; template typename BaseNodeFactory> @@ -280,6 +280,54 @@ namespace sl::graph return node; } }; + + template + struct DepthNodeDecorator + : public Node + { + using vertex_type = node::vertex_t; + + int depth{}; + + [[nodiscard]] + friend bool operator ==(const DepthNodeDecorator&, const DepthNodeDecorator&) = default; + }; + + template typename BaseNodeFactory> + class NodeFactoryDecorator, BaseNodeFactory> + : private BaseNodeFactory + { + private: + using Super = BaseNodeFactory; + + public: + using node_type = DepthNodeDecorator; + using vertex_type = node::vertex_t; + + template + [[nodiscard]] + constexpr node_type operator ()(vertex_type origin, Args&&... args) const + { + // leave code as-is, because directly returning the temporary results in an ICE on gcc10 + node_type node{ + {std::invoke(static_cast(*this), std::move(origin), std::forward(args)...)}, + {0} + }; + return node; + } + + template Edge, typename... Args> + [[nodiscard]] + constexpr node_type operator ()(const node_type& current, const Edge& edge, Args&&... args) const + { + // leave code as-is, because directly returning the temporary results in an ICE on gcc10 + node_type node{ + {std::invoke(static_cast(*this), current, edge, std::forward(args)...)}, + {current.depth + 1} + }; + return node; + } + }; } #endif diff --git a/tests/graph/Formatter.cpp b/tests/graph/Formatter.cpp index 8edacf911..7ba87f198 100644 --- a/tests/graph/Formatter.cpp +++ b/tests/graph/Formatter.cpp @@ -1,3 +1,8 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + #include "Simple-Utility/graph/Formatter.hpp" #include @@ -6,40 +11,48 @@ #ifdef SL_UTILITY_HAS_STD_FORMAT -TEST_CASE("node types can be formatted.", "[graph][graph::node]") +TEST_CASE("node types can be formatted.", "[graph][graph::node][graph::format]") { using TestType = GenericBasicNode; REQUIRE("{vertex: Hello, World!}" == std::format("{}", TestType{.vertex = "Hello, World!"})); } -TEST_CASE("ranked_node types can be formatted.", "[graph][graph::node]") +TEST_CASE("ranked_node types can be formatted.", "[graph][graph::node][graph::format]") { using TestType = GenericRankedNode; REQUIRE("{vertex: Hello, World!, rank: 42}" == std::format("{}", TestType{.vertex = "Hello, World!", .rank = 42})); } -TEST_CASE("decorated ranked_node types can be formatted.", "[graph][graph::node]") +TEST_CASE("predecessor decorated ranked_node types can be formatted.", "[graph][graph::node][graph::format]") { using TestType = sg::PredecessorNodeDecorator>; REQUIRE("{vertex: 42, rank: 1337, predecessor: 41}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, "41"})); - REQUIRE("{vertex: 42, rank: 1337, predecessor: null}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, std::nullopt})); + REQUIRE( + "{vertex: 42, rank: 1337, predecessor: null}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, std::nullopt})); +} + +TEST_CASE("depth decorated ranked_node types can be formatted.", "[graph][graph::node][graph::format]") +{ + using TestType = sg::DepthNodeDecorator>; + + REQUIRE("{vertex: -42, rank: 1337, depth: 42}" == std::format("{}", TestType{{.vertex = "-42", .rank = 1337}, 42})); } -TEST_CASE("edge types can be formatted.", "[graph][graph::edge]") +TEST_CASE("edge types can be formatted.", "[graph][graph::edge][graph::format]") { using TestType = GenericBasicEdge; REQUIRE("{destination: Hello, World!}" == std::format("{}", TestType{.destination = "Hello, World!"})); } -TEST_CASE("weighted_edge types can be formatted.", "[graph][graph::edge]") +TEST_CASE("weighted_edge types can be formatted.", "[graph][graph::edge][graph::format]") { using TestType = GenericWeightedEdge; REQUIRE("{destination: Hello, World!, weight: 42}" == std::format("{}", TestType{.destination = "Hello, World!", .weight = 42})); } -#endif \ No newline at end of file +#endif From 1ac1905368f68b8c83376eeff100492b23347651 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 1 Oct 2023 15:32:06 +0200 Subject: [PATCH 184/256] fix: make detail::NodeFactory accept all node decorators --- include/Simple-Utility/graph/Traverse.hpp | 39 ++++++++--------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 1e66f5bc1..4ee7d758e 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -9,9 +9,7 @@ #pragma once #include "Simple-Utility/Config.hpp" -#include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/functional/Tuple.hpp" -#include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Edge.hpp" #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Queue.hpp" @@ -44,33 +42,24 @@ namespace sl::graph::detail [[nodiscard]] constexpr Node operator ()([[maybe_unused]] const Node& predecessor, const Edge& edge) const { - return Node{edge::destination(edge)}; + if constexpr (concepts::ranked_node) + { + return Node{ + edge::destination(edge), + node::rank(predecessor) + edge::weight(edge) + }; + } + else + { + return Node{edge::destination(edge)}; + } } }; - template + template + requires requires { typename NodeFactoryDecorator::node_type; } struct NodeFactory - { - [[nodiscard]] - constexpr Node operator ()(const node::vertex_t& vertex) const - { - return Node{vertex}; - } - - template Edge> - [[nodiscard]] - constexpr Node operator ()(const Node& predecessor, const Edge& edge) const - { - return Node{ - edge::destination(edge), - node::rank(predecessor) + edge::weight(edge) - }; - } - }; - - template - struct NodeFactory> - : public NodeFactoryDecorator, NodeFactory> + : public NodeFactoryDecorator { }; From 653eb344e737901fd0e78ab0a2cf666ce4017efa Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 1 Oct 2023 15:32:27 +0200 Subject: [PATCH 185/256] test: add ViewStubs --- tests/graph/Defines.hpp | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index de6a7b053..151976536 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -141,3 +141,69 @@ class EmptyViewStub return {}; } }; + +inline static const std::unordered_map> graph{ + {1, {2, 3}}, + {2, {6}}, + {3, {5, 6}}, + {5, {5}}, + {6, {2}}, + + // begin isolated sub-graph + {4, {7}}, + {7, {4, 7, 9}}, + {8, {7, 9}}, + {9, {4}} +}; + +struct BasicViewStub +{ + using edge_type = sg::CommonBasicEdge; + + template + requires sg::concepts::edge_for + static std::vector edges(const Node& current) + { + const auto& vertices = graph.at(std::stoi(sg::node::vertex(current))); + std::vector infos{}; + infos.reserve(std::ranges::size(vertices)); + std::ranges::transform( + vertices, + std::back_inserter(infos), + [](const int v) { return edge_type{.destination = std::to_string(v)}; }); + return infos; + } +}; + +struct WeightedViewStub +{ + using edge_type = sg::CommonWeightedEdge; + + template + requires sg::concepts::edge_for + static std::vector edges(const Node& current) + { + const auto& vertices = graph.at(std::stoi(sg::node::vertex(current))); + std::vector infos{}; + infos.reserve(std::ranges::size(vertices)); + std::ranges::transform( + vertices, + std::back_inserter(infos), + [&](const int v) + { + return edge_type{ + .destination = std::to_string(v), + .weight = std::abs(v - std::stoi(current.vertex)) + }; + }); + return infos; + } +}; + +template +constexpr auto buffer_nodes(Range& range) +{ + std::vector nodes{}; + std::ranges::copy(range, std::back_inserter(nodes)); + return nodes; +} From 483f28772d0eb525811d2f75f84583053910926c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 1 Oct 2023 15:32:54 +0200 Subject: [PATCH 186/256] feat: graph::bfs namespace --- .../graph/BreadthFirstSearch.hpp | 38 +++++++ tests/graph/BreadthFirstSearch.cpp | 100 ++++++++++++++++++ tests/graph/CMakeLists.txt | 1 + 3 files changed, 139 insertions(+) create mode 100644 include/Simple-Utility/graph/BreadthFirstSearch.hpp create mode 100644 tests/graph/BreadthFirstSearch.cpp diff --git a/include/Simple-Utility/graph/BreadthFirstSearch.hpp b/include/Simple-Utility/graph/BreadthFirstSearch.hpp new file mode 100644 index 000000000..d88145bf3 --- /dev/null +++ b/include/Simple-Utility/graph/BreadthFirstSearch.hpp @@ -0,0 +1,38 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_BREADTH_FIRST_SEARCH_HPP +#define SIMPLE_UTILITY_GRAPH_BREADTH_FIRST_SEARCH_HPP + +#pragma once + +#include "Simple-Utility/graph/Traverse.hpp" +#include "Simple-Utility/graph/mixins/queue/std_queue.hpp" +#include "Simple-Utility/graph/mixins/tracker/std_unordered_map.hpp" + +namespace sl::graph::dfs +{ + template + struct NodeFactory + : public detail::NodeFactory + { + }; + + template < + class View, + concepts::basic_node Node = CommonBasicNode>>, + concepts::tracker_for> Tracker = tracker::CommonHashMap>> + requires concepts::view_for + && (!concepts::ranked_node) + using Range = IterableTraverser< + detail::BasicTraverser< + Node, + View, + queue::CommonQueue, + Tracker, + detail::default_kernel_t>>>; +} + +#endif diff --git a/tests/graph/BreadthFirstSearch.cpp b/tests/graph/BreadthFirstSearch.cpp new file mode 100644 index 000000000..ae4db0d0e --- /dev/null +++ b/tests/graph/BreadthFirstSearch.cpp @@ -0,0 +1,100 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include "Simple-Utility/graph/BreadthFirstSearch.hpp" + +#include +#include +#include + +#include "Defines.hpp" + +TEMPLATE_TEST_CASE( + "bfs::Range visits all reachable vertices.", + "[graph][graph::bfs]", + BasicViewStub, + WeightedViewStub +) +{ + using Node = sg::CommonBasicNode; + const auto& [expected, origin] = GENERATE( + (table, std::string>)({ + {{{"3"}, {"5"}, {"6"}, {"2"}}, "3"}, + {{{"6"}, {"2"}}, "6"}, + {{{"1"}, {"2"}, {"3"}, {"6"}, {"5"}}, "1"}, + {{{"8"}, {"7"}, {"9"}, {"4"}}, "8"} + })); + + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); + + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); +} + +TEMPLATE_TEST_CASE( + "bfs::Range can be used with depth decorated nodes.", + "[graph][graph::bfs]", + BasicViewStub, + WeightedViewStub +) +{ + using Node = sg::DepthNodeDecorator>; + const auto& [expected, origin] = GENERATE( + (table, std::string>)({ + {{{{"3"}, 0}, {{"5"}, 1}, {{"6"}, 1}, {{"2"}, 2}}, "3"}, + {{{{"6"}, 0}, {{"2"}, 1}}, "6"}, + {{{{"1"}, 0}, {{"2"}, 1}, {{"3"}, 1}, {{"6"}, 2}, {{"5"}, 2}}, "1"}, + {{{{"8"}, 0}, {{"7"}, 1}, {{"9"}, 1}, {{"4"}, 2}}, "8"} + })); + + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); + + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); +} + +TEMPLATE_TEST_CASE( + "bfs::Range can be used with predecessor decorated nodes.", + "[graph][graph::bfs]", + BasicViewStub, + WeightedViewStub +) +{ + using Node = sg::PredecessorNodeDecorator>; + const auto& [expected, origin] = GENERATE( + (table, std::string>)({ + {{{{"3"}, std::nullopt}, {{"5"}, "3"}, {{"6"}, "3"}, {{"2"}, "6"}}, "3"}, + {{{{"6"}, std::nullopt}, {{"2"}, "6"}}, "6"}, + {{{{"1"}, std::nullopt}, {{"2"}, "1"}, {{"3"}, "1"}, {{"6"}, "2"}, {{"5"}, "3"}}, "1"}, + {{{{"8"}, std::nullopt}, {{"7"}, "8"}, {{"9"}, "8"}, {{"4"}, "7"}}, "8"} + })); + + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); + + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); +} + +TEMPLATE_TEST_CASE( + "bfs::Range can be used with arbitrary decorated nodes.", + "[graph][graph::bfs]", + BasicViewStub, + WeightedViewStub +) +{ + using Node = sg::DepthNodeDecorator>>; + const auto& [expected, origin] = GENERATE( + (table, std::string>)({ + {{{{{"3"}, std::nullopt}, 0}, {{{"5"}, "3"}, 1}, {{{"6"}, "3"}, 1}, {{{"2"}, "6"}, 2}}, "3"}, + {{{{{"6"}, std::nullopt}, 0}, {{{"2"}, "6"}, 1}}, "6"}, + {{{{{"1"}, std::nullopt}, 0}, {{{"2"}, "1"}, 1}, {{{"3"}, "1"}, 1}, {{{"6"}, "2"}, 2}, {{{"5"}, "3"}, 2}}, "1"}, + {{{{{"8"}, std::nullopt}, 0}, {{{"7"}, "8"}, 1}, {{{"9"}, "8"}, 1}, {{{"4"}, "7"}, 2}}, "8"} + })); + + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); + + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); +} diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 0219c76e5..0d7923cf2 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -2,6 +2,7 @@ target_sources( Simple-Utility-Tests PRIVATE "AStarSearch.cpp" + "BreadthFirstSearch.cpp" "Common.cpp" "DepthFirstSearch.cpp" "Edge.cpp" From e29e4d0cead1a6260cd0a4196c6a365b6a89cb4d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 2 Oct 2023 19:18:36 +0200 Subject: [PATCH 187/256] test: extend dfs test cases --- tests/graph/Defines.hpp | 47 +++++++++++++ tests/graph/DepthFirstSearch.cpp | 113 ++++++++++++++++++------------- 2 files changed, 114 insertions(+), 46 deletions(-) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 151976536..d0959f039 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -207,3 +207,50 @@ constexpr auto buffer_nodes(Range& range) std::ranges::copy(range, std::back_inserter(nodes)); return nodes; } + +template +constexpr auto convert_test_expectations(const Range& range, Transform transform) +{ + using TargetNode = std::invoke_result_t< + Transform, + std::ranges::range_value_t>>>; + std::vector< + std::tuple< + std::vector, + std::tuple_element_t<1, std::ranges::range_value_t>>> results{}; + std::ranges::transform( + range, + std::back_inserter(results), + [&](const auto& tuple) + { + std::vector nodes{}; + std::ranges::transform(std::get<0>(tuple), std::back_inserter(nodes), transform); + return std::tuple{ + std::move(nodes), + std::get<1>(tuple) + }; + }); + + return results; +} + +constexpr auto toCommonBasicNode = [](const sg::concepts::basic_node auto& node) +{ + return sg::CommonBasicNode{sg::node::vertex(node)}; +}; + +constexpr auto toDepthBasicNode = [](const sg::DepthNodeDecorator& node) +{ + return sg::DepthNodeDecorator>>{ + {toCommonBasicNode(node)}, + node.depth + }; +}; + +constexpr auto toPredecessorBasicNode = [](const sg::PredecessorNodeDecorator& node) +{ + return sg::PredecessorNodeDecorator>>{ + {toCommonBasicNode(node)}, + node.predecessor + }; +}; diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 0f2101815..0b26afa36 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -7,61 +7,82 @@ #include #include +#include #include #include "Defines.hpp" -namespace +inline const std::vector< + std::tuple< + std::vector>>>, + std::string + >> testResults{ + {{{{{"3"}, std::nullopt}, 0}, {{{"6"}, "3"}, 1}, {{{"2"}, "6"}, 2}, {{{"5"}, "3"}, 1}}, "3"}, + {{{{{"6"}, std::nullopt}, 0}, {{{"2"}, "6"}, 1}}, "6"}, + {{{{{"1"}, std::nullopt}, 0}, {{{"3"}, "1"}, 1}, {{{"6"}, "3"}, 2}, {{{"2"}, "6"}, 3}, {{{"5"}, "3"}, 2}}, "1"}, + {{{{{"8"}, std::nullopt}, 0}, {{{"9"}, "8"}, 1}, {{{"4"}, "9"}, 2}, {{{"7"}, "4"}, 3}}, "8"} +}; + +TEMPLATE_TEST_CASE( + "dfs::Range visits all reachable vertices.", + "[graph][graph::dfs]", + BasicViewStub, + WeightedViewStub +) +{ + using Node = sg::CommonBasicNode; + const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toCommonBasicNode))); + + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); + + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); +} + +TEMPLATE_TEST_CASE( + "dfs::Range can be used with depth decorated nodes.", + "[graph][graph::dfs]", + BasicViewStub, + WeightedViewStub +) { - struct View - { - inline static const std::unordered_map> graph{ - {1, {2, 3}}, - {2, {6}}, - {3, {5, 6}}, - {5, {5}}, - {6, {2}}, - - // begin isolated sub-graph - {4, {7}}, - {7, {4, 7, 9}}, - {8, {7, 9}}, - {9, {4}} - }; - - using edge_type = sg::CommonBasicEdge; - - template - requires sg::concepts::edge_for - static std::vector edges(const Node& current) - { - const auto& vertices = graph.at(sg::node::vertex(current)); - std::vector infos{}; - infos.reserve(std::ranges::size(vertices)); - std::ranges::transform( - vertices, - std::back_inserter(infos), - [](const int v) { return edge_type{.destination = v}; }); - return infos; - } - }; + using Node = sg::DepthNodeDecorator>; + const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toDepthBasicNode))); + + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); + + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); } -TEST_CASE("dfs::Range visits all reachable vertices.", "[graph][graph::dfs]") +TEMPLATE_TEST_CASE( + "dfs::Range can be used with predecessor decorated nodes.", + "[graph][graph::dfs]", + BasicViewStub, + WeightedViewStub +) { - const auto& [expected, origin] = GENERATE( - (table>, int>)({ - {{{3}, {6}, {2}, {5}}, 3}, - {{{6}, {2}}, 6}, - {{{1}, {3}, {6}, {2}, {5}}, 1}, - {{{8}, {9}, {4}, {7}}, 8}, - })); - - sg::dfs::Range range{origin, std::tuple{View{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + using Node = sg::PredecessorNodeDecorator>; + const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toPredecessorBasicNode))); + + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); - std::vector> nodes{}; - std::ranges::copy(range, std::back_inserter(nodes)); + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); +} + +TEMPLATE_TEST_CASE( + "dfs::Range can be used with arbitrary decorated nodes.", + "[graph][graph::dfs]", + BasicViewStub, + WeightedViewStub +) +{ + using Node = sg::DepthNodeDecorator>>; + const auto& [expected, origin] = GENERATE(from_range(testResults)); + + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(nodes, Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); } From 0b7dbe3559a778a1abff58652ef1ef8b9d3df346 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 2 Oct 2023 19:34:31 +0200 Subject: [PATCH 188/256] test: overhaul bfs test cases --- tests/graph/BreadthFirstSearch.cpp | 59 ++++++++++++++++-------------- tests/graph/DepthFirstSearch.cpp | 38 +++++++++++++------ 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/tests/graph/BreadthFirstSearch.cpp b/tests/graph/BreadthFirstSearch.cpp index ae4db0d0e..b4fcd648d 100644 --- a/tests/graph/BreadthFirstSearch.cpp +++ b/tests/graph/BreadthFirstSearch.cpp @@ -7,10 +7,37 @@ #include #include +#include #include #include "Defines.hpp" +namespace +{ + inline const std::vector< + std::tuple< + std::vector>>>, + std::string + >> testResults{ + { + {{{{"3"}, std::nullopt}, 0}, {{{"5"}, "3"}, 1}, {{{"6"}, "3"}, 1}, {{{"2"}, "6"}, 2}}, + "3" + }, + { + {{{{"6"}, std::nullopt}, 0}, {{{"2"}, "6"}, 1}}, + "6" + }, + { + {{{{"1"}, std::nullopt}, 0}, {{{"2"}, "1"}, 1}, {{{"3"}, "1"}, 1}, {{{"6"}, "2"}, 2}, {{{"5"}, "3"}, 2}}, + "1" + }, + { + {{{{"8"}, std::nullopt}, 0}, {{{"7"}, "8"}, 1}, {{{"9"}, "8"}, 1}, {{{"4"}, "7"}, 2}}, + "8" + } + }; +} + TEMPLATE_TEST_CASE( "bfs::Range visits all reachable vertices.", "[graph][graph::bfs]", @@ -19,13 +46,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::CommonBasicNode; - const auto& [expected, origin] = GENERATE( - (table, std::string>)({ - {{{"3"}, {"5"}, {"6"}, {"2"}}, "3"}, - {{{"6"}, {"2"}}, "6"}, - {{{"1"}, {"2"}, {"3"}, {"6"}, {"5"}}, "1"}, - {{{"8"}, {"7"}, {"9"}, {"4"}}, "8"} - })); + const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toCommonBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); @@ -41,13 +62,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::DepthNodeDecorator>; - const auto& [expected, origin] = GENERATE( - (table, std::string>)({ - {{{{"3"}, 0}, {{"5"}, 1}, {{"6"}, 1}, {{"2"}, 2}}, "3"}, - {{{{"6"}, 0}, {{"2"}, 1}}, "6"}, - {{{{"1"}, 0}, {{"2"}, 1}, {{"3"}, 1}, {{"6"}, 2}, {{"5"}, 2}}, "1"}, - {{{{"8"}, 0}, {{"7"}, 1}, {{"9"}, 1}, {{"4"}, 2}}, "8"} - })); + const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toDepthBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); @@ -63,13 +78,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::PredecessorNodeDecorator>; - const auto& [expected, origin] = GENERATE( - (table, std::string>)({ - {{{{"3"}, std::nullopt}, {{"5"}, "3"}, {{"6"}, "3"}, {{"2"}, "6"}}, "3"}, - {{{{"6"}, std::nullopt}, {{"2"}, "6"}}, "6"}, - {{{{"1"}, std::nullopt}, {{"2"}, "1"}, {{"3"}, "1"}, {{"6"}, "2"}, {{"5"}, "3"}}, "1"}, - {{{{"8"}, std::nullopt}, {{"7"}, "8"}, {{"9"}, "8"}, {{"4"}, "7"}}, "8"} - })); + const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toPredecessorBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); @@ -85,13 +94,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::DepthNodeDecorator>>; - const auto& [expected, origin] = GENERATE( - (table, std::string>)({ - {{{{{"3"}, std::nullopt}, 0}, {{{"5"}, "3"}, 1}, {{{"6"}, "3"}, 1}, {{{"2"}, "6"}, 2}}, "3"}, - {{{{{"6"}, std::nullopt}, 0}, {{{"2"}, "6"}, 1}}, "6"}, - {{{{{"1"}, std::nullopt}, 0}, {{{"2"}, "1"}, 1}, {{{"3"}, "1"}, 1}, {{{"6"}, "2"}, 2}, {{{"5"}, "3"}, 2}}, "1"}, - {{{{{"8"}, std::nullopt}, 0}, {{{"7"}, "8"}, 1}, {{{"9"}, "8"}, 1}, {{{"4"}, "7"}, 2}}, "8"} - })); + const auto& [expected, origin] = GENERATE(from_range(testResults)); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 0b26afa36..24b1fb2e8 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -12,16 +12,32 @@ #include "Defines.hpp" -inline const std::vector< - std::tuple< - std::vector>>>, - std::string - >> testResults{ - {{{{{"3"}, std::nullopt}, 0}, {{{"6"}, "3"}, 1}, {{{"2"}, "6"}, 2}, {{{"5"}, "3"}, 1}}, "3"}, - {{{{{"6"}, std::nullopt}, 0}, {{{"2"}, "6"}, 1}}, "6"}, - {{{{{"1"}, std::nullopt}, 0}, {{{"3"}, "1"}, 1}, {{{"6"}, "3"}, 2}, {{{"2"}, "6"}, 3}, {{{"5"}, "3"}, 2}}, "1"}, - {{{{{"8"}, std::nullopt}, 0}, {{{"9"}, "8"}, 1}, {{{"4"}, "9"}, 2}, {{{"7"}, "4"}, 3}}, "8"} -}; +namespace +{ + inline const std::vector< + std::tuple< + std::vector>>>, + std::string + >> testResults{ + { + {{{{"3"}, std::nullopt}, 0}, {{{"6"}, "3"}, 1}, {{{"2"}, "6"}, 2}, {{{"5"}, "3"}, 1}}, + "3" + }, + { + {{{{"6"}, std::nullopt}, 0}, {{{"2"}, "6"}, 1}}, + "6" + }, + { + {{{{"1"}, std::nullopt}, 0}, {{{"3"}, "1"}, 1}, {{{"6"}, "3"}, 2}, {{{"2"}, "6"}, 3}, {{{"5"}, "3"}, 2}}, + "1" + }, + { + {{{{"8"}, std::nullopt}, 0}, {{{"9"}, "8"}, 1}, {{{"4"}, "9"}, 2}, {{{"7"}, "4"}, 3}}, + "8" + } + }; +} + TEMPLATE_TEST_CASE( "dfs::Range visits all reachable vertices.", @@ -33,7 +49,7 @@ TEMPLATE_TEST_CASE( using Node = sg::CommonBasicNode; const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toCommonBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); From 0ffc66f996bb1e4874de2c30b8accc10bd24af33 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 3 Oct 2023 11:49:04 +0200 Subject: [PATCH 189/256] fix: please clang --- tests/graph/Defines.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index d0959f039..e914aa15d 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -234,9 +234,9 @@ constexpr auto convert_test_expectations(const Range& range, Transform transform return results; } -constexpr auto toCommonBasicNode = [](const sg::concepts::basic_node auto& node) +constexpr auto toCommonBasicNode = [](const Node& node) { - return sg::CommonBasicNode{sg::node::vertex(node)}; + return sg::CommonBasicNode>{sg::node::vertex(node)}; }; constexpr auto toDepthBasicNode = [](const sg::DepthNodeDecorator& node) From 553670dd5a5083750f6aa636136899eb94376b90 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 3 Oct 2023 16:44:18 +0200 Subject: [PATCH 190/256] refactor: rename convert_test_expectations to slice_test_expectations --- tests/graph/BreadthFirstSearch.cpp | 6 +++--- tests/graph/Defines.hpp | 2 +- tests/graph/DepthFirstSearch.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/graph/BreadthFirstSearch.cpp b/tests/graph/BreadthFirstSearch.cpp index b4fcd648d..34df68216 100644 --- a/tests/graph/BreadthFirstSearch.cpp +++ b/tests/graph/BreadthFirstSearch.cpp @@ -46,7 +46,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::CommonBasicNode; - const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toCommonBasicNode))); + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); @@ -62,7 +62,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::DepthNodeDecorator>; - const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toDepthBasicNode))); + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); @@ -78,7 +78,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::PredecessorNodeDecorator>; - const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toPredecessorBasicNode))); + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index e914aa15d..cd02b249c 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -209,7 +209,7 @@ constexpr auto buffer_nodes(Range& range) } template -constexpr auto convert_test_expectations(const Range& range, Transform transform) +constexpr auto slice_test_expectations(const Range& range, Transform transform) { using TargetNode = std::invoke_result_t< Transform, diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 24b1fb2e8..31085f18e 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -47,7 +47,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::CommonBasicNode; - const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toCommonBasicNode))); + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); @@ -63,7 +63,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::DepthNodeDecorator>; - const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toDepthBasicNode))); + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); @@ -79,7 +79,7 @@ TEMPLATE_TEST_CASE( ) { using Node = sg::PredecessorNodeDecorator>; - const auto& [expected, origin] = GENERATE(from_range(convert_test_expectations(testResults, toPredecessorBasicNode))); + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); From 211330d8af0c5fabe40c887840fdd7bba9538fc6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 3 Oct 2023 17:36:35 +0200 Subject: [PATCH 191/256] test: extend graph::ucs tests --- tests/graph/Defines.hpp | 40 ++++++++- tests/graph/DepthFirstSearch.cpp | 1 - tests/graph/UniformCostSearch.cpp | 140 ++++++++++++++++-------------- 3 files changed, 111 insertions(+), 70 deletions(-) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index cd02b249c..f070ddcdf 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -164,7 +164,13 @@ struct BasicViewStub requires sg::concepts::edge_for static std::vector edges(const Node& current) { - const auto& vertices = graph.at(std::stoi(sg::node::vertex(current))); + const auto vertex = std::stoi(sg::node::vertex(current)); + if (!graph.contains(vertex)) + { + return {}; + } + + const auto& vertices = graph.at(vertex); std::vector infos{}; infos.reserve(std::ranges::size(vertices)); std::ranges::transform( @@ -183,7 +189,13 @@ struct WeightedViewStub requires sg::concepts::edge_for static std::vector edges(const Node& current) { - const auto& vertices = graph.at(std::stoi(sg::node::vertex(current))); + const auto vertex = std::stoi(sg::node::vertex(current)); + if (!graph.contains(vertex)) + { + return {}; + } + + const auto& vertices = graph.at(vertex); std::vector infos{}; infos.reserve(std::ranges::size(vertices)); std::ranges::transform( @@ -254,3 +266,27 @@ constexpr auto toPredecessorBasicNode = [](const sg::PredecessorN node.predecessor }; }; + +constexpr auto toCommonRankedNode = [](const Node& node) +{ + return sg::CommonRankedNode, sg::node::rank_t>{ + sg::node::vertex(node), + sg::node::rank(node) + }; +}; + +constexpr auto toDepthRankedNode = [](const sg::DepthNodeDecorator& node) +{ + return sg::DepthNodeDecorator, sg::node::rank_t>>{ + {toCommonRankedNode(node)}, + node.depth + }; +}; + +constexpr auto toPredecessorRankedNode = [](const sg::PredecessorNodeDecorator& node) +{ + return sg::PredecessorNodeDecorator, sg::node::rank_t>>{ + {toCommonRankedNode(node)}, + node.predecessor + }; +}; diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 31085f18e..7cbc452eb 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -38,7 +38,6 @@ namespace }; } - TEMPLATE_TEST_CASE( "dfs::Range visits all reachable vertices.", "[graph][graph::dfs]", diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp index 84c7cf5fa..2ae53b46d 100644 --- a/tests/graph/UniformCostSearch.cpp +++ b/tests/graph/UniformCostSearch.cpp @@ -7,92 +7,98 @@ #include #include +#include #include #include "Defines.hpp" namespace { - using Node = sg::CommonRankedNode; - using Edge = sg::CommonWeightedEdge; - - struct View - { - using edge_type = Edge; - - inline static const std::unordered_map> graph{ - {"1", {2, 3}}, - {"2", {6}}, - {"3", {5, 6}}, - {"5", {5}}, - {"6", {2}}, - - // begin isolated sub-graph - {"4", {7}}, - {"7", {4, 7, 9}}, - {"8", {7, 9}}, - {"9", {4}} - }; - - template - requires sg::concepts::edge_for - static std::vector edges(const Node& current) + inline const std::vector< + std::tuple< + std::vector>>>, + std::string + >> testResults{ + { + {{{{"42", 0}, std::nullopt}, 0}}, + "42" + }, + { + {{{{"3", 0}, std::nullopt}, 0}, {{{"5", 2}, "3"}, 1}, {{{"6", 3}, "3"}, 1}, {{{"2", 7}, "6"}, 2}}, + "3" + }, { - const auto& vertices = graph.at(sg::node::vertex(current)); - std::vector infos{}; - infos.reserve(std::ranges::size(vertices)); - std::ranges::transform( - vertices, - std::back_inserter(infos), - [&](const int v) - { - return edge_type{ - .destination = std::to_string(v), - .weight = std::abs(v - std::stoi(current.vertex)) - }; - }); - return infos; + {{{{"6", 0}, std::nullopt}, 0}, {{{"2", 4}, "6"}, 1}}, + "6" + }, + // non-deterministic, as 6 may have the predecessor 2 or 3 + //{ + // {{{{"1", 0}, std::nullopt}, 0}, {{{"2", 1}, "1"}, 1}, {{{"3", 2}, "1"}, 1}, {{{"6", 5}, "2"}, 2}, {{{"5", 4}, "3"}}, 2}, + // "1" + //}, + { + {{{{"8", 0}, std::nullopt}, 0}, {{{"7", 1}, "8"}, 1}, {{{"4", 4}, "7"}, 2}, {{{"9", 1}, "8"}, 1}}, + "8" } }; } -TEST_CASE("ucs::Range visits all reachable vertices.", "[graph][graph::ucs]") +TEMPLATE_TEST_CASE( + "ucs::Range visits all reachable vertices.", + "[graph][graph::ucs]", + WeightedViewStub +) { - const auto& [expected, origin] = GENERATE( - (table, std::string>)({ - {{{"3", 0}, {"5", 2}, {"6", 3}, {"2", 7}}, "3"}, - {{{"6", 0}, {"2", 4}}, "6"}, - {{{"1", 0}, {"2", 1}, {"3", 2}, {"6", 5}, {"5", 4}}, "1"}, - {{{"8", 0}, {"7", 1}, {"4", 4}, {"9", 1}}, "8"} - })); - - sg::ucs::Range range{origin, std::tuple{View{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + using Node = sg::CommonRankedNode; + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonRankedNode))); + + sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); - std::vector nodes{}; - std::ranges::copy(range, std::back_inserter(nodes)); + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); +} + +TEMPLATE_TEST_CASE( + "ucs::Range node can be decorated with DepthNodeDecorator.", + "[graph][graph::ucs]", + WeightedViewStub +) +{ + using Node = sg::DepthNodeDecorator>; + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthRankedNode))); - REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); + sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); + + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); } -TEST_CASE("ucs::Range node can be decorated with PredecessorNodeDecorator.", "[graph][graph::ucs]") +TEMPLATE_TEST_CASE( + "ucs::Range node can be decorated with PredecessorNodeDecorator.", + "[graph][graph::ucs]", + WeightedViewStub +) { - using DecoratedNode = sg::PredecessorNodeDecorator<::Node>; - - const auto& [expected, origin] = GENERATE( - (table, std::string>)({ - {{{{"3", 0}, std::nullopt}, {{"5", 2}, "3"}, {{"6", 3}, "3"}, {{"2", 7}, "6"}}, "3"}, - {{{{"6", 0}, std::nullopt}, {{"2", 4}, "6"}}, "6"}, - // non-deterministic, as 6 may have the predecessor 2 or 3 - //{{{{"1", 0}, std::nullopt}, {{"2", 1}, "1"}, {{"3", 2}, "1"}, {{"6", 5}, "2"}, {{"5", 4}, "3"}}, "1"}, - {{{{"8", 0}, std::nullopt}, {{"7", 1}, "8"}, {{"4", 4}, "7"}, {{"9", 1}, "8"}}, "8"} - })); - - sg::ucs::Range range{origin, std::tuple{View{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + using Node = sg::PredecessorNodeDecorator>; + const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorRankedNode))); + + sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); - std::vector nodes{}; - std::ranges::copy(range, std::back_inserter(nodes)); + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); +} + +TEMPLATE_TEST_CASE( + "ucs::Range can be used with arbitrary decorated nodes.", + "[graph][graph::ucs]", + WeightedViewStub +) +{ + using Node = sg::DepthNodeDecorator>>; + const auto& [expected, origin] = GENERATE(from_range(testResults)); + + sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); } From ad19649fb2bd4260dad031a17d64bb93706ea979 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 3 Oct 2023 17:37:43 +0200 Subject: [PATCH 192/256] test: extend dfs and bfs tests --- tests/graph/BreadthFirstSearch.cpp | 4 ++++ tests/graph/DepthFirstSearch.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/graph/BreadthFirstSearch.cpp b/tests/graph/BreadthFirstSearch.cpp index 34df68216..9c7f662f9 100644 --- a/tests/graph/BreadthFirstSearch.cpp +++ b/tests/graph/BreadthFirstSearch.cpp @@ -19,6 +19,10 @@ namespace std::vector>>>, std::string >> testResults{ + { + {{{{"42"}, std::nullopt}, 0}}, + "42" + }, { {{{{"3"}, std::nullopt}, 0}, {{{"5"}, "3"}, 1}, {{{"6"}, "3"}, 1}, {{{"2"}, "6"}, 2}}, "3" diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 7cbc452eb..894bdb815 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -19,6 +19,10 @@ namespace std::vector>>>, std::string >> testResults{ + { + {{{{"42"}, std::nullopt}, 0}}, + "42" + }, { {{{{"3"}, std::nullopt}, 0}, {{{"6"}, "3"}, 1}, {{{"2"}, "6"}, 2}, {{{"5"}, "3"}, 1}}, "3" From 5a232fccd3b5317ee84ee5ca825aa9aaf29e9c71 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 3 Oct 2023 20:15:00 +0200 Subject: [PATCH 193/256] fix: conditional noexcept of SingleDestinationHeuristic ctor --- include/Simple-Utility/graph/AStarSearch.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index 73817ad72..024b30e3e 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -243,7 +243,7 @@ namespace sl::graph::astar [[nodiscard]] explicit constexpr SingleDestinationHeuristic( Vertex destination - ) noexcept(std::is_nothrow_move_constructible_v + ) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_default_constructible_v) : m_Destination{std::move(destination)} { @@ -253,7 +253,7 @@ namespace sl::graph::astar explicit constexpr SingleDestinationHeuristic( Vertex destination, Strategy strategy - ) noexcept(std::is_nothrow_move_constructible_v + ) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v) : m_Destination{std::move(destination)}, m_Strategy{std::move(strategy)} From 3afe6e33eb1b3e27ef35cf41006fd13852f6188c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 5 Oct 2023 19:02:24 +0200 Subject: [PATCH 194/256] refactor: overhaul astar tests --- include/Simple-Utility/graph/AStarSearch.hpp | 74 ++++---- tests/graph/AStarSearch.cpp | 186 ++++++++++++------- tests/graph/Defines.hpp | 28 +-- 3 files changed, 172 insertions(+), 116 deletions(-) diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index 024b30e3e..31473fed2 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -186,51 +186,51 @@ namespace sl::graph::astar [[nodiscard]] friend bool operator==(const Node&, const Node&) = default; }; +} - template - struct NodeFactory; +template +struct sl::graph::detail::NodeFactory> +{ +public: + using node_type = astar::Node; + using vertex_type = Vertex; + using rank_type = Rank; - template - struct NodeFactory> + [[nodiscard]] + constexpr node_type operator ()(vertex_type origin, rank_type estimatedPendingCost) const { - public: - using node_type = Node; - using vertex_type = Vertex; - using rank_type = Rank; - - [[nodiscard]] - constexpr node_type operator ()(vertex_type origin, rank_type estimatedPendingCost) const - { - node_type node{ - .vertex = std::move(origin), - .cost = {}, - .estimatedPendingCost = std::move(estimatedPendingCost) - }; - detail::check_bounds(node.cost, node.estimatedPendingCost); - - return node; - } + node_type node{ + .vertex = std::move(origin), + .cost = {}, + .estimatedPendingCost = std::move(estimatedPendingCost) + }; + astar::detail::check_bounds(node.cost, node.estimatedPendingCost); + + return node; + } - template Edge> - [[nodiscard]] - constexpr node_type operator ()(const node_type& current, const Edge& edge, rank_type estimatedPendingCost) const - { - detail::check_bounds(current.cost, edge::weight(edge)); + template Edge> + [[nodiscard]] + constexpr node_type operator ()(const node_type& current, const Edge& edge, rank_type estimatedPendingCost) const + { + astar::detail::check_bounds(current.cost, edge::weight(edge)); - node_type node{ - .vertex = edge::destination(edge), - .cost = current.cost + edge::weight(edge), - .estimatedPendingCost = std::move(estimatedPendingCost) - }; - detail::check_bounds(node.cost, node.estimatedPendingCost); + node_type node{ + .vertex = edge::destination(edge), + .cost = current.cost + edge::weight(edge), + .estimatedPendingCost = std::move(estimatedPendingCost) + }; + astar::detail::check_bounds(node.cost, node.estimatedPendingCost); - return node; - } - }; + return node; + } +}; +namespace sl::graph::astar +{ template - struct NodeFactory> - : public NodeFactoryDecorator, NodeFactory> + struct NodeFactory + : public graph::detail::NodeFactory { }; diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index fc7e947bd..8cf9fdb21 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -7,51 +7,44 @@ #include #include +#include #include #include "Defines.hpp" namespace { - using Node = sg::astar::Node; - using Edge = sg::CommonWeightedEdge; - - struct View - { - using edge_type = Edge; - - inline static const std::unordered_map> graph{ - {"1", {2, 3}}, - {"2", {6}}, - {"3", {5, 6}}, - {"5", {5}}, - {"6", {2}}, - - // begin isolated sub-graph - {"4", {7}}, - {"7", {4, 7, 9}}, - {"8", {7, 9}}, - {"9", {4}} - }; - - template - requires sg::concepts::edge_for - static std::vector edges(const Node& current) + inline const std::vector< + std::tuple< + std::vector>>>, + std::string, // origin + std::string // destination + >> testResults{ + { + {{{{"42", 0, 30}, std::nullopt}, 0}}, + "42", + "12" + }, { - const auto& vertices = graph.at(sg::node::vertex(current)); - std::vector infos{}; - infos.reserve(std::ranges::size(vertices)); - std::ranges::transform( - vertices, - std::back_inserter(infos), - [&](const int v) - { - return edge_type{ - .destination = std::to_string(v), - .weight = std::abs(v - std::stoi(current.vertex)) - }; - }); - return infos; + {{{{"3", 0, 6}, std::nullopt}, 0}, {{{"5", 2, 4}, "3"}, 1}, {{{"6", 3, 3}, "3"}, 1}, {{{"2", 7, 7}, "6"}, 2}}, + "3", + "9" + }, + { + {{{{"6", 0, 3}, std::nullopt}, 0}, {{{"2", 4, 7}, "6"}, 1}}, + "6", + "9" + }, + // non-deterministic, as 6 may have the predecessor 2 or 3 + //{ + // {{{{"1", 0, 8}, std::nullopt}, 0}, {{{"2", 1, 7}, "1"}, 1}, {{{"3", 2, 6}, "1"}, 1}, {{{"6", 5, 3}, "2"}, 2}, {{{"5", 4, 5}, "3"}}, 2}, + // "1", + // "9" + //}, + { + {{{{"8", 0, 1}, std::nullopt}, 0}, {{{"7", 1, 2}, "8"}, 1}, {{{"4", 4, 5}, "7"}, 2}, {{{"9", 1, 0}, "8"}, 1}}, + "8", + "9" } }; @@ -65,21 +58,44 @@ namespace }; using Heuristic = sg::astar::SingleDestinationHeuristic; + + constexpr auto toCommonAStarNode = [](const Node& node) + { + return sg::astar::Node, sg::node::rank_t>{ + sg::node::vertex(node), + node.cost, + node.estimatedPendingCost + }; + }; + + constexpr auto toDepthAStarNode = [](const sg::DepthNodeDecorator& node) + { + return sg::DepthNodeDecorator, sg::node::rank_t>>{ + {toCommonAStarNode(node)}, + node.depth + }; + }; + + constexpr auto toPredecessorAStarNode = [](const sg::PredecessorNodeDecorator& node) + { + return sg::PredecessorNodeDecorator, sg::node::rank_t>>{ + {toCommonAStarNode(node)}, + node.predecessor + }; + }; } -TEST_CASE("astar::Range visits all reachable vertices.", "[graph][graph::astar]") +TEMPLATE_TEST_CASE( + "astar::Range visits all reachable vertices.", + "[graph][graph::astar]", + WeightedViewStub) { - const auto& [expected, origin, destination] = GENERATE( - (table, std::string, std::string>)({ - {{{"3", 0, 6}, {"5", 2, 4}, {"6", 3, 3}, {"2", 7, 7}}, "3", "9"}, - {{{"6", 0, 3}, {"2", 4, 7}}, "6", "9"}, - {{{"1", 0, 8}, {"2", 1, 7}, {"3", 2, 6}, {"6", 5, 3}, {"5", 4, 4}}, "1", "9"}, - {{{"8", 0, 1}, {"7", 1, 2}, {"4", 4, 5}, {"9", 1, 0}}, "8", "9"} - })); - - sg::astar::Range range{ + using Node = sg::astar::Node; + const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toCommonAStarNode))); + + sg::astar::Range range{ origin, - std::tuple{View{}}, + std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, @@ -87,35 +103,71 @@ TEST_CASE("astar::Range visits all reachable vertices.", "[graph][graph::astar]" }; STATIC_CHECK(std::ranges::input_range); - std::vector nodes{}; - std::ranges::copy(range, std::back_inserter(nodes)); - - REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); } -TEST_CASE("astar::Range node can be decorated with PredecessorNodeDecorator.", "[graph][graph::ucs]") +TEMPLATE_TEST_CASE( + "astar::Range node can be decorated with DepthNodeDecorator.", + "[graph][graph::astar]", + WeightedViewStub +) { - using DecoratedNode = sg::PredecessorNodeDecorator<::Node>; + using Node = sg::DepthNodeDecorator>; + const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toDepthAStarNode))); - const auto& [expected, origin, destination] = GENERATE( - (table, std::string, std::string>)({ - {{{{"3", 0, 6}, std::nullopt}, {{"5", 2, 4}, "3"}, {{"6", 3, 3}, "3"}, {{"2", 7, 7}, "6"}}, "3", "9"}, - {{{{"6", 0, 3}, std::nullopt}, {{"2", 4, 7}, "6"}}, "6", "9"}, - {{{{"8", 0, 1}, std::nullopt}, {{"7", 1, 2}, "8"}, {{"4", 4, 5}, "7"}, {{"9", 1, 0}, "8"}}, "8", "9"} - })); + sg::astar::Range range{ + origin, + std::tuple{TestType{}}, + std::tuple{}, + std::tuple{}, + std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, + std::tuple{} + }; + STATIC_CHECK(std::ranges::input_range); + + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); +} - sg::astar::Range range{ +TEMPLATE_TEST_CASE( + "astar::Range node can be decorated with PredecessorNodeDecorator.", + "[graph][graph::astar]", + WeightedViewStub +) +{ + using Node = sg::PredecessorNodeDecorator>; + const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorAStarNode))); + + sg::astar::Range range{ origin, - std::tuple{View{}}, + std::tuple{TestType{}}, std::tuple{}, std::tuple{}, - std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, + std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, std::tuple{} }; STATIC_CHECK(std::ranges::input_range); - std::vector nodes{}; - std::ranges::copy(range, std::back_inserter(nodes)); + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); +} + +TEMPLATE_TEST_CASE( + "astar::Range can be used with arbitrary decorated nodes.", + "[graph][graph::astar]", + WeightedViewStub +) +{ + using Node = sg::DepthNodeDecorator>>; + const auto& [expected, origin, destination] = GENERATE(from_range(testResults)); + + sg::astar::Range range{ + origin, + std::tuple{TestType{}}, + std::tuple{}, + std::tuple{}, + std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, + std::tuple{} + }; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(nodes, Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); } diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index f070ddcdf..8768d7f25 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -8,6 +8,8 @@ #include #include +#include "Simple-Utility/TypeList.hpp" +#include "Simple-Utility/functional/Tuple.hpp" #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Edge.hpp" #include "Simple-Utility/graph/Formatter.hpp" @@ -227,21 +229,23 @@ constexpr auto slice_test_expectations(const Range& range, Transform transform) Transform, std::ranges::range_value_t>>>; std::vector< - std::tuple< - std::vector, - std::tuple_element_t<1, std::ranges::range_value_t>>> results{}; + sl::type_list::prepend_t< + sl::type_list::pop_front_t< + std::ranges::range_value_t>, + std::vector>> results{}; std::ranges::transform( range, std::back_inserter(results), - [&](const auto& tuple) - { - std::vector nodes{}; - std::ranges::transform(std::get<0>(tuple), std::back_inserter(nodes), transform); - return std::tuple{ - std::move(nodes), - std::get<1>(tuple) - }; - }); + sl::functional::envelop( + [&](const Nodes& nodes, Args&&... args) + { + std::vector targetNodes{}; + std::ranges::transform(nodes, std::back_inserter(targetNodes), transform); + return std::tuple{ + std::move(targetNodes), + std::forward(args)... + }; + })); return results; } From af85c2d321d93d990ae21c2820e96707f3969784 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 5 Oct 2023 19:33:24 +0200 Subject: [PATCH 195/256] refactor: move node and factory decorators into decorator namespace --- include/Simple-Utility/graph/Formatter.hpp | 8 ++++---- include/Simple-Utility/graph/Node.hpp | 21 ++++++++++++--------- include/Simple-Utility/graph/Traverse.hpp | 4 ++-- tests/graph/AStarSearch.cpp | 16 ++++++++-------- tests/graph/BreadthFirstSearch.cpp | 8 ++++---- tests/graph/Defines.hpp | 16 ++++++++-------- tests/graph/DepthFirstSearch.cpp | 8 ++++---- tests/graph/Formatter.cpp | 4 ++-- tests/graph/Node.cpp | 22 ++++++++++++++++++---- tests/graph/UniformCostSearch.cpp | 12 ++++++------ 10 files changed, 68 insertions(+), 51 deletions(-) diff --git a/include/Simple-Utility/graph/Formatter.hpp b/include/Simple-Utility/graph/Formatter.hpp index dd01008f9..385c3b031 100644 --- a/include/Simple-Utility/graph/Formatter.hpp +++ b/include/Simple-Utility/graph/Formatter.hpp @@ -54,7 +54,7 @@ namespace sl::graph }; template - class NodeFormatter, Char> + class NodeFormatter, Char> { public: constexpr auto parse(std::basic_format_parse_context& ctx) noexcept @@ -63,7 +63,7 @@ namespace sl::graph } template - auto format(const PredecessorNodeDecorator& node, FormatContext& ctx) const + auto format(const decorator::PredecessorNode& node, FormatContext& ctx) const { return std::format_to( m_Formatter.format(node, ctx), @@ -76,7 +76,7 @@ namespace sl::graph }; template - class NodeFormatter, Char> + class NodeFormatter, Char> { public: constexpr auto parse(std::basic_format_parse_context& ctx) noexcept @@ -85,7 +85,7 @@ namespace sl::graph } template - auto format(const DepthNodeDecorator& node, FormatContext& ctx) const + auto format(const decorator::DepthNode& node, FormatContext& ctx) const { return std::format_to( m_Formatter.format(node, ctx), diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 0e3300fca..4f64624fa 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -229,9 +229,12 @@ namespace sl::graph [[nodiscard]] friend bool operator ==(const CommonRankedNode&, const CommonRankedNode&) = default; }; +} +namespace sl::graph::decorator +{ template - struct PredecessorNodeDecorator + struct PredecessorNode : public Node { using vertex_type = node::vertex_t; @@ -239,21 +242,21 @@ namespace sl::graph std::optional predecessor{}; [[nodiscard]] - friend bool operator ==(const PredecessorNodeDecorator&, const PredecessorNodeDecorator&) = default; + friend bool operator ==(const PredecessorNode&, const PredecessorNode&) = default; }; template typename BaseNodeFactory> - class NodeFactoryDecorator; + class NodeFactory; template typename BaseNodeFactory> - class NodeFactoryDecorator, BaseNodeFactory> + class NodeFactory, BaseNodeFactory> : private BaseNodeFactory { private: using Super = BaseNodeFactory; public: - using node_type = PredecessorNodeDecorator; + using node_type = PredecessorNode; using vertex_type = node::vertex_t; template @@ -282,7 +285,7 @@ namespace sl::graph }; template - struct DepthNodeDecorator + struct DepthNode : public Node { using vertex_type = node::vertex_t; @@ -290,18 +293,18 @@ namespace sl::graph int depth{}; [[nodiscard]] - friend bool operator ==(const DepthNodeDecorator&, const DepthNodeDecorator&) = default; + friend bool operator ==(const DepthNode&, const DepthNode&) = default; }; template typename BaseNodeFactory> - class NodeFactoryDecorator, BaseNodeFactory> + class NodeFactory, BaseNodeFactory> : private BaseNodeFactory { private: using Super = BaseNodeFactory; public: - using node_type = DepthNodeDecorator; + using node_type = DepthNode; using vertex_type = node::vertex_t; template diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 4ee7d758e..59bdf387e 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -57,9 +57,9 @@ namespace sl::graph::detail }; template - requires requires { typename NodeFactoryDecorator::node_type; } + requires requires { typename decorator::NodeFactory::node_type; } struct NodeFactory - : public NodeFactoryDecorator + : public decorator::NodeFactory { }; diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index 8cf9fdb21..1a6be097f 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -16,7 +16,7 @@ namespace { inline const std::vector< std::tuple< - std::vector>>>, + std::vector>>>, std::string, // origin std::string // destination >> testResults{ @@ -68,17 +68,17 @@ namespace }; }; - constexpr auto toDepthAStarNode = [](const sg::DepthNodeDecorator& node) + constexpr auto toDepthAStarNode = [](const sg::decorator::DepthNode& node) { - return sg::DepthNodeDecorator, sg::node::rank_t>>{ + return sg::decorator::DepthNode, sg::node::rank_t>>{ {toCommonAStarNode(node)}, node.depth }; }; - constexpr auto toPredecessorAStarNode = [](const sg::PredecessorNodeDecorator& node) + constexpr auto toPredecessorAStarNode = [](const sg::decorator::PredecessorNode& node) { - return sg::PredecessorNodeDecorator, sg::node::rank_t>>{ + return sg::decorator::PredecessorNode, sg::node::rank_t>>{ {toCommonAStarNode(node)}, node.predecessor }; @@ -112,7 +112,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::DepthNodeDecorator>; + using Node = sg::decorator::DepthNode>; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toDepthAStarNode))); sg::astar::Range range{ @@ -134,7 +134,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::PredecessorNodeDecorator>; + using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorAStarNode))); sg::astar::Range range{ @@ -156,7 +156,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::DepthNodeDecorator>>; + using Node = sg::decorator::DepthNode>>; const auto& [expected, origin, destination] = GENERATE(from_range(testResults)); sg::astar::Range range{ diff --git a/tests/graph/BreadthFirstSearch.cpp b/tests/graph/BreadthFirstSearch.cpp index 9c7f662f9..6a95ccb7a 100644 --- a/tests/graph/BreadthFirstSearch.cpp +++ b/tests/graph/BreadthFirstSearch.cpp @@ -16,7 +16,7 @@ namespace { inline const std::vector< std::tuple< - std::vector>>>, + std::vector>>>, std::string >> testResults{ { @@ -65,7 +65,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::DepthNodeDecorator>; + using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; @@ -81,7 +81,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::PredecessorNodeDecorator>; + using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; @@ -97,7 +97,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::DepthNodeDecorator>>; + using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 8768d7f25..e2df723ca 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -255,17 +255,17 @@ constexpr auto toCommonBasicNode = [](const Node& return sg::CommonBasicNode>{sg::node::vertex(node)}; }; -constexpr auto toDepthBasicNode = [](const sg::DepthNodeDecorator& node) +constexpr auto toDepthBasicNode = [](const sg::decorator::DepthNode& node) { - return sg::DepthNodeDecorator>>{ + return sg::decorator::DepthNode>>{ {toCommonBasicNode(node)}, node.depth }; }; -constexpr auto toPredecessorBasicNode = [](const sg::PredecessorNodeDecorator& node) +constexpr auto toPredecessorBasicNode = [](const sg::decorator::PredecessorNode& node) { - return sg::PredecessorNodeDecorator>>{ + return sg::decorator::PredecessorNode>>{ {toCommonBasicNode(node)}, node.predecessor }; @@ -279,17 +279,17 @@ constexpr auto toCommonRankedNode = [](const Nod }; }; -constexpr auto toDepthRankedNode = [](const sg::DepthNodeDecorator& node) +constexpr auto toDepthRankedNode = [](const sg::decorator::DepthNode& node) { - return sg::DepthNodeDecorator, sg::node::rank_t>>{ + return sg::decorator::DepthNode, sg::node::rank_t>>{ {toCommonRankedNode(node)}, node.depth }; }; -constexpr auto toPredecessorRankedNode = [](const sg::PredecessorNodeDecorator& node) +constexpr auto toPredecessorRankedNode = [](const sg::decorator::PredecessorNode& node) { - return sg::PredecessorNodeDecorator, sg::node::rank_t>>{ + return sg::decorator::PredecessorNode, sg::node::rank_t>>{ {toCommonRankedNode(node)}, node.predecessor }; diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 894bdb815..8e74d91ba 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -16,7 +16,7 @@ namespace { inline const std::vector< std::tuple< - std::vector>>>, + std::vector>>>, std::string >> testResults{ { @@ -65,7 +65,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::DepthNodeDecorator>; + using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; @@ -81,7 +81,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::PredecessorNodeDecorator>; + using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorBasicNode))); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; @@ -97,7 +97,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::DepthNodeDecorator>>; + using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; diff --git a/tests/graph/Formatter.cpp b/tests/graph/Formatter.cpp index 7ba87f198..790f6defb 100644 --- a/tests/graph/Formatter.cpp +++ b/tests/graph/Formatter.cpp @@ -27,7 +27,7 @@ TEST_CASE("ranked_node types can be formatted.", "[graph][graph::node][graph::fo TEST_CASE("predecessor decorated ranked_node types can be formatted.", "[graph][graph::node][graph::format]") { - using TestType = sg::PredecessorNodeDecorator>; + using TestType = sg::decorator::PredecessorNode>; REQUIRE("{vertex: 42, rank: 1337, predecessor: 41}" == std::format("{}", TestType{{.vertex = "42", .rank = 1337}, "41"})); REQUIRE( @@ -36,7 +36,7 @@ TEST_CASE("predecessor decorated ranked_node types can be formatted.", "[graph][ TEST_CASE("depth decorated ranked_node types can be formatted.", "[graph][graph::node][graph::format]") { - using TestType = sg::DepthNodeDecorator>; + using TestType = sg::decorator::DepthNode>; REQUIRE("{vertex: -42, rank: 1337, depth: 42}" == std::format("{}", TestType{{.vertex = "-42", .rank = 1337}, 42})); } diff --git a/tests/graph/Node.cpp b/tests/graph/Node.cpp index fbf77c1d2..42f729734 100644 --- a/tests/graph/Node.cpp +++ b/tests/graph/Node.cpp @@ -196,8 +196,15 @@ TEMPLATE_TEST_CASE_SIG( (true, GenericRankedNode), (true, sg::CommonBasicNode), (true, sg::CommonRankedNode), - (true, sg::PredecessorNodeDecorator>), - (true, sg::PredecessorNodeDecorator>) + + (true, sg::decorator::PredecessorNode>), + (true, sg::decorator::PredecessorNode>), + (true, sg::decorator::DepthNode>), + (true, sg::decorator::DepthNode>), + (true, sg::decorator::PredecessorNode>>), + (true, sg::decorator::PredecessorNode>>), + (true, sg::decorator::DepthNode>>), + (true, sg::decorator::DepthNode>>) ) { STATIC_REQUIRE(expected == sg::concepts::basic_node); @@ -215,9 +222,16 @@ TEMPLATE_TEST_CASE_SIG( (false, GenericBasicNode), (true, GenericRankedNode), (false, sg::CommonBasicNode), - (false, sg::PredecessorNodeDecorator>), (true, sg::CommonRankedNode), - (true, sg::PredecessorNodeDecorator>) + + (false, sg::decorator::PredecessorNode>), + (true, sg::decorator::PredecessorNode>), + (false, sg::decorator::DepthNode>), + (true, sg::decorator::DepthNode>), + (false, sg::decorator::PredecessorNode>>), + (true, sg::decorator::PredecessorNode>>), + (false, sg::decorator::DepthNode>>), + (true, sg::decorator::DepthNode>>) ) { STATIC_REQUIRE(expected == sg::concepts::ranked_node); diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp index 2ae53b46d..2ce598339 100644 --- a/tests/graph/UniformCostSearch.cpp +++ b/tests/graph/UniformCostSearch.cpp @@ -16,7 +16,7 @@ namespace { inline const std::vector< std::tuple< - std::vector>>>, + std::vector>>>, std::string >> testResults{ { @@ -59,12 +59,12 @@ TEMPLATE_TEST_CASE( } TEMPLATE_TEST_CASE( - "ucs::Range node can be decorated with DepthNodeDecorator.", + "ucs::Range node can be decorated with DepthNode.", "[graph][graph::ucs]", WeightedViewStub ) { - using Node = sg::DepthNodeDecorator>; + using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthRankedNode))); sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; @@ -74,12 +74,12 @@ TEMPLATE_TEST_CASE( } TEMPLATE_TEST_CASE( - "ucs::Range node can be decorated with PredecessorNodeDecorator.", + "ucs::Range node can be decorated with PredecessorNode.", "[graph][graph::ucs]", WeightedViewStub ) { - using Node = sg::PredecessorNodeDecorator>; + using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorRankedNode))); sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; @@ -94,7 +94,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::DepthNodeDecorator>>; + using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; From ef0ecee4c35ae5ee9169fb59be40a353bb997956 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 5 Oct 2023 19:37:53 +0200 Subject: [PATCH 196/256] feat: add CommonNode alias into algorithm namespaces --- include/Simple-Utility/graph/AStarSearch.hpp | 16 ++++++++-------- .../Simple-Utility/graph/BreadthFirstSearch.hpp | 5 ++++- .../Simple-Utility/graph/DepthFirstSearch.hpp | 5 ++++- .../Simple-Utility/graph/UniformCostSearch.hpp | 5 ++++- tests/graph/AStarSearch.cpp | 16 ++++++++-------- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index 31473fed2..d3912598e 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -158,15 +158,15 @@ namespace sl::graph::astar::detail template using default_kernel_t = LazyKernel; #else - template - using default_kernel_t = BufferedKernel; + template + using default_kernel_t = BufferedKernel; #endif } namespace sl::graph::astar { template - struct Node + struct CommonNode { using vertex_type = Vertex; using rank_type = Rank; @@ -176,7 +176,7 @@ namespace sl::graph::astar rank_type estimatedPendingCost{}; [[nodiscard]] - friend constexpr rank_type rank(const Node& node) noexcept(noexcept(node.cost + node.cost)) + friend constexpr rank_type rank(const CommonNode& node) noexcept(noexcept(node.cost + node.cost)) { detail::check_bounds(node.cost, node.estimatedPendingCost); @@ -184,15 +184,15 @@ namespace sl::graph::astar } [[nodiscard]] - friend bool operator==(const Node&, const Node&) = default; + friend bool operator==(const CommonNode&, const CommonNode&) = default; }; } template -struct sl::graph::detail::NodeFactory> +struct sl::graph::detail::NodeFactory> { public: - using node_type = astar::Node; + using node_type = astar::CommonNode; using vertex_type = Vertex; using rank_type = Rank; @@ -274,7 +274,7 @@ namespace sl::graph::astar template < typename View, typename Heuristic, - concepts::ranked_node Node = Node>, edge::weight_t>>, + concepts::ranked_node Node = CommonNode>, edge::weight_t>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::view_for && concepts::heuristic_for diff --git a/include/Simple-Utility/graph/BreadthFirstSearch.hpp b/include/Simple-Utility/graph/BreadthFirstSearch.hpp index d88145bf3..7b91b1064 100644 --- a/include/Simple-Utility/graph/BreadthFirstSearch.hpp +++ b/include/Simple-Utility/graph/BreadthFirstSearch.hpp @@ -20,9 +20,12 @@ namespace sl::graph::dfs { }; + template + using CommonNode = CommonBasicNode; + template < class View, - concepts::basic_node Node = CommonBasicNode>>, + concepts::basic_node Node = CommonNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::view_for && (!concepts::ranked_node) diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index 9dde3bdd0..1955df571 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -20,9 +20,12 @@ namespace sl::graph::dfs { }; + template + using CommonNode = CommonBasicNode; + template < class View, - concepts::basic_node Node = CommonBasicNode>>, + concepts::basic_node Node = CommonNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::view_for && (!concepts::ranked_node) diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp index f8121f99d..c1650cd08 100644 --- a/include/Simple-Utility/graph/UniformCostSearch.hpp +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -20,9 +20,12 @@ namespace sl::graph::ucs { }; + template + using CommonNode = CommonRankedNode; + template < class View, - concepts::ranked_node Node = CommonRankedNode>, edge::weight_t>>, + concepts::ranked_node Node = CommonNode>, edge::weight_t>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::view_for using Range = IterableTraverser< diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index 1a6be097f..621095ef9 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -16,7 +16,7 @@ namespace { inline const std::vector< std::tuple< - std::vector>>>, + std::vector>>>, std::string, // origin std::string // destination >> testResults{ @@ -61,7 +61,7 @@ namespace constexpr auto toCommonAStarNode = [](const Node& node) { - return sg::astar::Node, sg::node::rank_t>{ + return sg::astar::CommonNode, sg::node::rank_t>{ sg::node::vertex(node), node.cost, node.estimatedPendingCost @@ -70,7 +70,7 @@ namespace constexpr auto toDepthAStarNode = [](const sg::decorator::DepthNode& node) { - return sg::decorator::DepthNode, sg::node::rank_t>>{ + return sg::decorator::DepthNode, sg::node::rank_t>>{ {toCommonAStarNode(node)}, node.depth }; @@ -78,7 +78,7 @@ namespace constexpr auto toPredecessorAStarNode = [](const sg::decorator::PredecessorNode& node) { - return sg::decorator::PredecessorNode, sg::node::rank_t>>{ + return sg::decorator::PredecessorNode, sg::node::rank_t>>{ {toCommonAStarNode(node)}, node.predecessor }; @@ -90,7 +90,7 @@ TEMPLATE_TEST_CASE( "[graph][graph::astar]", WeightedViewStub) { - using Node = sg::astar::Node; + using Node = sg::astar::CommonNode; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toCommonAStarNode))); sg::astar::Range range{ @@ -112,7 +112,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::decorator::DepthNode>; + using Node = sg::decorator::DepthNode>; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toDepthAStarNode))); sg::astar::Range range{ @@ -134,7 +134,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::decorator::PredecessorNode>; + using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorAStarNode))); sg::astar::Range range{ @@ -156,7 +156,7 @@ TEMPLATE_TEST_CASE( WeightedViewStub ) { - using Node = sg::decorator::DepthNode>>; + using Node = sg::decorator::DepthNode>>; const auto& [expected, origin, destination] = GENERATE(from_range(testResults)); sg::astar::Range range{ From b08cd14ae95329a17d4c244c3323789b560e65b9 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 5 Oct 2023 19:41:19 +0200 Subject: [PATCH 197/256] test: add IterableTraverser trait tests --- tests/graph/Traverse.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 0bf1cf480..521c2c84e 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -320,3 +320,11 @@ TEMPLATE_TEST_CASE( STATIC_REQUIRE(!requires{std::declval().begin(); }); STATIC_REQUIRE(!requires{std::declval().begin(); }); } + +TEST_CASE("graph::IterableTraverser can be used with std::ranges traits.", "[graph][graph::traverser]") +{ + using Range = sg::IterableTraverser>; + + STATIC_REQUIRE(std::same_as>); + STATIC_REQUIRE(std::same_as>); +} From c6d4edd865b67f42a0ac52a5f62fbf7239e5c256 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 5 Oct 2023 22:13:05 +0200 Subject: [PATCH 198/256] cleanup: revisit Node.hpp includes --- include/Simple-Utility/graph/Node.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 4f64624fa..988cac012 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -8,13 +8,13 @@ #pragma once -#include "Simple-Utility/Config.hpp" #include "Simple-Utility/Utility.hpp" #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Edge.hpp" -#include +// ReSharper disable once CppUnusedIncludeDirective +#include // std::invoke #include namespace sl::graph::customize From 1669886df28858bc5e16afb15172f71f66cb2ee4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 5 Oct 2023 22:13:43 +0200 Subject: [PATCH 199/256] feat: view::edges customization point --- include/Simple-Utility/graph/View.hpp | 116 +++++++++++++++++++++----- tests/graph/View.cpp | 78 ++++++++++++++++- 2 files changed, 169 insertions(+), 25 deletions(-) diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp index faa2e9763..a57608fab 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 69a59452c..06876834b 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]", From 90356499eaf087d8f3286bf799e09e7111443bc2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 5 Oct 2023 22:18:16 +0200 Subject: [PATCH 200/256] test: adapt test case to matcher --- tests/graph/View.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/graph/View.cpp b/tests/graph/View.cpp index 06876834b..e8bee47e2 100644 --- a/tests/graph/View.cpp +++ b/tests/graph/View.cpp @@ -6,6 +6,7 @@ #include "Simple-Utility/graph/View.hpp" #include +#include #include "Defines.hpp" @@ -88,7 +89,7 @@ TEST_CASE( const member_fun_get_edges mock{}; REQUIRE_CALL(mock, edges(node)) .RETURN(expected); - REQUIRE(expected == sg::view::edges(mock, node)); + REQUIRE_THAT(sg::view::edges(mock, node), Catch::Matchers::RangeEquals(expected)); } SECTION("Access via the free function.") @@ -96,7 +97,7 @@ TEST_CASE( const free_fun_get_edges mock{}; REQUIRE_CALL(mock, get_edges(node)) .RETURN(expected); - REQUIRE(expected == sg::view::edges(mock, node)); + REQUIRE_THAT(sg::view::edges(mock, node), Catch::Matchers::RangeEquals(expected)); } SECTION("Access via customized function.") @@ -104,7 +105,7 @@ TEST_CASE( const customized_get_edges mock{}; REQUIRE_CALL(mock, get_edges(node)) .RETURN(expected); - REQUIRE(expected == sg::view::edges(mock, node)); + REQUIRE_THAT(sg::view::edges(mock, node), Catch::Matchers::RangeEquals(expected)); } } From 98c79a976631118d69df1a984f16cd7d94b86c6c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 5 Oct 2023 23:49:58 +0200 Subject: [PATCH 201/256] fix: make use of view::edges customization point --- include/Simple-Utility/graph/Traverse.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 59bdf387e..fae49c2b7 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -135,7 +135,7 @@ namespace sl::graph::detail [[nodiscard]] constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) { - auto edges = graph.edges(current); + auto edges = view::edges(graph, current); std::vector> results{}; if constexpr (std::ranges::sized_range) @@ -159,7 +159,7 @@ namespace sl::graph::detail [[nodiscard]] constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) const { - return graph.edges(current) + return view::edges(graph, current) | std::views::filter([&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); } }; From 781f1cc131ed628d388eb921ade98910d8cb60f0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 02:37:12 +0200 Subject: [PATCH 202/256] fix: please msvc v142 --- include/Simple-Utility/graph/View.hpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp index a57608fab..457af7379 100644 --- a/include/Simple-Utility/graph/View.hpp +++ b/include/Simple-Utility/graph/View.hpp @@ -56,13 +56,16 @@ namespace sl::graph::detail return customize::edges_fn{}(view, node); } + // pleases msvc v142 + // ReSharper disable CppRedundantTemplateKeyword + // ReSharper disable CppRedundantTypenameKeyword 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>; + typename view::template edge_t>; } constexpr decltype(auto) edges(const View& view, const Node& node, const priority_tag<1>) noexcept(noexcept(view.edges(node))) { @@ -75,7 +78,7 @@ namespace sl::graph::detail { edges(view, node) } -> std::ranges::input_range; requires std::convertible_to< std::ranges::range_reference_t, - view::edge_t>; + typename view::template edge_t>; } constexpr decltype(auto) edges(const View& view, const Node& node, const priority_tag<0>) noexcept(noexcept(edges(view, node))) { @@ -90,7 +93,7 @@ namespace sl::graph::detail { detail::edges(view, node, tag) } -> std::ranges::input_range; requires std::convertible_to< std::ranges::range_reference_t, - view::edge_t>; + typename view::template edge_t>; } constexpr decltype(auto) operator ()( const View& view, @@ -100,6 +103,9 @@ namespace sl::graph::detail return detail::edges(view, node, priority_tag<2>{}); } }; + + // ReSharper restore CppRedundantTemplateKeyword + // ReSharper restore CppRedundantTypenameKeyword } namespace sl::graph::view @@ -115,13 +121,16 @@ namespace sl::graph::concepts && requires(const T& view, const Node& node) { // fixes compile error on msvc v142 - // ReSharper disable once CppRedundantTemplateKeyword + // ReSharper disable CppRedundantTemplateKeyword + // ReSharper disable CppRedundantTypenameKeyword typename view::template traits::edge_type; - requires edge_for, Node>; + requires edge_for, Node>; { view::edges(view, node) } -> std::ranges::input_range; requires std::convertible_to< std::ranges::range_value_t>, - view::edge_t>; + typename view::template edge_t>; + // ReSharper restore CppRedundantTemplateKeyword + // ReSharper restore CppRedundantTypenameKeyword }; } From a62c0a5b3f4eb7d9ca04704d2dfddc9a0795d64c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 02:44:18 +0200 Subject: [PATCH 203/256] fix: please gcc 10 --- tests/graph/Defines.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index e2df723ca..3ff970886 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -281,16 +281,22 @@ constexpr auto toCommonRankedNode = [](const Nod constexpr auto toDepthRankedNode = [](const sg::decorator::DepthNode& node) { - return sg::decorator::DepthNode, sg::node::rank_t>>{ + // leave code as-is, because directly returning the temporary results in an ICE on gcc10 + sg::decorator::DepthNode, sg::node::rank_t>> sliced{ {toCommonRankedNode(node)}, node.depth }; + + return sliced; }; constexpr auto toPredecessorRankedNode = [](const sg::decorator::PredecessorNode& node) { - return sg::decorator::PredecessorNode, sg::node::rank_t>>{ + // leave code as-is, because directly returning the temporary results in an ICE on gcc10 + sg::decorator::PredecessorNode, sg::node::rank_t>> sliced{ {toCommonRankedNode(node)}, node.predecessor }; + + return sliced; }; From 6745c51490d1685ee6d2a4f392f85cef82b7bf07 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 02:52:47 +0200 Subject: [PATCH 204/256] fix: please coverage runner --- tests/graph/View.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/graph/View.cpp b/tests/graph/View.cpp index e8bee47e2..6048a98d1 100644 --- a/tests/graph/View.cpp +++ b/tests/graph/View.cpp @@ -45,8 +45,10 @@ namespace template requires sg::concepts::edge_for - // ReSharper disable once CppFunctionIsNotImplemented - static std::vector edges(const Node&); // NOLINT(clang-diagnostic-undefined-internal) + static std::vector edges(const Node&) + { + return {}; + } }; static_assert(sg::concepts::view_for, GenericBasicNode>); @@ -58,8 +60,10 @@ namespace template requires sg::concepts::edge_for - // ReSharper disable once CppFunctionIsNotImplemented - static std::vector edges(const Node&); // NOLINT(clang-diagnostic-undefined-internal) + static std::vector edges(const Node&) + { + return {}; + } }; } From f53c2d4933b3656fabc2cc2b6ba0a1ce0c73e023 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 17:53:42 +0200 Subject: [PATCH 205/256] add benchmarking vs boost::graph --- CMakeLists.txt | 6 + benchmarks/CMakeLists.txt | 25 ++ benchmarks/graph/AStarBoostComparison.cpp | 409 ++++++++++++++++++++++ benchmarks/graph/CMakeLists.txt | 5 + 4 files changed, 445 insertions(+) create mode 100644 benchmarks/CMakeLists.txt create mode 100644 benchmarks/graph/AStarBoostComparison.cpp create mode 100644 benchmarks/graph/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c481d3f8..493c66adb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,12 @@ if (SIMPLE_UTILITY_BUILD_TESTS OR CMAKE_SOURCE_DIR STREQUAL Simple-Utility_SOURC add_subdirectory("tests") endif() +OPTION(SIMPLE_UTILITY_BUILD_BENCHMARKS "Determines whether benchmarks will be built." OFF) +if (SIMPLE_UTILITY_BUILD_BENCHMARKS) + include(CTest) + add_subdirectory("benchmarks") +endif() + option(SIMPLE_UTILITY_GEN_DOCS_ENABLED "Enables the GenerateDocs target." OFF) if (SIMPLE_UTILITY_GEN_DOCS_ENABLED) add_subdirectory("docs") diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 000000000..e55088263 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,25 @@ +CPMAddPackage("gh:catchorg/Catch2@3.4.0") +include("${Catch2_SOURCE_DIR}/extras/Catch.cmake") + +CPMAddPackage("gh:kokkos/mdspan#mdspan-0.6.0") +CPMAddPackage( + name boost + URL "https://github.com/boostorg/boost/releases/download/boost-1.83.0/boost-1.83.0.zip" +) + +add_executable( + Simple-Utility-Benchmarks +) + +add_subdirectory("graph") + +target_link_libraries( + Simple-Utility-Benchmarks + PRIVATE + Simple::Utility + Catch2::Catch2WithMain + std::mdspan + Boost::graph +) + +catch_discover_tests(Simple-Utility-Benchmarks) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp new file mode 100644 index 000000000..018ea3193 --- /dev/null +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -0,0 +1,409 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include + +#include +namespace stdexp = std::experimental; + +#include +#include +#include + +#include +#include +#include + +#include + +#include "Simple-Utility/graph/AStarSearch.hpp" + +/* Most of this code is directly taken from https://github.com/boostorg/graph/blob/develop/example/astar_maze.cpp + * and are used for benchmarking both, boost::graph and sl::graph. + */ + +// Distance traveled in the maze +using distance = double; + +using grid = boost::grid_graph<2>; +using vertex_descriptor = boost::graph_traits::vertex_descriptor; +using edge_descriptor = boost::graph_traits::edge_descriptor; +using vertices_size_type = boost::graph_traits::vertices_size_type; + +struct vertex_hash +{ + using argument_type = vertex_descriptor; + using result_type = std::size_t; + + std::size_t operator()(const vertex_descriptor& u) const + { + std::size_t seed = 0; + boost::hash_combine(seed, u[0]); + boost::hash_combine(seed, u[1]); + return seed; + } +}; + +using vertex_set = boost::unordered_set; +using filtered_grid = boost::vertex_subset_complement_filter::type; + +// A searchable maze +// +// The maze is grid of locations which can either be empty or contain a +// barrier. You can move to an adjacent location in the grid by going up, +// down, left and right. Moving onto a barrier is not allowed. The maze can +// be solved by finding a path from the lower-left-hand corner to the +// upper-right-hand corner. If no open path exists between these two +// locations, the maze is unsolvable. +// +// The maze is implemented as a filtered grid graph where locations are +// vertices. Barrier vertices are filtered out of the graph. +// +// A-star search is used to find a path through the maze. Each edge has a +// weight of one, so the total path length is equal to the number of edges +// traversed. +class maze +{ +public: + friend void random_maze(maze&, std::uint32_t); + + maze(std::size_t x, std::size_t y) + : m_grid(create_grid(x, y)), + m_barrier_grid(create_barrier_grid()) + { + } + + // The length of the maze along the specified dimension. + vertices_size_type length(std::size_t d) const { return m_grid.length(d); } + + bool has_barrier(vertex_descriptor u) const + { + return m_barriers.find(u) != m_barriers.end(); + } + + // Try to find a path from the lower-left-hand corner source (0,0) to the + // upper-right-hand corner goal (x-1, y-1). + vertex_descriptor source() const { return vertex(0, m_grid); } + + vertex_descriptor goal() const + { + return vertex(num_vertices(m_grid) - 1, m_grid); + } + + std::optional> solve(); + + bool solved() const { return !m_solution.empty(); } + + bool solution_contains(vertex_descriptor u) const + { + return m_solution.find(u) != m_solution.end(); + } + + const filtered_grid& get_grid() const noexcept + { + return m_barrier_grid; + } + +private: + // Create the underlying rank-2 grid with the specified dimensions. + grid create_grid(std::size_t x, std::size_t y) + { + boost::array lengths = {{x, y}}; + return grid(lengths); + } + + // Filter the barrier vertices out of the underlying grid. + filtered_grid create_barrier_grid() + { + return boost::make_vertex_subset_complement_filter(m_grid, m_barriers); + } + + // The grid underlying the maze + grid m_grid; + // The barriers in the maze + vertex_set m_barriers; + // The underlying maze grid with barrier vertices filtered out + filtered_grid m_barrier_grid; + // The vertices on a solution path through the maze + vertex_set m_solution; + // The length of the solution path + distance m_solution_length; +}; + +// Euclidean heuristic for a grid +// +// This calculates the Euclidean distance between a vertex and a goal +// vertex. +class euclidean_heuristic + : public boost::astar_heuristic +{ +public: + euclidean_heuristic(vertex_descriptor goal) + : m_goal(goal) + { + }; + + double operator()(vertex_descriptor v) const + { + return sqrt( + pow(static_cast(m_goal[0] - v[0]), 2) + + pow(static_cast(m_goal[1] - v[1]), 2)); + } + +private: + vertex_descriptor m_goal; +}; + +// Exception thrown when the goal vertex is found +struct found_goal +{ +}; + +// Visitor that terminates when we find the goal vertex +struct astar_goal_visitor : public boost::default_astar_visitor +{ + astar_goal_visitor(vertex_descriptor goal) + : m_goal(goal) + { + }; + + void examine_vertex(vertex_descriptor u, const filtered_grid&) + { + if (u == m_goal) + { + throw found_goal(); + } + } + +private: + vertex_descriptor m_goal; +}; + +// Solve the maze using A-star search. Return true if a solution was found. +std::optional> maze::solve() +{ + boost::static_property_map weight(1); + // The predecessor map is a vertex-to-vertex mapping. + using pred_map = std::unordered_map; + pred_map predecessor; + boost::associative_property_map pred_pmap(predecessor); + // The distance map is a vertex-to-distance mapping. + using dist_map = std::unordered_map; + dist_map distance; + boost::associative_property_map dist_pmap(distance); + + vertex_descriptor s = source(); + vertex_descriptor g = goal(); + euclidean_heuristic heuristic(g); + astar_goal_visitor visitor(g); + + try + { + astar_search( + m_barrier_grid, + s, + heuristic, + boost::weight_map(weight) + .predecessor_map(pred_pmap) + .distance_map(dist_pmap) + .visitor(visitor)); + } + catch (found_goal fg) + { + // Walk backwards from the goal through the predecessor chain adding + // vertices to the solution path. + for (vertex_descriptor u = g; u != s; u = predecessor[u]) + { + m_solution.insert(u); + } + m_solution.insert(s); + m_solution_length = distance[g]; + return std::tuple{m_solution, m_solution_length}; + } + + return std::nullopt; +} + +// Generate a maze with a random assignment of barriers. +void random_maze(maze& m, const std::uint32_t seed) +{ + vertices_size_type n = num_vertices(m.m_grid); + vertex_descriptor s = m.source(); + vertex_descriptor g = m.goal(); + // One quarter of the cells in the maze should be barriers. + int barriers = n / 4; + + std::mt19937 rng{seed}; + while (barriers > 0) + { + // Choose horizontal or vertical direction. + std::size_t direction = std::uniform_int{0, 1}(rng); + // Walls range up to one quarter the dimension length in this direction. + vertices_size_type wall = std::uniform_int{1, m.length(direction) / 4}(rng); + // Create the wall while decrementing the total barrier count. + vertex_descriptor u = vertex(std::uniform_int{0, n - 1}(rng), m.m_grid); + while (wall) + { + // Start and goal spaces should never be barriers. + if (u != s && u != g) + { + wall--; + if (!m.has_barrier(u)) + { + m.m_barriers.insert(u); + barriers--; + } + } + vertex_descriptor v = m.m_grid.next(u, direction); + // Stop creating this wall if we reached the maze's edge. + if (u == v) + { + break; + } + u = v; + } + } +} + +/* ############################# + * Begin sl::graph related symbols + ############################## */ + +template <> +struct sl::graph::view::traits> +{ + using edge_type = CommonWeightedEdge; +}; + +template <> +struct sl::graph::customize::edges_fn> +{ + using edge_type = view::edge_t>; + using vertex_type = edge::vertex_t; + using weight_type = edge::weight_t; + + constexpr auto operator ()(const maze& m, const auto& current) const + { + const auto& g = m.get_grid(); + const auto [edgesBegin, edgesEnd] = out_edges(node::vertex(current), g); + return std::ranges::subrange{edgesBegin, edgesEnd} + | std::views::transform([&](const auto& e) { return edge_type{target(e, g), 1.}; }); + } +}; + +std::optional> sl_graph_solve(const maze& m) +{ + namespace sg = sl::graph; + + using Node = sg::decorator::PredecessorNode>; + using Range = sg::astar::Range< + std::reference_wrapper, + euclidean_heuristic, + Node, + sg::tracker::CommonHashMap>; + + Range range{ + m.source(), + std::make_tuple(std::ref(m)), + std::tuple{}, + std::tuple{}, + std::tuple{euclidean_heuristic{m.goal()}, sg::astar::NodeFactory{}}, + std::tuple{} + }; + + std::unordered_map nodes{}; + for (const auto& node : range) + { + nodes.emplace(node.vertex, node); + if (node.vertex == m.goal()) + { + break; + } + } + + if (nodes.contains(m.goal())) + { + // Walk backwards from the goal through the predecessor chain adding + // vertices to the solution path. + vertex_set solution{}; + for (Node current = nodes[m.goal()]; current.predecessor; current = nodes[*current.predecessor]) + { + solution.emplace(current.vertex); + } + solution.emplace(m.source()); + + return std::tuple{solution, nodes[m.goal()].cost}; + } + + return std::nullopt; +} + +/* ############################# + * End sl::graph related symbols + ############################## */ + +TEST_CASE("", "[comparison]") +{ + +} + +TEMPLATE_TEST_CASE_SIG( + "sl::graph and boost::graph generate equally good solutions", + "[vs_boost][comparison]", + ((int width, int height), width, height), + (8, 8), + (16, 16), + (32, 32), + (64, 64), + (128, 128), + (256, 256), + (512, 512), + (1024, 1024) +) +{ + maze m{width, height}; + random_maze(m, Catch::getSeed()); + + const std::optional boostSolution = [m]() mutable { return m.solve(); }(); + const std::optional slSolution = [m] { return sl_graph_solve(m); }(); + + REQUIRE(boostSolution.has_value() == slSolution.has_value()); + if (boostSolution.has_value()) + { + REQUIRE(std::get<1>(*boostSolution) == Catch::Approx{std::get<1>(*slSolution)}); + } +} + +TEMPLATE_TEST_CASE_SIG( + "Benchmarking sl::graph vs boost::graph AStar.", + "[vs_boost][benchmark][benchmark::graph]", + ((int width, int height), width, height), + (8, 8), + (16, 16), + (32, 32), + (64, 64), + (128, 128), + (256, 256), + (512, 512), + (1024, 1024) +) +{ + maze m{width, height}; + random_maze(m, Catch::getSeed()); + + BENCHMARK("boost::graph") + { + return m.solve(); + }; + + BENCHMARK("sl::graph") + { + return sl_graph_solve(m); + }; +} diff --git a/benchmarks/graph/CMakeLists.txt b/benchmarks/graph/CMakeLists.txt new file mode 100644 index 000000000..c72da6926 --- /dev/null +++ b/benchmarks/graph/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources( + Simple-Utility-Benchmarks + PRIVATE + "AStarBoostComparison.cpp" + ) From 514d491f6628db0ffa7f019c03da8a77244b9c78 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 18:11:15 +0200 Subject: [PATCH 206/256] cleanup: remove unnecessary TEST_CASE --- benchmarks/graph/AStarBoostComparison.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 018ea3193..fbffc3644 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -348,11 +348,6 @@ std::optional> sl_graph_solve(const maze& m) * End sl::graph related symbols ############################## */ -TEST_CASE("", "[comparison]") -{ - -} - TEMPLATE_TEST_CASE_SIG( "sl::graph and boost::graph generate equally good solutions", "[vs_boost][comparison]", From 8413b5e6925c4e36073b855026a39028ddd8619a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 19:14:38 +0200 Subject: [PATCH 207/256] refactor: apply some modernizations in boost benchmark --- benchmarks/graph/AStarBoostComparison.cpp | 131 +++++++++------------- 1 file changed, 51 insertions(+), 80 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index fbffc3644..0b64e40ee 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -72,67 +72,47 @@ class maze public: friend void random_maze(maze&, std::uint32_t); - maze(std::size_t x, std::size_t y) - : m_grid(create_grid(x, y)), - m_barrier_grid(create_barrier_grid()) + explicit maze(const std::size_t x, const std::size_t y) + : m_Grid{{x, y}}, + m_BarrierGrid(make_vertex_subset_complement_filter(m_Grid, m_Barriers)) { } // The length of the maze along the specified dimension. - vertices_size_type length(std::size_t d) const { return m_grid.length(d); } + vertices_size_type length(std::size_t d) const { return m_Grid.length(d); } bool has_barrier(vertex_descriptor u) const { - return m_barriers.find(u) != m_barriers.end(); + return m_Barriers.find(u) != m_Barriers.end(); } // Try to find a path from the lower-left-hand corner source (0,0) to the // upper-right-hand corner goal (x-1, y-1). - vertex_descriptor source() const { return vertex(0, m_grid); } + vertex_descriptor source() const { return vertex(0, m_Grid); } vertex_descriptor goal() const { - return vertex(num_vertices(m_grid) - 1, m_grid); + return vertex(num_vertices(m_Grid) - 1, m_Grid); } std::optional> solve(); - bool solved() const { return !m_solution.empty(); } - - bool solution_contains(vertex_descriptor u) const - { - return m_solution.find(u) != m_solution.end(); - } - const filtered_grid& get_grid() const noexcept { - return m_barrier_grid; + return m_BarrierGrid; } private: - // Create the underlying rank-2 grid with the specified dimensions. - grid create_grid(std::size_t x, std::size_t y) - { - boost::array lengths = {{x, y}}; - return grid(lengths); - } - - // Filter the barrier vertices out of the underlying grid. - filtered_grid create_barrier_grid() - { - return boost::make_vertex_subset_complement_filter(m_grid, m_barriers); - } - // The grid underlying the maze - grid m_grid; + grid m_Grid; // The barriers in the maze - vertex_set m_barriers; + vertex_set m_Barriers{}; // The underlying maze grid with barrier vertices filtered out - filtered_grid m_barrier_grid; + filtered_grid m_BarrierGrid; // The vertices on a solution path through the maze - vertex_set m_solution; + vertex_set m_Solution{}; // The length of the solution path - distance m_solution_length; + distance m_SolutionLength{}; }; // Euclidean heuristic for a grid @@ -143,20 +123,20 @@ class euclidean_heuristic : public boost::astar_heuristic { public: - euclidean_heuristic(vertex_descriptor goal) - : m_goal(goal) + explicit euclidean_heuristic(const vertex_descriptor& goal) + : m_Goal{goal} { - }; + } double operator()(vertex_descriptor v) const { - return sqrt( - pow(static_cast(m_goal[0] - v[0]), 2) - + pow(static_cast(m_goal[1] - v[1]), 2)); + return std::sqrt( + std::pow(static_cast(m_Goal[0] - v[0]), 2) + + std::pow(static_cast(m_Goal[1] - v[1]), 2)); } private: - vertex_descriptor m_goal; + vertex_descriptor m_Goal; }; // Exception thrown when the goal vertex is found @@ -167,64 +147,55 @@ struct found_goal // Visitor that terminates when we find the goal vertex struct astar_goal_visitor : public boost::default_astar_visitor { - astar_goal_visitor(vertex_descriptor goal) - : m_goal(goal) + explicit astar_goal_visitor(const vertex_descriptor& goal) + : m_Goal{goal} { - }; + } - void examine_vertex(vertex_descriptor u, const filtered_grid&) + void examine_vertex(const vertex_descriptor& u, const filtered_grid&) const { - if (u == m_goal) + if (u == m_Goal) { - throw found_goal(); + throw found_goal{}; } } private: - vertex_descriptor m_goal; + vertex_descriptor m_Goal; }; // Solve the maze using A-star search. Return true if a solution was found. std::optional> maze::solve() { - boost::static_property_map weight(1); // The predecessor map is a vertex-to-vertex mapping. - using pred_map = std::unordered_map; - pred_map predecessor; - boost::associative_property_map pred_pmap(predecessor); + std::unordered_map predecessors{}; // The distance map is a vertex-to-distance mapping. - using dist_map = std::unordered_map; - dist_map distance; - boost::associative_property_map dist_pmap(distance); + std::unordered_map distances{}; - vertex_descriptor s = source(); - vertex_descriptor g = goal(); - euclidean_heuristic heuristic(g); - astar_goal_visitor visitor(g); + const vertex_descriptor g = goal(); try { astar_search( - m_barrier_grid, - s, - heuristic, - boost::weight_map(weight) - .predecessor_map(pred_pmap) - .distance_map(dist_pmap) - .visitor(visitor)); + m_BarrierGrid, + source(), + euclidean_heuristic{g}, + weight_map(boost::static_property_map{distance{1}}) + .predecessor_map(boost::associative_property_map{predecessors}) + .distance_map(boost::associative_property_map{distances}) + .visitor(astar_goal_visitor{g})); } - catch (found_goal fg) + catch (const found_goal&) { // Walk backwards from the goal through the predecessor chain adding // vertices to the solution path. - for (vertex_descriptor u = g; u != s; u = predecessor[u]) + for (vertex_descriptor u = g, s = source(); u != s; u = predecessors[u]) { - m_solution.insert(u); + m_Solution.insert(u); } - m_solution.insert(s); - m_solution_length = distance[g]; - return std::tuple{m_solution, m_solution_length}; + m_Solution.insert(source()); + m_SolutionLength = distances[g]; + return std::tuple{m_Solution, m_SolutionLength}; } return std::nullopt; @@ -233,21 +204,21 @@ std::optional> maze::solve() // Generate a maze with a random assignment of barriers. void random_maze(maze& m, const std::uint32_t seed) { - vertices_size_type n = num_vertices(m.m_grid); - vertex_descriptor s = m.source(); - vertex_descriptor g = m.goal(); + const vertices_size_type n = num_vertices(m.m_Grid); + const vertex_descriptor s = m.source(); + const vertex_descriptor g = m.goal(); // One quarter of the cells in the maze should be barriers. - int barriers = n / 4; + vertices_size_type barriers{n / 4u}; std::mt19937 rng{seed}; while (barriers > 0) { // Choose horizontal or vertical direction. - std::size_t direction = std::uniform_int{0, 1}(rng); + const std::size_t direction = std::uniform_int{0, 1}(rng); // Walls range up to one quarter the dimension length in this direction. vertices_size_type wall = std::uniform_int{1, m.length(direction) / 4}(rng); // Create the wall while decrementing the total barrier count. - vertex_descriptor u = vertex(std::uniform_int{0, n - 1}(rng), m.m_grid); + vertex_descriptor u = vertex(std::uniform_int{0, n - 1}(rng), m.m_Grid); while (wall) { // Start and goal spaces should never be barriers. @@ -256,11 +227,11 @@ void random_maze(maze& m, const std::uint32_t seed) wall--; if (!m.has_barrier(u)) { - m.m_barriers.insert(u); + m.m_Barriers.insert(u); barriers--; } } - vertex_descriptor v = m.m_grid.next(u, direction); + vertex_descriptor v = m.m_Grid.next(u, direction); // Stop creating this wall if we reached the maze's edge. if (u == v) { From 26b9e58e1a472b719c17614cd6d58f365ab7052e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 19:15:30 +0200 Subject: [PATCH 208/256] cleanup: remove unused includes --- benchmarks/graph/AStarBoostComparison.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 0b64e40ee..cdbd3a66f 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -8,9 +8,6 @@ #include #include -#include -namespace stdexp = std::experimental; - #include #include #include From e5901e784ca5691d3c59e4a89ad0b310b5ee5004 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 19:16:33 +0200 Subject: [PATCH 209/256] docs: modify comment --- benchmarks/graph/AStarBoostComparison.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index cdbd3a66f..d4ed4fbba 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -21,7 +21,8 @@ #include "Simple-Utility/graph/AStarSearch.hpp" /* Most of this code is directly taken from https://github.com/boostorg/graph/blob/develop/example/astar_maze.cpp - * and are used for benchmarking both, boost::graph and sl::graph. + * and slightly modernized. + * The example types are used for benchmarking both, boost::graph and sl::graph. */ // Distance traveled in the maze From 472570bbca0363e544d8883f5b39ae0125987019 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 19:36:01 +0200 Subject: [PATCH 210/256] fix: correctly use uniform_int_distribution --- benchmarks/graph/AStarBoostComparison.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index d4ed4fbba..d20e7c2e3 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -212,11 +212,11 @@ void random_maze(maze& m, const std::uint32_t seed) while (barriers > 0) { // Choose horizontal or vertical direction. - const std::size_t direction = std::uniform_int{0, 1}(rng); + const std::size_t direction = std::uniform_int_distribution{0, 1}(rng); // Walls range up to one quarter the dimension length in this direction. - vertices_size_type wall = std::uniform_int{1, m.length(direction) / 4}(rng); + vertices_size_type wall = std::uniform_int_distribution{1, m.length(direction) / 4}(rng); // Create the wall while decrementing the total barrier count. - vertex_descriptor u = vertex(std::uniform_int{0, n - 1}(rng), m.m_Grid); + vertex_descriptor u = vertex(std::uniform_int_distribution{0, n - 1}(rng), m.m_Grid); while (wall) { // Start and goal spaces should never be barriers. From b0202c77c2c3e62226c9df16b5c03f49de0052a2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 19:47:37 +0200 Subject: [PATCH 211/256] fix: grid construction in boost comparison --- benchmarks/graph/AStarBoostComparison.cpp | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index d20e7c2e3..6589e9dbf 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -71,7 +71,7 @@ class maze friend void random_maze(maze&, std::uint32_t); explicit maze(const std::size_t x, const std::size_t y) - : m_Grid{{x, y}}, + : m_Grid{boost::array{{x, y}}}, m_BarrierGrid(make_vertex_subset_complement_filter(m_Grid, m_Barriers)) { } @@ -317,32 +317,32 @@ std::optional> sl_graph_solve(const maze& m) * End sl::graph related symbols ############################## */ -TEMPLATE_TEST_CASE_SIG( - "sl::graph and boost::graph generate equally good solutions", - "[vs_boost][comparison]", - ((int width, int height), width, height), - (8, 8), - (16, 16), - (32, 32), - (64, 64), - (128, 128), - (256, 256), - (512, 512), - (1024, 1024) -) -{ - maze m{width, height}; - random_maze(m, Catch::getSeed()); - - const std::optional boostSolution = [m]() mutable { return m.solve(); }(); - const std::optional slSolution = [m] { return sl_graph_solve(m); }(); - - REQUIRE(boostSolution.has_value() == slSolution.has_value()); - if (boostSolution.has_value()) - { - REQUIRE(std::get<1>(*boostSolution) == Catch::Approx{std::get<1>(*slSolution)}); - } -} +//TEMPLATE_TEST_CASE_SIG( +// "sl::graph and boost::graph generate equally good solutions", +// "[vs_boost][comparison]", +// ((int width, int height), width, height), +// (8, 8), +// (16, 16), +// (32, 32), +// (64, 64), +// (128, 128), +// (256, 256), +// (512, 512), +// (1024, 1024) +//) +//{ +// maze m{width, height}; +// random_maze(m, Catch::getSeed()); +// +// const std::optional boostSolution = [m]() mutable { return m.solve(); }(); +// const std::optional slSolution = [m] { return sl_graph_solve(m); }(); +// +// REQUIRE(boostSolution.has_value() == slSolution.has_value()); +// if (boostSolution.has_value()) +// { +// REQUIRE(std::get<1>(*boostSolution) == Catch::Approx{std::get<1>(*slSolution)}); +// } +//} TEMPLATE_TEST_CASE_SIG( "Benchmarking sl::graph vs boost::graph AStar.", From 66c8c6e2aed6b0f4fbf324f61ea7fc473ff8af9b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 21:20:15 +0200 Subject: [PATCH 212/256] fix: prevent astar benchmark from endless looping --- benchmarks/graph/AStarBoostComparison.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 6589e9dbf..e1eb59f65 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -222,11 +222,14 @@ void random_maze(maze& m, const std::uint32_t seed) // Start and goal spaces should never be barriers. if (u != s && u != g) { - wall--; + --wall; if (!m.has_barrier(u)) { m.m_Barriers.insert(u); - barriers--; + if (0 == --barriers) + { + break; + } } } vertex_descriptor v = m.m_Grid.next(u, direction); From b4e96784fa835a2d051154704b5d20a3d775f669 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 6 Oct 2023 21:20:56 +0200 Subject: [PATCH 213/256] test: re-enable benchmark test cases --- benchmarks/graph/AStarBoostComparison.cpp | 52 +++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index e1eb59f65..3b7d79264 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -320,32 +320,32 @@ std::optional> sl_graph_solve(const maze& m) * End sl::graph related symbols ############################## */ -//TEMPLATE_TEST_CASE_SIG( -// "sl::graph and boost::graph generate equally good solutions", -// "[vs_boost][comparison]", -// ((int width, int height), width, height), -// (8, 8), -// (16, 16), -// (32, 32), -// (64, 64), -// (128, 128), -// (256, 256), -// (512, 512), -// (1024, 1024) -//) -//{ -// maze m{width, height}; -// random_maze(m, Catch::getSeed()); -// -// const std::optional boostSolution = [m]() mutable { return m.solve(); }(); -// const std::optional slSolution = [m] { return sl_graph_solve(m); }(); -// -// REQUIRE(boostSolution.has_value() == slSolution.has_value()); -// if (boostSolution.has_value()) -// { -// REQUIRE(std::get<1>(*boostSolution) == Catch::Approx{std::get<1>(*slSolution)}); -// } -//} +TEMPLATE_TEST_CASE_SIG( + "sl::graph and boost::graph generate equally good solutions", + "[vs_boost][comparison]", + ((int width, int height), width, height), + (8, 8), + (16, 16), + (32, 32), + (64, 64), + (128, 128), + (256, 256), + (512, 512), + (1024, 1024) +) +{ + maze m{width, height}; + random_maze(m, Catch::getSeed()); + + const std::optional boostSolution = [m]() mutable { return m.solve(); }(); + const std::optional slSolution = [m] { return sl_graph_solve(m); }(); + + REQUIRE(boostSolution.has_value() == slSolution.has_value()); + if (boostSolution.has_value()) + { + REQUIRE(std::get<1>(*boostSolution) == Catch::Approx{std::get<1>(*slSolution)}); + } +} TEMPLATE_TEST_CASE_SIG( "Benchmarking sl::graph vs boost::graph AStar.", From 3d8c86787cf8ae63c70d53b2a10aa22c74a68a3c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 7 Oct 2023 00:10:05 +0200 Subject: [PATCH 214/256] feat: store randomized benchmark mazes --- benchmarks/graph/AStarBoostComparison.cpp | 105 ++++++++++++++++++++-- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 3b7d79264..f8bdd455f 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -3,10 +3,13 @@ // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) +#include + #include #include #include #include +#include #include #include @@ -16,7 +19,7 @@ #include #include -#include +#include #include "Simple-Utility/graph/AStarSearch.hpp" @@ -68,6 +71,7 @@ using filtered_grid = boost::vertex_subset_complement_filter:: class maze { public: + friend std::ostream& operator<<(std::ostream&, const maze&); friend void random_maze(maze&, std::uint32_t); explicit maze(const std::size_t x, const std::size_t y) @@ -95,6 +99,13 @@ class maze std::optional> solve(); + bool solved() const { return !m_Solution.empty(); } + + bool solution_contains(vertex_descriptor u) const + { + return m_Solution.find(u) != m_Solution.end(); + } + const filtered_grid& get_grid() const noexcept { return m_BarrierGrid; @@ -171,6 +182,7 @@ std::optional> maze::solve() std::unordered_map distances{}; const vertex_descriptor g = goal(); + const vertex_descriptor s = source(); try { @@ -187,11 +199,11 @@ std::optional> maze::solve() { // Walk backwards from the goal through the predecessor chain adding // vertices to the solution path. - for (vertex_descriptor u = g, s = source(); u != s; u = predecessors[u]) + for (vertex_descriptor u = g; u != s; u = predecessors[u]) { m_Solution.insert(u); } - m_Solution.insert(source()); + m_Solution.insert(s); m_SolutionLength = distances[g]; return std::tuple{m_Solution, m_SolutionLength}; } @@ -320,6 +332,82 @@ std::optional> sl_graph_solve(const maze& m) * End sl::graph related symbols ############################## */ +// Print the maze as an ASCII map. +std::ostream& operator<<(std::ostream& output, const maze& m) +{ + constexpr char barrier = '#'; + constexpr char start = 'S'; + constexpr char goal = 'G'; + + // Header + for (vertices_size_type i = 0; i < m.length(0) + 2; i++) + { + output << barrier; + } + output << std::endl; + // Body + for (vertices_size_type i = 0; i < m.length(1); ++i) + { + const vertices_size_type y = m.length(1) - 1 - i; + // Enumerate rows in reverse order and columns in regular order so that + // (0,0) appears in the lower left-hand corner. This requires that y be + // int and not the unsigned vertices_size_type because the loop exit + // condition is y==-1. + for (vertices_size_type x = 0; x < m.length(0); x++) + { + // Put a barrier on the left-hand side. + if (x == 0) + { + output << barrier; + } + // Put the character representing this point in the maze grid. + vertex_descriptor u = {{x, y}}; + if (u == m.source()) + { + output << start; + } + else if (u == m.goal()) + { + output << goal; + } + else if (m.solution_contains(u)) + { + output << "."; + } + else if (m.has_barrier(u)) + { + output << barrier; + } + else + { + output << " "; + } + // Put a barrier on the right-hand side. + if (x == m.length(0) - 1) + { + output << barrier; + } + } + // Put a newline after every row except the last one. + output << std::endl; + } + // Footer + for (vertices_size_type i = 0; i < m.length(0) + 2; i++) + { + output << barrier; + } + if (m.solved()) + { + output << std::endl << "Solution length " << m.m_SolutionLength; + } + else + { + output << std::endl << "Not solvable"; + } + + return output; +} + TEMPLATE_TEST_CASE_SIG( "sl::graph and boost::graph generate equally good solutions", "[vs_boost][comparison]", @@ -334,10 +422,17 @@ TEMPLATE_TEST_CASE_SIG( (1024, 1024) ) { + const auto seed = Catch::getSeed(); maze m{width, height}; - random_maze(m, Catch::getSeed()); + random_maze(m, seed); - const std::optional boostSolution = [m]() mutable { return m.solve(); }(); + const std::optional boostSolution = [m, fileName = std::format("./{}_{}x{}.maze.txt", seed, width, height)]() mutable + { + auto result = m.solve(); + std::ofstream out{std::filesystem::current_path() / fileName}; + out << m; + return result; + }(); const std::optional slSolution = [m] { return sl_graph_solve(m); }(); REQUIRE(boostSolution.has_value() == slSolution.has_value()); From baa237455c378966da08306e91288873490641d7 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 7 Oct 2023 17:40:32 +0200 Subject: [PATCH 215/256] refactor: merge explorer and kernel implementations --- benchmarks/graph/AStarBoostComparison.cpp | 3 +- include/Simple-Utility/graph/AStarSearch.hpp | 232 +++++------------- .../graph/BreadthFirstSearch.hpp | 2 +- .../Simple-Utility/graph/DepthFirstSearch.hpp | 2 +- include/Simple-Utility/graph/Node.hpp | 4 + include/Simple-Utility/graph/Traverse.hpp | 154 ++++-------- .../graph/UniformCostSearch.hpp | 2 +- tests/graph/AStarSearch.cpp | 12 +- tests/graph/BreadthFirstSearch.cpp | 8 +- tests/graph/DepthFirstSearch.cpp | 8 +- tests/graph/Traverse.cpp | 96 ++++---- tests/graph/UniformCostSearch.cpp | 8 +- 12 files changed, 180 insertions(+), 351 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index f8bdd455f..421ae0f0a 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -297,8 +297,7 @@ std::optional> sl_graph_solve(const maze& m) std::make_tuple(std::ref(m)), std::tuple{}, std::tuple{}, - std::tuple{euclidean_heuristic{m.goal()}, sg::astar::NodeFactory{}}, - std::tuple{} + std::tuple{sg::astar::NodeFactory{euclidean_heuristic{m.goal()}}} }; std::unordered_map nodes{}; diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index d3912598e..b864dcdb1 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -31,136 +31,6 @@ namespace sl::graph::astar::detail assert(0 <= increase && "increase must be non-negative."); assert(increase <= std::numeric_limits::max() - base && "Rank is about to overflow."); } - - template Heuristic, typename NodeFactory> - class BaseKernel - { - public: - [[nodiscard]] - explicit BaseKernel() = default; - - [[nodiscard]] - explicit constexpr BaseKernel( - Heuristic heuristic, - NodeFactory nodeFactory - ) noexcept(std::is_nothrow_move_constructible_v - && std::is_nothrow_move_constructible_v) - : m_Heuristic{std::move(heuristic)}, - m_NodeFactory{std::move(nodeFactory)} - { - } - - constexpr Node operator ()(const node::vertex_t& vertex) const - { - return std::invoke( - m_NodeFactory, - vertex, - std::invoke(m_Heuristic, vertex)); - } - - [[nodiscard]] - constexpr const Heuristic& heuristic() const & noexcept - { - return m_Heuristic; - } - - [[nodiscard]] - constexpr const NodeFactory& node_factory() const & noexcept - { - return m_NodeFactory; - } - - protected: - SL_UTILITY_NO_UNIQUE_ADDRESS Heuristic m_Heuristic{}; - SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; - }; - - template Heuristic, typename NodeFactory> - class BufferedKernel - : public BaseKernel - { - private: - using Super = BaseKernel; - - public: - using Super::Super; - using Super::operator(); - - template - requires std::convertible_to< - std::invoke_result_t< - NodeFactory, - const Node&, - std::ranges::range_reference_t, - std::invoke_result_t>>, - Node> - [[nodiscard]] - constexpr auto operator ()(const Node& current, Edges&& edges) const - { - std::vector results{}; - if constexpr (std::ranges::sized_range) - { - results.reserve(std::ranges::size(edges)); - } - - std::ranges::transform( - std::forward(edges), - std::back_inserter(results), - [&](const auto& edge) - { - return std::invoke( - Super::m_NodeFactory, - current, - edge, - std::invoke(Super::m_Heuristic, edge::destination(edge))); - }); - - return results; - } - }; - -#ifdef SL_UTILITY_HAS_RANGES_VIEWS - template Heuristic, typename NodeFactory> - class LazyKernel - : public BaseKernel - { - private: - using Super = BaseKernel; - - public: - using Super::Super; - using Super::operator(); - - template - requires std::convertible_to< - std::invoke_result_t< - NodeFactory, - const Node&, - std::ranges::range_reference_t, - std::invoke_result_t>>, - Node> - [[nodiscard]] - constexpr auto operator ()(const Node& current, Edges&& edges) const - { - return std::forward(edges) - | std::views::transform( - [&](const auto& edge) - { - return std::invoke( - Super::m_NodeFactory, - current, - edge, - std::invoke(Super::m_Heuristic, edge::destination(edge))); - }); - } - }; - - template - using default_kernel_t = LazyKernel; -#else - template - using default_kernel_t = BufferedKernel; -#endif } namespace sl::graph::astar @@ -186,52 +56,75 @@ namespace sl::graph::astar [[nodiscard]] friend bool operator==(const CommonNode&, const CommonNode&) = default; }; -} -template -struct sl::graph::detail::NodeFactory> -{ -public: - using node_type = astar::CommonNode; - using vertex_type = Vertex; - using rank_type = Rank; + template Heuristic> + struct NodeFactory; - [[nodiscard]] - constexpr node_type operator ()(vertex_type origin, rank_type estimatedPendingCost) const + template > Heuristic> + struct NodeFactory, Heuristic> { - node_type node{ - .vertex = std::move(origin), - .cost = {}, - .estimatedPendingCost = std::move(estimatedPendingCost) - }; - astar::detail::check_bounds(node.cost, node.estimatedPendingCost); + public: + using node_type = CommonNode; + using vertex_type = Vertex; + using rank_type = Rank; - return node; - } + [[nodiscard]] + explicit NodeFactory(Heuristic heuristic) noexcept(std::is_nothrow_move_constructible_v) + : m_Heuristic{std::move(heuristic)} + { + } - template Edge> - [[nodiscard]] - constexpr node_type operator ()(const node_type& current, const Edge& edge, rank_type estimatedPendingCost) const - { - astar::detail::check_bounds(current.cost, edge::weight(edge)); + [[nodiscard]] + constexpr node_type operator ()(const vertex_type& origin) const + { + node_type node{ + .vertex = origin, + .cost = {0}, + .estimatedPendingCost = std::invoke(m_Heuristic, origin) + }; + detail::check_bounds(node.cost, node.estimatedPendingCost); - node_type node{ - .vertex = edge::destination(edge), - .cost = current.cost + edge::weight(edge), - .estimatedPendingCost = std::move(estimatedPendingCost) - }; - astar::detail::check_bounds(node.cost, node.estimatedPendingCost); + return node; + } - return node; - } -}; + template Edge> + [[nodiscard]] + constexpr node_type operator ()(const node_type& current, const Edge& edge) const + { + detail::check_bounds(current.cost, edge::weight(edge)); -namespace sl::graph::astar -{ - template - struct NodeFactory - : public graph::detail::NodeFactory + node_type node{ + .vertex = edge::destination(edge), + .cost = current.cost + edge::weight(edge), + .estimatedPendingCost = std::invoke(m_Heuristic, edge::destination(edge)) + }; + detail::check_bounds(node.cost, node.estimatedPendingCost); + + return node; + } + + private: + SL_UTILITY_NO_UNIQUE_ADDRESS Heuristic m_Heuristic; + }; + + template + struct NodeFactoryTemplate + { + template + using type = NodeFactory; + }; + + template Heuristic> + requires requires { typename decorator::NodeFactory::template type>::node_type; } + struct NodeFactory + : public decorator::NodeFactory::template type> { + private: + using Super = decorator::NodeFactory::template type>; + + public: + using Super::Super; + using Super::operator (); }; template Strategy> @@ -284,10 +177,9 @@ namespace sl::graph::astar View, queue::CommonPriorityQueue, Tracker, - detail::default_kernel_t< + graph::detail::default_explorer_t< Node, - Heuristic, - NodeFactory>>>; + NodeFactory>>>; } #endif diff --git a/include/Simple-Utility/graph/BreadthFirstSearch.hpp b/include/Simple-Utility/graph/BreadthFirstSearch.hpp index 7b91b1064..30ee10a3b 100644 --- a/include/Simple-Utility/graph/BreadthFirstSearch.hpp +++ b/include/Simple-Utility/graph/BreadthFirstSearch.hpp @@ -35,7 +35,7 @@ namespace sl::graph::dfs View, queue::CommonQueue, Tracker, - detail::default_kernel_t>>>; + detail::default_explorer_t>>>; } #endif diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index 1955df571..f7908e52d 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -35,7 +35,7 @@ namespace sl::graph::dfs View, queue::CommonStack, Tracker, - detail::default_kernel_t>>>; + detail::default_explorer_t>>>; } #endif diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 988cac012..c571059ac 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -259,6 +259,8 @@ namespace sl::graph::decorator using node_type = PredecessorNode; using vertex_type = node::vertex_t; + using Super::Super; + template [[nodiscard]] constexpr node_type operator ()(vertex_type origin, Args&&... args) const @@ -307,6 +309,8 @@ namespace sl::graph::decorator using node_type = DepthNode; using vertex_type = node::vertex_t; + using Super::Super; + template [[nodiscard]] constexpr node_type operator ()(vertex_type origin, Args&&... args) const diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index fae49c2b7..cb2b568c9 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -63,142 +63,100 @@ namespace sl::graph::detail { }; - template - class BaseKernel + template + class BasicExplorer { public: [[nodiscard]] - explicit BaseKernel() = default; + explicit BasicExplorer() = default; [[nodiscard]] - explicit constexpr BaseKernel( + explicit constexpr BasicExplorer( NodeFactory nodeFactory ) noexcept(std::is_nothrow_move_constructible_v) : m_NodeFactory{std::move(nodeFactory)} { } - constexpr Node operator ()(const node::vertex_t& vertex) const - { - return std::invoke(m_NodeFactory, vertex); - } - + template [[nodiscard]] - constexpr const NodeFactory& node_factory() const & noexcept + constexpr Node operator ()(const node::vertex_t& vertex, Tracker& tracker) const { - return m_NodeFactory; - } - - protected: - SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; - }; - - template - class BufferedKernel - : public BaseKernel - { - private: - using Super = BaseKernel; + [[maybe_unused]] const bool result = tracker::set_discovered(tracker, vertex); + assert(result && "Tracker returned false (already visited) for the origin node."); - public: - using Super::Super; - using Super::operator(); + return std::invoke(m_NodeFactory, vertex); + } - template + template requires std::convertible_to< std::invoke_result_t< NodeFactory, const Node&, - std::ranges::range_reference_t>, + view::edge_t>, Node> [[nodiscard]] - constexpr auto operator ()(const Node& current, Edges&& edges) const + constexpr auto operator ()(const View& graph, const Node& current, Tracker& tracker) const { - std::vector results{}; - if constexpr (std::ranges::sized_range) - { - results.reserve(std::ranges::size(edges)); - } - - std::ranges::transform( - std::forward(edges), - std::back_inserter(results), - [&](const auto& edge) { return std::invoke(Super::m_NodeFactory, current, edge); }); - - return results; + return std::invoke( + CollectorStrategy{}, + view::edges(graph, current), + current, + m_NodeFactory, + tracker); } + + private: + SL_UTILITY_NO_UNIQUE_ADDRESS NodeFactory m_NodeFactory{}; }; - struct BufferedExplorer + struct BufferedCollector { - template - [[nodiscard]] - constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) + template + constexpr auto operator ()(Edges&& edges, const Node& current, const NodeFactory& nodeFactory, Tracker& tracker) { - auto edges = view::edges(graph, current); - - std::vector> results{}; + std::vector results{}; if constexpr (std::ranges::sized_range) { results.reserve(std::ranges::size(edges)); } - std::ranges::copy_if( - std::move(edges), - std::back_inserter(results), - [&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); + for (const auto& edge : edges) + { + if (tracker::set_discovered(tracker, edge::destination(edge))) + { + results.emplace_back(std::invoke(nodeFactory, current, edge)); + } + } return results; } }; -#ifdef SL_UTILITY_HAS_RANGES_VIEWS - struct LazyExplorer - { - template - [[nodiscard]] - constexpr auto operator ()(const Node& current, const View& graph, Tracker& tracker) const - { - return view::edges(graph, current) - | std::views::filter([&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }); - } - }; - template - class LazyKernel - : public BaseKernel - { - private: - using Super = BaseKernel; + using BufferedExplorer = BasicExplorer; - public: - using Super::Super; - using Super::operator(); +#ifdef SL_UTILITY_HAS_RANGES_VIEWS - template - requires std::convertible_to< - std::invoke_result_t< - NodeFactory, - const Node&, - std::ranges::range_reference_t>, - Node> - [[nodiscard]] - constexpr auto operator ()(const Node& current, Edges&& edges) const + struct LazyCollector + { + template + constexpr auto operator ()(Edges&& edges, const Node& current, const NodeFactory& nodeFactory, Tracker& tracker) { - return std::forward(edges) - | std::views::transform([&](const auto& edge) { return std::invoke(Super::m_NodeFactory, current, edge); }); + return std::views::all(std::forward(edges)) + | std::views::filter([&](const auto& edge) { return tracker::set_discovered(tracker, edge::destination(edge)); }) + | std::views::transform([&](const auto& edge) { return std::invoke(nodeFactory, current, edge); }); } }; - using default_explorer_t = LazyExplorer; + template + using LazyExplorer = BasicExplorer; template - using default_kernel_t = LazyKernel; + using default_explorer_t = BufferedExplorer; #else - using default_explorer_t = BufferedExplorer; - template - using default_kernel_t = BufferedKernel; + using default_explorer_t = BufferedExplorer; #endif template < @@ -206,8 +164,7 @@ namespace sl::graph::detail concepts::view_for View, concepts::queue_for QueueStrategy, concepts::tracker_for> TrackingStrategy, - typename KernelStrategy = default_kernel_t>, - typename ExplorationStrategy = default_explorer_t> + typename ExplorationStrategy = default_explorer_t>> requires concepts::edge_for, Node> class BasicTraverser { @@ -230,12 +187,10 @@ namespace sl::graph::detail typename... ViewArgs, typename... QueueArgs, typename... TrackerArgs, - typename... KernelArgs, typename... ExplorerArgs> requires std::constructible_from && std::constructible_from && std::constructible_from - && std::constructible_from && std::constructible_from [[nodiscard]] explicit constexpr BasicTraverser( @@ -243,21 +198,16 @@ namespace sl::graph::detail std::tuple viewArgs, std::tuple queueArgs, std::tuple trackerArgs, - std::tuple kernelArgs, std::tuple explorerArgs ) : m_Explorer{std::make_from_tuple(std::move(explorerArgs))}, - m_Kernel{std::make_from_tuple(std::move(kernelArgs))}, m_Queue{std::make_from_tuple(std::move(queueArgs))}, m_Tracker{std::make_from_tuple(std::move(trackerArgs))}, m_View{std::make_from_tuple(std::move(viewArgs))} { assert(queue::empty(m_Queue) && "Queue already contains elements."); - const bool result = tracker::set_discovered(m_Tracker, origin); - assert(result && "Tracker returned false (already visited) for the origin node."); - - queue::insert(m_Queue, std::array{std::invoke(m_Kernel, origin)}); + queue::insert(m_Queue, std::array{std::invoke(m_Explorer, origin, m_Tracker)}); } [[nodiscard]] @@ -284,10 +234,7 @@ namespace sl::graph::detail { queue::insert( m_Queue, - std::invoke( - m_Kernel, - *result, - std::invoke(m_Explorer, *result, m_View, m_Tracker))); + std::invoke(m_Explorer, m_View, *result, m_Tracker)); } return result; @@ -313,10 +260,9 @@ namespace sl::graph::detail private: SL_UTILITY_NO_UNIQUE_ADDRESS ExplorationStrategy m_Explorer{}; - KernelStrategy m_Kernel{}; queue_type m_Queue; tracker_type m_Tracker; - View m_View; + view_type m_View; }; } diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp index c1650cd08..f80ef2902 100644 --- a/include/Simple-Utility/graph/UniformCostSearch.hpp +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -34,7 +34,7 @@ namespace sl::graph::ucs View, queue::CommonPriorityQueue, Tracker, - detail::default_kernel_t>>>; + detail::default_explorer_t>>>; } #endif diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index 621095ef9..e0b151247 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -98,8 +98,7 @@ TEMPLATE_TEST_CASE( std::tuple{TestType{}}, std::tuple{}, std::tuple{}, - std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, - std::tuple{} + std::tuple{sg::astar::NodeFactory{Heuristic{destination}}} }; STATIC_CHECK(std::ranges::input_range); @@ -120,8 +119,7 @@ TEMPLATE_TEST_CASE( std::tuple{TestType{}}, std::tuple{}, std::tuple{}, - std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, - std::tuple{} + std::tuple{sg::astar::NodeFactory{Heuristic{destination}}} }; STATIC_CHECK(std::ranges::input_range); @@ -142,8 +140,7 @@ TEMPLATE_TEST_CASE( std::tuple{TestType{}}, std::tuple{}, std::tuple{}, - std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, - std::tuple{} + std::tuple{sg::astar::NodeFactory{Heuristic{destination}}} }; STATIC_CHECK(std::ranges::input_range); @@ -164,8 +161,7 @@ TEMPLATE_TEST_CASE( std::tuple{TestType{}}, std::tuple{}, std::tuple{}, - std::tuple{Heuristic{destination}, sg::astar::NodeFactory{}}, - std::tuple{} + std::tuple{sg::astar::NodeFactory{Heuristic{destination}}} }; STATIC_CHECK(std::ranges::input_range); diff --git a/tests/graph/BreadthFirstSearch.cpp b/tests/graph/BreadthFirstSearch.cpp index 6a95ccb7a..905c39a79 100644 --- a/tests/graph/BreadthFirstSearch.cpp +++ b/tests/graph/BreadthFirstSearch.cpp @@ -52,7 +52,7 @@ TEMPLATE_TEST_CASE( using Node = sg::CommonBasicNode; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); @@ -68,7 +68,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); @@ -84,7 +84,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); @@ -100,7 +100,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 8e74d91ba..7db4df319 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -52,7 +52,7 @@ TEMPLATE_TEST_CASE( using Node = sg::CommonBasicNode; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); @@ -68,7 +68,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); @@ -84,7 +84,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); @@ -100,7 +100,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 521c2c84e..88f12b064 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -51,44 +51,13 @@ namespace }; } -using TestExplorers = std::tuple< -#ifdef SL_UTILITY_HAS_RANGES_VIEWS - sg::detail::LazyExplorer, -#endif - sg::detail::BufferedExplorer>; - -TEMPLATE_LIST_TEST_CASE( - "Explorer implementations behave as expected.", - "[graph][graph::detail]", - TestExplorers -) -{ - using namespace Catch::Matchers; - - constexpr DefaultNode current{.vertex = 42}; - const BasicViewMock view{}; - REQUIRE_CALL(view, edges(current)) - .RETURN(std::vector{{41}, {43}, {44}, {45}}); - - TrackerMock tracker{}; - REQUIRE_CALL(tracker, set_discovered(41)) - .RETURN(false); - REQUIRE_CALL(tracker, set_discovered(43)) - .RETURN(false); - REQUIRE_CALL(tracker, set_discovered(44)) - .RETURN(true); - REQUIRE_CALL(tracker, set_discovered(45)) - .RETURN(false); - - TestType explorer{}; - REQUIRE_THAT(std::invoke(explorer, current, view, tracker), RangeEquals(std::array{DefaultEdge{44}})); -} - namespace { template struct NodeFactoryMock { + inline static constexpr bool trompeloeil_movable_mock = true; + MAKE_CONST_MOCK1(MakeOrigin, Node(const sg::node::vertex_t& vertex)); MAKE_CONST_MOCK2(MakeSuccessor, Node(const Node& current, const Edge& edge)); @@ -106,49 +75,74 @@ namespace }; } -using TestKernels = std::tuple< +using TestExplorers = std::tuple< #ifdef SL_UTILITY_HAS_RANGES_VIEWS - sg::detail::LazyKernel>, + sg::detail::LazyExplorer>, #endif - sg::detail::BufferedKernel>>; + sg::detail::BufferedExplorer>>; TEMPLATE_LIST_TEST_CASE( - "Kernel implementations behave as expected.", + "Explorer implementations behave as expected.", "[graph][graph::detail]", - TestKernels + TestExplorers ) { using namespace Catch::Matchers; - TestType kernel{}; + TrackerMock tracker{}; SECTION("When creating origin.") { - REQUIRE_CALL(kernel.node_factory(), MakeOrigin(42)) + REQUIRE_CALL(tracker, set_discovered(42)) + .RETURN(true); + + NodeFactoryMock nodeFactory{}; + REQUIRE_CALL(nodeFactory, MakeOrigin(42)) .RETURN(DefaultNode{.vertex = 42}); - REQUIRE(DefaultNode{.vertex = 42} == std::invoke(kernel, 42)); + TestType explorer{std::move(nodeFactory)}; + REQUIRE(DefaultNode{.vertex = 42} == std::invoke(explorer, 42, tracker)); } SECTION("When creating successor(s).") { constexpr DefaultNode current{.vertex = 42}; - const auto& [expected, edges] = GENERATE( - (table, std::vector>)({ - {{}, {}}, - {{{.vertex = 43}}, {{.destination = 43}}}, - {{{.vertex = 43}, {.vertex = 41}}, {{.destination = 43}, {.destination = 41}}} + const auto& [expected, visited, edges] = GENERATE( + (table, std::vector, std::vector>)({ + {{}, {}, {}}, + {{{.vertex = 43}}, {false}, {{.destination = 43}}}, + {{}, {true}, {{.destination = 43}}}, + {{{.vertex = 43}, {.vertex = 41}}, {false, false}, {{.destination = 43}, {.destination = 41}}}, + {{{.vertex = 41}}, {true, false}, {{.destination = 43}, {.destination = 41}}}, + {{{.vertex = 43}}, {false, true}, {{.destination = 43}, {.destination = 41}}} })); + CHECK(std::ssize(edges) == std::ssize(visited)); + + const BasicViewMock view{}; + REQUIRE_CALL(view, edges(current)) + .RETURN(edges); + NodeFactoryMock nodeFactory{}; std::vector> expectations{}; - for (const auto& edge : edges) + for (std::size_t i{0}; i < std::ranges::size(visited); ++i) { + const auto& edge = edges[i]; + const bool isVisited = visited[i]; + expectations.emplace_back( - NAMED_REQUIRE_CALL(kernel.node_factory(), MakeSuccessor(current, edge)) - .RETURN(DefaultNode{.vertex = sg::edge::destination(edge)})); + NAMED_REQUIRE_CALL(tracker, set_discovered(sg::edge::destination(edge))) + .RETURN(!isVisited)); + + if (!isVisited) + { + expectations.emplace_back( + NAMED_REQUIRE_CALL(nodeFactory, MakeSuccessor(current, edge)) + .RETURN(DefaultNode{.vertex = sg::edge::destination(edge)})); + } } - REQUIRE_THAT(std::invoke(kernel, current, edges), RangeEquals(expected)); + TestType explorer{std::move(nodeFactory)}; + REQUIRE_THAT(std::invoke(explorer, view, current, tracker), RangeEquals(expected)); } } @@ -195,7 +189,6 @@ TEST_CASE("detail::BasicTraverser can be constructed with an origin.", "[graph][ std::forward_as_tuple(DefaultView{}), std::forward_as_tuple(std::move(queue)), std::forward_as_tuple(std::move(tracker)), - std::tuple{}, std::tuple{} }; } @@ -224,7 +217,6 @@ TEST_CASE("detail::BasicTraverser::next returns the current node, or std::nullop std::forward_as_tuple(DefaultView{}), std::forward_as_tuple(std::move(queue)), std::forward_as_tuple(std::move(trackerMock)), - std::tuple{}, std::tuple{} }; }(); diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp index 2ce598339..49a194227 100644 --- a/tests/graph/UniformCostSearch.cpp +++ b/tests/graph/UniformCostSearch.cpp @@ -52,7 +52,7 @@ TEMPLATE_TEST_CASE( using Node = sg::CommonRankedNode; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonRankedNode))); - sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); @@ -67,7 +67,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthRankedNode))); - sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); @@ -82,7 +82,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorRankedNode))); - sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); @@ -97,7 +97,7 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); - sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}, std::tuple{}}; + sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; STATIC_CHECK(std::ranges::input_range); REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); From fd5e3e9708dd9b488dedafc68a5e4c0144b1120d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 7 Oct 2023 17:51:04 +0200 Subject: [PATCH 216/256] fix: please clang --- tests/graph/Traverse.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 88f12b064..c8f835937 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -107,7 +107,7 @@ TEMPLATE_LIST_TEST_CASE( SECTION("When creating successor(s).") { constexpr DefaultNode current{.vertex = 42}; - const auto& [expected, visited, edges] = GENERATE( + const auto& [expected, visited, edgeCollection] = GENERATE( (table, std::vector, std::vector>)({ {{}, {}, {}}, {{{.vertex = 43}}, {false}, {{.destination = 43}}}, @@ -116,17 +116,19 @@ TEMPLATE_LIST_TEST_CASE( {{{.vertex = 41}}, {true, false}, {{.destination = 43}, {.destination = 41}}}, {{{.vertex = 43}}, {false, true}, {{.destination = 43}, {.destination = 41}}} })); - CHECK(std::ssize(edges) == std::ssize(visited)); + CHECK(std::ssize(edgeCollection) == std::ssize(visited)); const BasicViewMock view{}; + // clang, honestly. I hate you. This workaround is necessary for the whole range clang[11, 14] + clangCl + const std::vector& edgeCollectionRef = edgeCollection; REQUIRE_CALL(view, edges(current)) - .RETURN(edges); + .LR_RETURN(edgeCollectionRef); NodeFactoryMock nodeFactory{}; std::vector> expectations{}; for (std::size_t i{0}; i < std::ranges::size(visited); ++i) { - const auto& edge = edges[i]; + const auto& edge = edgeCollection[i]; const bool isVisited = visited[i]; expectations.emplace_back( From 1b54d1f6de884c6a5e1816466e9f399150b5f051 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 7 Oct 2023 21:13:35 +0200 Subject: [PATCH 217/256] feat: graph::concepts::explorer --- include/Simple-Utility/graph/Traverse.hpp | 41 ++++++++++++++++------- tests/graph/Traverse.cpp | 9 +++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index cb2b568c9..03a15e3b2 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -24,6 +24,33 @@ #include #include +namespace sl::graph::concepts +{ + template + concept explorer = basic_node + && view_for + && tracker_for> + && sl::concepts::unqualified + && std::destructible + && requires(const T& explorer, const Node& node, const View& view, Tracker& tracker) + { + { std::invoke(explorer, node::vertex(node), tracker) } -> std::convertible_to; + { std::invoke(explorer, view, node, tracker) } -> std::ranges::input_range; + requires std::convertible_to< + std::ranges::range_reference_t>, + Node>; + }; + + template + concept traverser = std::destructible + && requires(T& traverser) + { + typename T::node_type; + { !traverser.next() } -> std::convertible_to; + { *traverser.next() } -> std::convertible_to; + }; +} + namespace sl::graph::detail { template @@ -164,7 +191,7 @@ namespace sl::graph::detail concepts::view_for View, concepts::queue_for QueueStrategy, concepts::tracker_for> TrackingStrategy, - typename ExplorationStrategy = default_explorer_t>> + concepts::explorer ExplorationStrategy = default_explorer_t>> requires concepts::edge_for, Node> class BasicTraverser { @@ -266,18 +293,6 @@ namespace sl::graph::detail }; } -namespace sl::graph::concepts -{ - template - concept traverser = std::destructible - && requires(T& traverser) - { - typename T::node_type; - { !traverser.next() } -> std::convertible_to; - { *traverser.next() } -> std::convertible_to; - }; -} - namespace sl::graph { template diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index c8f835937..82914f742 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -81,6 +81,15 @@ using TestExplorers = std::tuple< #endif sg::detail::BufferedExplorer>>; +TEMPLATE_LIST_TEST_CASE( + "Explorer implementations satisfy concepts::explorer.", + "[graph][graph::detail][graph::concept]", + TestExplorers +) +{ + STATIC_REQUIRE(sg::concepts::explorer, TrackerMock>); +} + TEMPLATE_LIST_TEST_CASE( "Explorer implementations behave as expected.", "[graph][graph::detail]", From 93a18bb2a563b5104bb670e8e43a2ce78c3ecbe8 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 7 Oct 2023 22:21:48 +0200 Subject: [PATCH 218/256] refactor: make traversing an actual strategy of BasicTraverser --- include/Simple-Utility/graph/Traverse.hpp | 85 ++++++++++++++++------- tests/graph/Traverse.cpp | 14 ++++ 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 03a15e3b2..f10c71033 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -41,6 +41,23 @@ namespace sl::graph::concepts Node>; }; + template + concept traverser_kernel = sl::concepts::unqualified + && std::destructible + && requires( + T& kernel, + const View& view, + const Explorer& explorer, + Queue& queue, + Tracker& tracker + ) + { + { !std::invoke(kernel, view, explorer, queue, tracker) } -> std::convertible_to; + //{ + // *std::invoke(kernel, explorer, queue, tracker) + //} -> std::convertible_to>; + }; + template concept traverser = std::destructible && requires(T& traverser) @@ -186,12 +203,49 @@ namespace sl::graph::detail using default_explorer_t = BufferedExplorer; #endif + struct PreOrderKernel + { + template + [[nodiscard]] + constexpr auto operator()(const View& view, const Explorer& explorer, Queue& queue, Tracker& tracker) const + { + using node_type = std::remove_cvref_t; + + const auto queueNext = [&]() -> std::optional + { + if (!queue::empty(queue)) + { + return {queue::next(queue)}; + } + + return std::nullopt; + }; + + std::optional result = queueNext(); + for (; + result && !tracker::set_visited(tracker, node::vertex(*result)); + result = queueNext()) + { + } + + if (result) + { + queue::insert( + queue, + std::invoke(explorer, view, *result, tracker)); + } + + return result; + } + }; + template < concepts::basic_node Node, concepts::view_for View, concepts::queue_for QueueStrategy, concepts::tracker_for> TrackingStrategy, - concepts::explorer ExplorationStrategy = default_explorer_t>> + concepts::explorer ExplorationStrategy = default_explorer_t>, + concepts::traverser_kernel KernelStrategy = PreOrderKernel> requires concepts::edge_for, Node> class BasicTraverser { @@ -240,31 +294,7 @@ namespace sl::graph::detail [[nodiscard]] constexpr std::optional next() { - const auto queueNext = [&]() -> std::optional - { - if (!queue::empty(m_Queue)) - { - return {queue::next(m_Queue)}; - } - - return std::nullopt; - }; - - std::optional result = queueNext(); - for (; - result && !tracker::set_visited(m_Tracker, node::vertex(*result)); - result = queueNext()) - { - } - - if (result) - { - queue::insert( - m_Queue, - std::invoke(m_Explorer, m_View, *result, m_Tracker)); - } - - return result; + return std::invoke(m_Kernel, m_View, m_Explorer, m_Queue, m_Tracker); } [[nodiscard]] @@ -286,7 +316,8 @@ namespace sl::graph::detail } private: - SL_UTILITY_NO_UNIQUE_ADDRESS ExplorationStrategy m_Explorer{}; + ExplorationStrategy m_Explorer{}; + KernelStrategy m_Kernel{}; queue_type m_Queue; tracker_type m_Tracker; view_type m_View; diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index 82914f742..e83aef15f 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -157,6 +157,20 @@ TEMPLATE_LIST_TEST_CASE( } } +using TestKernels = std::tuple< + sg::detail::PreOrderKernel>; + +TEMPLATE_LIST_TEST_CASE( + "Kernel implementations satisfy concepts::traverser_kernel", + "[graph][graph::detail][graph::concept]", + TestKernels +) +{ + using Explorer = sg::detail::BufferedExplorer>; + + STATIC_REQUIRE(sg::concepts::traverser_kernel, Explorer, QueueMock, TrackerMock>); +} + TEST_CASE( "detail::BasicTraverser is not copyable but movable, when strategies support it.", "[graph][graph::traverser][graph::detail]" From c379dd66a1e5a6a3d20e348a5affb587a95c2e46 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 7 Oct 2023 23:50:47 +0200 Subject: [PATCH 219/256] refactor: merge test case and benchmarks --- benchmarks/graph/AStarBoostComparison.cpp | 58 ++++++++++------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 421ae0f0a..1ed07aeb6 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -407,40 +407,6 @@ std::ostream& operator<<(std::ostream& output, const maze& m) return output; } -TEMPLATE_TEST_CASE_SIG( - "sl::graph and boost::graph generate equally good solutions", - "[vs_boost][comparison]", - ((int width, int height), width, height), - (8, 8), - (16, 16), - (32, 32), - (64, 64), - (128, 128), - (256, 256), - (512, 512), - (1024, 1024) -) -{ - const auto seed = Catch::getSeed(); - maze m{width, height}; - random_maze(m, seed); - - const std::optional boostSolution = [m, fileName = std::format("./{}_{}x{}.maze.txt", seed, width, height)]() mutable - { - auto result = m.solve(); - std::ofstream out{std::filesystem::current_path() / fileName}; - out << m; - return result; - }(); - const std::optional slSolution = [m] { return sl_graph_solve(m); }(); - - REQUIRE(boostSolution.has_value() == slSolution.has_value()); - if (boostSolution.has_value()) - { - REQUIRE(std::get<1>(*boostSolution) == Catch::Approx{std::get<1>(*slSolution)}); - } -} - TEMPLATE_TEST_CASE_SIG( "Benchmarking sl::graph vs boost::graph AStar.", "[vs_boost][benchmark][benchmark::graph]", @@ -455,9 +421,33 @@ TEMPLATE_TEST_CASE_SIG( (1024, 1024) ) { + const std::uint32_t seed = GENERATE( + // add static seeds here + Catch::getSeed()); + maze m{width, height}; random_maze(m, Catch::getSeed()); + SECTION("Compare the results of both implementations.") + { + const std::optional boostSolution = [m, fileName = std::format("./{}_{}x{}.maze.txt", seed, width, height)]() mutable + { + auto result = m.solve(); + const auto path = std::filesystem::current_path() / "graph" / "astar_vs_boost"; + create_directories(path); + std::ofstream out{path / fileName}; + out << m; + return result; + }(); + const std::optional slSolution = [m] { return sl_graph_solve(m); }(); + + REQUIRE(boostSolution.has_value() == slSolution.has_value()); + if (boostSolution.has_value()) + { + REQUIRE(std::get<1>(*boostSolution) == Catch::Approx{std::get<1>(*slSolution)}); + } + } + BENCHMARK("boost::graph") { return m.solve(); From 5b7911a4f9ef4ab563e6c86ead4d4692522e46a0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 7 Oct 2023 23:57:20 +0200 Subject: [PATCH 220/256] feat: add benchmarks/Defines.hpp --- benchmarks/Defines.hpp | 19 +++++++++++++++++++ benchmarks/graph/AStarBoostComparison.cpp | 5 +++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 benchmarks/Defines.hpp diff --git a/benchmarks/Defines.hpp b/benchmarks/Defines.hpp new file mode 100644 index 000000000..166d3781d --- /dev/null +++ b/benchmarks/Defines.hpp @@ -0,0 +1,19 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_BENCHMARKS_DEFINES_HPP +#define SIMPLE_UTILITY_BENCHMARKS_DEFINES_HPP + +#pragma once + +#include + +[[nodiscard]] +inline std::filesystem::path artifacts_root_path() +{ + return std::filesystem::current_path() / "artifacts"; +} + +#endif diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 1ed07aeb6..628b955fa 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -15,11 +15,12 @@ #include #include +#include #include #include #include -#include +#include "../Defines.hpp" #include "Simple-Utility/graph/AStarSearch.hpp" @@ -433,7 +434,7 @@ TEMPLATE_TEST_CASE_SIG( const std::optional boostSolution = [m, fileName = std::format("./{}_{}x{}.maze.txt", seed, width, height)]() mutable { auto result = m.solve(); - const auto path = std::filesystem::current_path() / "graph" / "astar_vs_boost"; + const auto path = artifacts_root_path() / "graph" / "astar_vs_boost"; create_directories(path); std::ofstream out{path / fileName}; out << m; From ba78acd1e85995ac691e113c334b81682d661a3a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 00:07:49 +0200 Subject: [PATCH 221/256] ci: add run_benchmarks workflow --- .github/workflows/run_benchmarks.yml | 177 +++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 .github/workflows/run_benchmarks.yml diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml new file mode 100644 index 000000000..c5c2cd439 --- /dev/null +++ b/.github/workflows/run_benchmarks.yml @@ -0,0 +1,177 @@ +name: run tests + +env: + BUILD_DIR: out # root build directory + BENCHMARK_DIR: benchmarks # relative directory below BUILD_DIR + ARTIFACTS_DIR: benchmarks/artifacts # relative directory below BUILD_DIR + +on: + push: + branches: + - master + - development + paths-ignore: + - 'README.md' + - 'docs/' + pull_request: + branches: + - '**' + paths-ignore: + - 'README.md' + - 'docs/' + +jobs: + ubuntu-22_04: + runs-on: ubuntu-22.04 + + strategy: + fail-fast: false + matrix: + build_mode: [Release] + compiler: + - pkg: g++-12 + exe: g++-12 + - pkg: g++-11 + exe: g++-11 + - pkg: g++-10 + exe: g++-10 + - pkg: clang-14 + exe: clang++-14 + - pkg: clang-13 + exe: clang++-13 + - pkg: clang-12 + exe: clang++-12 + - pkg: clang-11 + exe: clang++-11 + + steps: + - uses: actions/checkout@v3 + - name: Install compiler + run: | + sudo apt-get update + sudo apt-get install ${{ matrix.compiler.pkg }} -y + - name: Compile tests + env: + CXX: ${{ matrix.compiler.exe }} + run: | + cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B ${{ env.BUILD_DIR }} -S . + cmake --build ${{ env.BUILD_DIR }} -j4 + - name: Run tests + env: + CTEST_OUTPUT_ON_FAILURE: 1 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 + - name: Upload generated report artifacts + uses: actions/upload-artifact@v3 + with: + path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} + + + ubuntu-20_04: + runs-on: ubuntu-20.04 + + strategy: + fail-fast: false + matrix: + build_mode: [Release] + compiler: + - pkg: g++-11 + exe: g++-11 + - pkg: g++-10 + exe: g++-10 + - pkg: clang-12 + exe: clang++-12 + - pkg: clang-11 + exe: clang++-11 + - pkg: clang-10 + exe: clang++-10 + + steps: + - uses: actions/checkout@v3 + - name: Install compiler + run: | + sudo apt-get update + sudo apt-get install ${{ matrix.compiler.pkg }} -y + - name: Compile tests + env: + CXX: ${{ matrix.compiler.exe }} + run: | + cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -B ${{ env.BUILD_DIR }} -S . + cmake --build ${{ env.BUILD_DIR }} -j4 + - name: Run tests + env: + CTEST_OUTPUT_ON_FAILURE: 1 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C ${{ matrix.build_mode }} -j4 + - name: Upload generated report artifacts + uses: actions/upload-artifact@v3 + with: + path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} + + + windows_2022: + runs-on: windows-2022 + + strategy: + fail-fast: false + matrix: + build_mode: [Release] + toolset: [v142, v143, ClangCl] + + steps: + - uses: actions/checkout@v3 + - name: Compile tests + run: | + cmake -G"Visual Studio 17 2022" -T${{ matrix.toolset }} -B${{ env.BUILD_DIR }} -Ax64 -S . + cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 + - name: Run tests + env: + CTEST_OUTPUT_ON_FAILURE: 1 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C${{ matrix.build_mode }} -j4 + - name: Upload generated report artifacts + uses: actions/upload-artifact@v3 + with: + path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} + + + windows_2019: + runs-on: windows-2019 + + strategy: + fail-fast: false + matrix: + build_mode: [Release] + toolset: [v142, ClangCl] + + steps: + - uses: actions/checkout@v3 + - name: Compile tests + run: | + cmake -G"Visual Studio 16 2019" -T${{ matrix.toolset }} -B${{ env.BUILD_DIR }} -Ax64 -S . + cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 + - name: Run tests + env: + CTEST_OUTPUT_ON_FAILURE: 1 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C${{ matrix.build_mode }} -j4 + - name: Upload generated report artifacts + uses: actions/upload-artifact@v3 + with: + path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} + + +# macos: +# runs-on: macos-latest +# +# strategy: +# fail-fast: false +# matrix: +# build_mode: [Debug, Release] +# +# steps: +# - uses: actions/checkout@v3 +# - name: Compile tests +# run: | +# cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -B ${{ env.BUILD_DIR }} -S . +# cmake --build ${{ env.BUILD_DIR }} -j4 +# - name: Run tests +# env: +# CTEST_OUTPUT_ON_FAILURE: 1 +# run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C${{ matrix.build_mode }} -j4 From 9b9e8790f160f70d77ffed926f5abe715e281aa4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 00:09:02 +0200 Subject: [PATCH 222/256] fix: benchmark workflow name --- .github/workflows/run_benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index c5c2cd439..03258c772 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -1,4 +1,4 @@ -name: run tests +name: run benchmarks env: BUILD_DIR: out # root build directory From e039dd85c249dc79bee14a0c9fc2077ee552dce9 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 00:12:56 +0200 Subject: [PATCH 223/256] fix: benchmark workflow names --- .github/workflows/run_benchmarks.yml | 36 +++++++--------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 03258c772..69d7e4a75 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -50,13 +50,13 @@ jobs: run: | sudo apt-get update sudo apt-get install ${{ matrix.compiler.pkg }} -y - - name: Compile tests + - name: Compile env: CXX: ${{ matrix.compiler.exe }} run: | cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B ${{ env.BUILD_DIR }} -S . cmake --build ${{ env.BUILD_DIR }} -j4 - - name: Run tests + - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 @@ -91,13 +91,13 @@ jobs: run: | sudo apt-get update sudo apt-get install ${{ matrix.compiler.pkg }} -y - - name: Compile tests + - name: Compile env: CXX: ${{ matrix.compiler.exe }} run: | cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -B ${{ env.BUILD_DIR }} -S . cmake --build ${{ env.BUILD_DIR }} -j4 - - name: Run tests + - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C ${{ matrix.build_mode }} -j4 @@ -118,11 +118,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Compile tests + - name: Compile run: | cmake -G"Visual Studio 17 2022" -T${{ matrix.toolset }} -B${{ env.BUILD_DIR }} -Ax64 -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - - name: Run tests + - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C${{ matrix.build_mode }} -j4 @@ -143,11 +143,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Compile tests + - name: Compile run: | cmake -G"Visual Studio 16 2019" -T${{ matrix.toolset }} -B${{ env.BUILD_DIR }} -Ax64 -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - - name: Run tests + - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C${{ matrix.build_mode }} -j4 @@ -155,23 +155,3 @@ jobs: uses: actions/upload-artifact@v3 with: path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} - - -# macos: -# runs-on: macos-latest -# -# strategy: -# fail-fast: false -# matrix: -# build_mode: [Debug, Release] -# -# steps: -# - uses: actions/checkout@v3 -# - name: Compile tests -# run: | -# cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -B ${{ env.BUILD_DIR }} -S . -# cmake --build ${{ env.BUILD_DIR }} -j4 -# - name: Run tests -# env: -# CTEST_OUTPUT_ON_FAILURE: 1 -# run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C${{ matrix.build_mode }} -j4 From 1e60a7ec6b362cc15d34b381149b7b89d020eb78 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 01:23:12 +0200 Subject: [PATCH 224/256] refactor: make benchmarks less c++20 dependent --- benchmarks/CMakeLists.txt | 4 ++++ benchmarks/graph/AStarBoostComparison.cpp | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index e55088263..2ee0fa991 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,6 +1,8 @@ CPMAddPackage("gh:catchorg/Catch2@3.4.0") include("${Catch2_SOURCE_DIR}/extras/Catch.cmake") +CPMAddPackage("gh:fmtlib/fmt#10.1.1") +CPMAddPackage("gh:ericniebler/range-v3#0.12.0") CPMAddPackage("gh:kokkos/mdspan#mdspan-0.6.0") CPMAddPackage( name boost @@ -20,6 +22,8 @@ target_link_libraries( Catch2::Catch2WithMain std::mdspan Boost::graph + fmt::fmt + range-v3::range-v3 ) catch_discover_tests(Simple-Utility-Benchmarks) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 628b955fa..5bfd792cf 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -15,6 +15,9 @@ #include #include +#include +#include + #include #include #include @@ -76,7 +79,7 @@ class maze friend void random_maze(maze&, std::uint32_t); explicit maze(const std::size_t x, const std::size_t y) - : m_Grid{boost::array{{x, y}}}, + : m_Grid{grid::vertex_descriptor{x, y}}, m_BarrierGrid(make_vertex_subset_complement_filter(m_Grid, m_Barriers)) { } @@ -277,8 +280,9 @@ struct sl::graph::customize::edges_fn> { const auto& g = m.get_grid(); const auto [edgesBegin, edgesEnd] = out_edges(node::vertex(current), g); - return std::ranges::subrange{edgesBegin, edgesEnd} - | std::views::transform([&](const auto& e) { return edge_type{target(e, g), 1.}; }); + + return ranges::subrange{edgesBegin, edgesEnd} + | ranges::views::transform([&](const auto& e) { return edge_type{target(e, g), 1.}; }); } }; @@ -431,7 +435,7 @@ TEMPLATE_TEST_CASE_SIG( SECTION("Compare the results of both implementations.") { - const std::optional boostSolution = [m, fileName = std::format("./{}_{}x{}.maze.txt", seed, width, height)]() mutable + const std::optional boostSolution = [m, fileName = fmt::format("./{}_{}x{}.maze.txt", seed, width, height)]() mutable { auto result = m.solve(); const auto path = artifacts_root_path() / "graph" / "astar_vs_boost"; From f2f5c9d8481ba8e2d5b25e546a29a489ce6466ac Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 01:24:57 +0200 Subject: [PATCH 225/256] fix: benchmark workflow --- .github/workflows/run_benchmarks.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 69d7e4a75..6bdff3ced 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -3,7 +3,7 @@ name: run benchmarks env: BUILD_DIR: out # root build directory BENCHMARK_DIR: benchmarks # relative directory below BUILD_DIR - ARTIFACTS_DIR: benchmarks/artifacts # relative directory below BUILD_DIR + ARTIFACTS_DIR: artifacts # relative directory below BUILD_DIR on: push: @@ -95,12 +95,12 @@ jobs: env: CXX: ${{ matrix.compiler.exe }} run: | - cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -B ${{ env.BUILD_DIR }} -S . + cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B ${{ env.BUILD_DIR }} -S . cmake --build ${{ env.BUILD_DIR }} -j4 - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C ${{ matrix.build_mode }} -j4 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -120,12 +120,12 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 17 2022" -T${{ matrix.toolset }} -B${{ env.BUILD_DIR }} -Ax64 -S . + cmake -G"Visual Studio 17 2022" -T${{ matrix.toolset }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B${{ env.BUILD_DIR }} -Ax64 -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C${{ matrix.build_mode }} -j4 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C${{ matrix.build_mode }} -j4 - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -145,12 +145,12 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 16 2019" -T${{ matrix.toolset }} -B${{ env.BUILD_DIR }} -Ax64 -S . + cmake -G"Visual Studio 16 2019" -T${{ matrix.toolset }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B${{ env.BUILD_DIR }} -Ax64 -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.TEST_DIR }} -C${{ matrix.build_mode }} -j4 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C${{ matrix.build_mode }} -j4 - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: From b5a2c38f3159677f141c6818522d3e3547963770 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 01:37:14 +0200 Subject: [PATCH 226/256] fix: use correct artifacts path in benchmark workflow --- .github/workflows/run_benchmarks.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 6bdff3ced..d937d46d7 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -63,7 +63,7 @@ jobs: - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} + path: ${{env.ARTIFACTS_DIR}} ubuntu-20_04: @@ -104,7 +104,7 @@ jobs: - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} + path: ${{env.ARTIFACTS_DIR}} windows_2022: @@ -129,7 +129,7 @@ jobs: - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} + path: ${{env.ARTIFACTS_DIR}} windows_2019: @@ -154,4 +154,4 @@ jobs: - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: ${{env.BUILD_DIR}}/${{env.ARTIFACTS_DIR}} + path: ${{env.ARTIFACTS_DIR}} From aecc39894e5b3f4d74d3299987180aa10a8dcff3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 14:00:48 +0200 Subject: [PATCH 227/256] ci: adjust workflow --- .github/workflows/run_benchmarks.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index d937d46d7..69cc98a8a 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -3,7 +3,7 @@ name: run benchmarks env: BUILD_DIR: out # root build directory BENCHMARK_DIR: benchmarks # relative directory below BUILD_DIR - ARTIFACTS_DIR: artifacts # relative directory below BUILD_DIR + ARTIFACTS_DIR: artifacts # relative directory below BENCHMARK_DIR on: push: @@ -59,11 +59,11 @@ jobs: - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 --verbose - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: ${{env.ARTIFACTS_DIR}} + path: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/${{env.ARTIFACTS_DIR}}" ubuntu-20_04: @@ -100,11 +100,11 @@ jobs: - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 --verbose - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: ${{env.ARTIFACTS_DIR}} + path: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/${{env.ARTIFACTS_DIR}}" windows_2022: @@ -125,11 +125,11 @@ jobs: - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C${{ matrix.build_mode }} -j4 + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C${{ matrix.build_mode }} -j4 --verbose - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: ${{env.ARTIFACTS_DIR}} + path: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/${{env.ARTIFACTS_DIR}}" windows_2019: @@ -150,8 +150,8 @@ jobs: - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C${{ matrix.build_mode }} -j4 + run: ctest --test-dir ${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}} -C${{ matrix.build_mode }} -j4 --verbose - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: ${{env.ARTIFACTS_DIR}} + path: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/${{env.ARTIFACTS_DIR}}" From 96f22804d9d44e4fee1abba2253c26c9af8ae6f6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 15:15:57 +0200 Subject: [PATCH 228/256] ci: run benchmarks sequential --- .github/workflows/run_benchmarks.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 69cc98a8a..84fa0d9af 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -59,7 +59,7 @@ jobs: - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 --verbose + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j1 --verbose - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -100,7 +100,7 @@ jobs: - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j4 --verbose + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j1 --verbose - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -125,7 +125,7 @@ jobs: - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C${{ matrix.build_mode }} -j4 --verbose + run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C${{ matrix.build_mode }} -j1 --verbose - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -150,7 +150,7 @@ jobs: - name: Run benchmarks env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}} -C${{ matrix.build_mode }} -j4 --verbose + run: ctest --test-dir ${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}} -C${{ matrix.build_mode }} -j1 --verbose - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: From 8864db89a424046c6c02252979046bcf2966721f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 15:18:32 +0200 Subject: [PATCH 229/256] ci: directly run benchmark executable --- .github/workflows/run_benchmarks.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 84fa0d9af..c3b0bab7f 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -57,9 +57,7 @@ jobs: cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B ${{ env.BUILD_DIR }} -S . cmake --build ${{ env.BUILD_DIR }} -j4 - name: Run benchmarks - env: - CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j1 --verbose + run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks" - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -98,9 +96,7 @@ jobs: cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_mode }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B ${{ env.BUILD_DIR }} -S . cmake --build ${{ env.BUILD_DIR }} -j4 - name: Run benchmarks - env: - CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C ${{ matrix.build_mode }} -j1 --verbose + run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks" - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -123,9 +119,7 @@ jobs: cmake -G"Visual Studio 17 2022" -T${{ matrix.toolset }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B${{ env.BUILD_DIR }} -Ax64 -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks - env: - CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{ env.BUILD_DIR }}/${{ env.BENCHMARK_DIR }} -C${{ matrix.build_mode }} -j1 --verbose + run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks" - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -148,9 +142,7 @@ jobs: cmake -G"Visual Studio 16 2019" -T${{ matrix.toolset }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B${{ env.BUILD_DIR }} -Ax64 -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks - env: - CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir ${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}} -C${{ matrix.build_mode }} -j1 --verbose + run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks" - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: From 8e7b5af1c372f231dae859a942432049ed12274f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 16:13:57 +0200 Subject: [PATCH 230/256] fix: windows benchmark workflow --- .github/workflows/run_benchmarks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index c3b0bab7f..07a205369 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -119,7 +119,7 @@ jobs: cmake -G"Visual Studio 17 2022" -T${{ matrix.toolset }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B${{ env.BUILD_DIR }} -Ax64 -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks - run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks" + run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks.exe" - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: @@ -142,7 +142,7 @@ jobs: cmake -G"Visual Studio 16 2019" -T${{ matrix.toolset }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B${{ env.BUILD_DIR }} -Ax64 -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks - run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks" + run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks.exe" - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: From 9401c3bb4f436a860f9ad35060de92c0840b8f9d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 17:12:57 +0200 Subject: [PATCH 231/256] fix: benchmark workflow --- .github/workflows/run_benchmarks.yml | 30 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 07a205369..3eb2aab92 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -61,7 +61,7 @@ jobs: - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/${{env.ARTIFACTS_DIR}}" + path: "${{env.ARTIFACTS_DIR}}" ubuntu-20_04: @@ -100,7 +100,7 @@ jobs: - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/${{env.ARTIFACTS_DIR}}" + path: "${{env.ARTIFACTS_DIR}}" windows_2022: @@ -116,14 +116,20 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 17 2022" -T${{ matrix.toolset }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B${{ env.BUILD_DIR }} -Ax64 -S . + cmake -G"Visual Studio 17 2022" \ + -T${{matrix.toolset}} \ + -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} \ + -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes \ + -B${{env.BUILD_DIR}} \ + -Ax64 \ + -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks - run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks.exe" + run: ".\\${{env.BUILD_DIR}}\\${{env.BENCHMARK_DIR}}\\${{matrix.build_mode}}\\Simple-Utility-Benchmarks.exe" - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/${{env.ARTIFACTS_DIR}}" + path: "${{env.ARTIFACTS_DIR}}" windows_2019: @@ -139,11 +145,17 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 16 2019" -T${{ matrix.toolset }} -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes -B${{ env.BUILD_DIR }} -Ax64 -S . - cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 + cmake -G"Visual Studio 16 2019" \ + -T${{matrix.toolset}} \ + -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} \ + -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes \ + -B${{env.BUILD_DIR}} \ + -Ax64 \ + -S . + cmake --build ${{env.BUILD_DIR}} --config ${{matrix.build_mode}} -j4 - name: Run benchmarks - run: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/Simple-Utility-Benchmarks.exe" + run: ".\\${{env.BUILD_DIR}}\\${{env.BENCHMARK_DIR}}\\${{matrix.build_mode}}\\Simple-Utility-Benchmarks.exe" - name: Upload generated report artifacts uses: actions/upload-artifact@v3 with: - path: "${{env.BUILD_DIR}}/${{env.BENCHMARK_DIR}}/${{env.ARTIFACTS_DIR}}" + path: "${{env.ARTIFACTS_DIR}}" From e32046c747f3d6d1615da886c94029350b468e10 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 17:23:23 +0200 Subject: [PATCH 232/256] fix: windows multiline command in benchmark workflow --- .github/workflows/run_benchmarks.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 3eb2aab92..5da63cc43 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -116,12 +116,12 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 17 2022" \ - -T${{matrix.toolset}} \ - -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} \ - -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes \ - -B${{env.BUILD_DIR}} \ - -Ax64 \ + cmake -G"Visual Studio 17 2022" ^ + -T${{matrix.toolset}} ^ + -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} ^ + -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes ^ + -B${{env.BUILD_DIR}} ^ + -Ax64 ^ -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks @@ -145,12 +145,12 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 16 2019" \ - -T${{matrix.toolset}} \ - -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} \ - -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes \ - -B${{env.BUILD_DIR}} \ - -Ax64 \ + cmake -G"Visual Studio 16 2019" ^ + -T${{matrix.toolset}} ^ + -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} ^ + -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes ^ + -B${{env.BUILD_DIR}} ^ + -Ax64 ^ -S . cmake --build ${{env.BUILD_DIR}} --config ${{matrix.build_mode}} -j4 - name: Run benchmarks From 147967184e168d88d349e4f9efb1130cf51d6be3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 17:31:30 +0200 Subject: [PATCH 233/256] fix: another approach and try ` instead.. --- .github/workflows/run_benchmarks.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 5da63cc43..192434fda 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -116,12 +116,12 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 17 2022" ^ - -T${{matrix.toolset}} ^ - -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} ^ - -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes ^ - -B${{env.BUILD_DIR}} ^ - -Ax64 ^ + cmake -G"Visual Studio 17 2022" ` + -T${{matrix.toolset}} ` + -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} ` + -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes ` + -B${{env.BUILD_DIR}} ` + -Ax64 ` -S . cmake --build ${{ env.BUILD_DIR }} --config ${{ matrix.build_mode }} -j4 - name: Run benchmarks @@ -145,12 +145,12 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 16 2019" ^ - -T${{matrix.toolset}} ^ - -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} ^ - -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes ^ - -B${{env.BUILD_DIR}} ^ - -Ax64 ^ + cmake -G"Visual Studio 17 2022" ` + -T${{matrix.toolset}} ` + -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} ` + -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes ` + -B${{env.BUILD_DIR}} ` + -Ax64 ` -S . cmake --build ${{env.BUILD_DIR}} --config ${{matrix.build_mode}} -j4 - name: Run benchmarks From 31dcbb8e22cd0c7476321672198ac198643754c5 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 8 Oct 2023 17:36:01 +0200 Subject: [PATCH 234/256] fix: selected generator --- .github/workflows/run_benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 192434fda..ce4896b66 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -145,7 +145,7 @@ jobs: - uses: actions/checkout@v3 - name: Compile run: | - cmake -G"Visual Studio 17 2022" ` + cmake -G"Visual Studio 16 2019" ` -T${{matrix.toolset}} ` -DCMAKE_BUILD_TYPE=${{matrix.build_mode}} ` -DSIMPLE_UTILITY_BUILD_BENCHMARKS=Yes ` From 2c8b2e440f19a794b23405f2279089b5e952c176 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 9 Oct 2023 19:49:27 +0200 Subject: [PATCH 235/256] refactor: graph::view api and concepts --- benchmarks/graph/AStarBoostComparison.cpp | 8 +- include/Simple-Utility/graph/AStarSearch.hpp | 5 +- .../graph/BreadthFirstSearch.hpp | 5 +- .../Simple-Utility/graph/DepthFirstSearch.hpp | 5 +- include/Simple-Utility/graph/Traverse.hpp | 15 +- .../graph/UniformCostSearch.hpp | 3 +- include/Simple-Utility/graph/View.hpp | 95 +++++++------ tests/graph/Defines.hpp | 37 ++--- tests/graph/Traverse.cpp | 4 +- tests/graph/View.cpp | 134 ++++++++++-------- 10 files changed, 163 insertions(+), 148 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 5bfd792cf..8ed92b052 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -267,19 +267,21 @@ template <> struct sl::graph::view::traits> { using edge_type = CommonWeightedEdge; + using vertex_type = edge::vertex_t; + using weight_type = edge::weight_t; }; template <> -struct sl::graph::customize::edges_fn> +struct sl::graph::customize::out_edges_fn> { using edge_type = view::edge_t>; using vertex_type = edge::vertex_t; using weight_type = edge::weight_t; - constexpr auto operator ()(const maze& m, const auto& current) const + auto operator ()(const maze& m, const vertex_type& current) const { const auto& g = m.get_grid(); - const auto [edgesBegin, edgesEnd] = out_edges(node::vertex(current), g); + const auto [edgesBegin, edgesEnd] = out_edges(current, g); return ranges::subrange{edgesBegin, edgesEnd} | ranges::views::transform([&](const auto& e) { return edge_type{target(e, g), 1.}; }); diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index b864dcdb1..85a0a04ea 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -165,12 +165,11 @@ namespace sl::graph::astar }; template < - typename View, + concepts::basic_graph View, typename Heuristic, concepts::ranked_node Node = CommonNode>, edge::weight_t>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> - requires concepts::view_for - && concepts::heuristic_for + requires concepts::heuristic_for using Range = IterableTraverser< graph::detail::BasicTraverser< Node, diff --git a/include/Simple-Utility/graph/BreadthFirstSearch.hpp b/include/Simple-Utility/graph/BreadthFirstSearch.hpp index 30ee10a3b..06e5aa5b1 100644 --- a/include/Simple-Utility/graph/BreadthFirstSearch.hpp +++ b/include/Simple-Utility/graph/BreadthFirstSearch.hpp @@ -24,11 +24,10 @@ namespace sl::graph::dfs using CommonNode = CommonBasicNode; template < - class View, + concepts::basic_graph View, concepts::basic_node Node = CommonNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> - requires concepts::view_for - && (!concepts::ranked_node) + requires (!concepts::ranked_node) using Range = IterableTraverser< detail::BasicTraverser< Node, diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index f7908e52d..8a3220ebe 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -24,11 +24,10 @@ namespace sl::graph::dfs using CommonNode = CommonBasicNode; template < - class View, + concepts::basic_graph View, concepts::basic_node Node = CommonNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> - requires concepts::view_for - && (!concepts::ranked_node) + requires (!concepts::ranked_node) using Range = IterableTraverser< detail::BasicTraverser< Node, diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index f10c71033..d1ec34412 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -28,7 +28,7 @@ namespace sl::graph::concepts { template concept explorer = basic_node - && view_for + && basic_graph && tracker_for> && sl::concepts::unqualified && std::destructible @@ -42,7 +42,8 @@ namespace sl::graph::concepts }; template - concept traverser_kernel = sl::concepts::unqualified + concept traverser_kernel = basic_graph + && sl::concepts::unqualified && std::destructible && requires( T& kernel, @@ -53,9 +54,9 @@ namespace sl::graph::concepts ) { { !std::invoke(kernel, view, explorer, queue, tracker) } -> std::convertible_to; - //{ - // *std::invoke(kernel, explorer, queue, tracker) - //} -> std::convertible_to>; + { + *std::invoke(kernel, view, explorer, queue, tracker) + } -> std::convertible_to>; }; template @@ -144,7 +145,7 @@ namespace sl::graph::detail { return std::invoke( CollectorStrategy{}, - view::edges(graph, current), + view::out_edges(graph, node::vertex(current)), current, m_NodeFactory, tracker); @@ -241,7 +242,7 @@ namespace sl::graph::detail template < concepts::basic_node Node, - concepts::view_for View, + concepts::basic_graph View, concepts::queue_for QueueStrategy, concepts::tracker_for> TrackingStrategy, concepts::explorer ExplorationStrategy = default_explorer_t>, diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp index f80ef2902..25c4cfb0c 100644 --- a/include/Simple-Utility/graph/UniformCostSearch.hpp +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -24,10 +24,9 @@ namespace sl::graph::ucs using CommonNode = CommonRankedNode; template < - class View, + concepts::basic_graph View, concepts::ranked_node Node = CommonNode>, edge::weight_t>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> - requires concepts::view_for using Range = IterableTraverser< detail::BasicTraverser< Node, diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp index 457af7379..e82ff3152 100644 --- a/include/Simple-Utility/graph/View.hpp +++ b/include/Simple-Utility/graph/View.hpp @@ -25,10 +25,14 @@ namespace sl::graph::view using edge_t = typename traits::edge_type; template - requires requires { typename T::edge_type; } - && concepts::edge + using vertex_t = typename traits::vertex_type; + + template + requires concepts::readable_vertex_type + && requires { requires concepts::edge; } struct traits { + using vertex_type = typename T::vertex_type; using edge_type = typename T::edge_type; }; } @@ -36,71 +40,81 @@ namespace sl::graph::view namespace sl::graph::customize { template - struct edges_fn; + struct out_edges_fn; } namespace sl::graph::detail { - template - requires requires { customize::edges_fn{}; } - && std::ranges::input_range, const View&, const Node&>> + template + requires requires { customize::out_edges_fn{}; } + && std::ranges::input_range, const View&, const view::vertex_t&>> && std::convertible_to< - std::ranges::range_reference_t, const View&, const Node&>>, + std::ranges::range_reference_t, const View&, const view::vertex_t&>>, view::edge_t> - constexpr decltype(auto) edges( + constexpr decltype(auto) out_edges( const View& view, - const Node& node, + const view::vertex_t& vertex, const priority_tag<2> - ) noexcept(noexcept(customize::edges_fn{}(view, node))) + ) noexcept(noexcept(customize::out_edges_fn{}(view, vertex))) { - return customize::edges_fn{}(view, node); + return customize::out_edges_fn{}(view, vertex); } // pleases msvc v142 // ReSharper disable CppRedundantTemplateKeyword // ReSharper disable CppRedundantTypenameKeyword - template - requires requires(const View& view, const Node& node) + template + requires requires(const View& view, const view::vertex_t& vertex) { - { view.edges(node) } -> std::ranges::input_range; + { view.out_edges(vertex) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_reference_t, + std::ranges::range_reference_t, typename view::template edge_t>; } - constexpr decltype(auto) edges(const View& view, const Node& node, const priority_tag<1>) noexcept(noexcept(view.edges(node))) + constexpr decltype(auto) out_edges( + const View& view, + const view::vertex_t& vertex, + const priority_tag<1> + ) noexcept(noexcept(view.out_edges(vertex))) { - return view.edges(node); + return view.out_edges(vertex); } - template - requires requires(const View& view, const Node& node) + template + requires requires(const View& view, const view::vertex_t& vertex) { - { edges(view, node) } -> std::ranges::input_range; + { out_edges(view, vertex) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_reference_t, + std::ranges::range_reference_t, typename view::template edge_t>; } - constexpr decltype(auto) edges(const View& view, const Node& node, const priority_tag<0>) noexcept(noexcept(edges(view, node))) + constexpr decltype(auto) out_edges( + const View& view, + const view::vertex_t& vertex, + const priority_tag<0> + ) noexcept(noexcept(out_edges(view, vertex))) { - return edges(view, node); + return out_edges(view, vertex); } - struct edges_fn + struct out_edges_fn { - template - requires requires(const View& view, const Node& node, const priority_tag<2> tag) + template + requires requires(const View& view, const view::vertex_t& vertex, const priority_tag<2> tag) { - { detail::edges(view, node, tag) } -> std::ranges::input_range; + { detail::out_edges(view, vertex, tag) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_reference_t, + std::ranges::range_reference_t, typename view::template edge_t>; } constexpr decltype(auto) operator ()( const View& view, - const Node& node - ) const noexcept(noexcept(detail::edges(view, node, priority_tag<2>{}))) + const view::vertex_t& vertex + ) const noexcept(noexcept(detail::out_edges(view, vertex, priority_tag<2>{}))) { - return detail::edges(view, node, priority_tag<2>{}); + return detail::out_edges(view, vertex, priority_tag<2>{}); } }; @@ -110,24 +124,25 @@ namespace sl::graph::detail namespace sl::graph::view { - inline constexpr detail::edges_fn edges{}; + inline constexpr detail::out_edges_fn out_edges{}; } namespace sl::graph::concepts { - template - concept view_for = basic_node - && sl::concepts::unqualified - && requires(const T& view, const Node& node) + template + concept basic_graph = sl::concepts::unqualified + && std::destructible + && requires(const T& view) { // fixes compile error on msvc v142 // ReSharper disable CppRedundantTemplateKeyword // ReSharper disable CppRedundantTypenameKeyword - typename view::template traits::edge_type; - requires edge_for, Node>; - { view::edges(view, node) } -> std::ranges::input_range; + requires vertex::vertex_type>; + requires edge::edge_type>; + { view::out_edges(view, std::declval&>()) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_value_t>, + std::ranges::range_value_t&>>, typename view::template edge_t>; // ReSharper restore CppRedundantTemplateKeyword // ReSharper restore CppRedundantTypenameKeyword diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 3ff970886..6c9675a1b 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -118,27 +118,20 @@ class BasicViewMock public: inline static constexpr bool trompeloeil_movable_mock = true; - using edge_type = GenericBasicEdge; - - MAKE_CONST_MOCK1(edges, std::vector(const GenericBasicNode&)); + using vertex_type = Vertex; + using edge_type = GenericBasicEdge; - template - requires sg::concepts::edge_for - std::vector edges(const Node& node) const - { - return edges(GenericBasicNode{.vertex = sg::node::vertex(node)}); - } + MAKE_CONST_MOCK1(out_edges, std::vector(const vertex_type&)); }; template class EmptyViewStub { public: - using edge_type = GenericBasicEdge; + using vertex_type = Vertex; + using edge_type = GenericBasicEdge; - template - requires sg::concepts::edge_for - static constexpr std::array edges([[maybe_unused]] const Node& node) noexcept + static constexpr std::array out_edges([[maybe_unused]] const vertex_type&) noexcept { return {}; } @@ -160,13 +153,12 @@ inline static const std::unordered_map> graph{ struct BasicViewStub { - using edge_type = sg::CommonBasicEdge; + using vertex_type = std::string; + using edge_type = sg::CommonBasicEdge; - template - requires sg::concepts::edge_for - static std::vector edges(const Node& current) + static std::vector out_edges(const vertex_type& current) { - const auto vertex = std::stoi(sg::node::vertex(current)); + const auto vertex = std::stoi(current); if (!graph.contains(vertex)) { return {}; @@ -185,13 +177,12 @@ struct BasicViewStub struct WeightedViewStub { + using vertex_type = std::string; using edge_type = sg::CommonWeightedEdge; - template - requires sg::concepts::edge_for - static std::vector edges(const Node& current) + static std::vector out_edges(const vertex_type& current) { - const auto vertex = std::stoi(sg::node::vertex(current)); + const auto vertex = std::stoi(current); if (!graph.contains(vertex)) { return {}; @@ -207,7 +198,7 @@ struct WeightedViewStub { return edge_type{ .destination = std::to_string(v), - .weight = std::abs(v - std::stoi(current.vertex)) + .weight = std::abs(v - vertex) }; }); return infos; diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index e83aef15f..cd081bc25 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -130,7 +130,7 @@ TEMPLATE_LIST_TEST_CASE( const BasicViewMock view{}; // clang, honestly. I hate you. This workaround is necessary for the whole range clang[11, 14] + clangCl const std::vector& edgeCollectionRef = edgeCollection; - REQUIRE_CALL(view, edges(current)) + REQUIRE_CALL(view, out_edges(sg::node::vertex(current))) .LR_RETURN(edgeCollectionRef); NodeFactoryMock nodeFactory{}; @@ -254,7 +254,7 @@ TEST_CASE("detail::BasicTraverser::next returns the current node, or std::nullop SECTION("Next returns a node, when queue contains elements.") { // vertex 43 will be skipped on purpose - REQUIRE_CALL(view, edges(originNode)) + REQUIRE_CALL(view, out_edges(sg::node::vertex(originNode))) .RETURN(std::vector{{41}, {43}, {44}}); REQUIRE_CALL(queue, do_insert(matches(RangeEquals(std::vector{{41}, {44}})))); diff --git a/tests/graph/View.cpp b/tests/graph/View.cpp index 6048a98d1..6ac6e7fdf 100644 --- a/tests/graph/View.cpp +++ b/tests/graph/View.cpp @@ -12,55 +12,55 @@ namespace { - struct member_fun_get_edges + struct member_fun_out_edges { - using edge_type = GenericBasicEdge; + using vertex_type = std::string; + using edge_type = GenericBasicEdge; - MAKE_CONST_MOCK1(edges, std::vector(const GenericBasicNode&)); + MAKE_CONST_MOCK1(out_edges, std::vector(const vertex_type&)); }; - struct free_fun_get_edges + struct free_fun_out_edges { + using vertex_type = std::string; using edge_type = GenericBasicEdge; - MAKE_CONST_MOCK1(get_edges, std::vector(const GenericBasicNode&)); + MAKE_CONST_MOCK1(get_out_edges, std::vector(const vertex_type&)); - friend std::vector edges(const free_fun_get_edges& obj, const GenericBasicNode& node) + friend std::vector out_edges(const free_fun_out_edges& obj, const vertex_type& vertex) { - return obj.get_edges(node); + return obj.get_out_edges(vertex); } }; - struct customized_get_edges + struct customized_out_edges { + using vertex_type = std::string; using edge_type = GenericBasicEdge; - MAKE_CONST_MOCK1(get_edges, std::vector(const GenericBasicNode&)); + MAKE_CONST_MOCK1(get_out_edges, std::vector(const vertex_type&)); }; template struct GenericBasicView { + using vertex_type = Vertex; using edge_type = GenericBasicEdge; - template - requires sg::concepts::edge_for - static std::vector edges(const Node&) + static std::vector out_edges(const vertex_type&) { return {}; } }; - static_assert(sg::concepts::view_for, GenericBasicNode>); - template struct GenericWeightedView { + using vertex_type = Vertex; + using weight_type = Weight; using edge_type = GenericWeightedEdge; - template - requires sg::concepts::edge_for - static std::vector edges(const Node&) + static std::vector out_edges(const vertex_type&) { return {}; } @@ -68,20 +68,61 @@ namespace } template <> -struct sl::graph::customize::edges_fn +struct sl::graph::customize::out_edges_fn { [[nodiscard]] - auto operator ()(const customized_get_edges& e, const GenericBasicNode& node) const + auto operator ()(const customized_out_edges& e, const std::string& vertex) const { - return e.get_edges(node); + return e.get_out_edges(vertex); } }; +TEMPLATE_TEST_CASE_SIG( + "view::traits extracts edge type.", + "[graph][graph::view]", + ((bool dummy, class Expected, class Graph), dummy, Expected, Graph), + (true, GenericBasicEdge, GenericBasicView), + (true, GenericBasicEdge, GenericBasicView), + (true, GenericWeightedEdge, GenericWeightedView) +) +{ + STATIC_REQUIRE(std::same_as::edge_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEMPLATE_TEST_CASE_SIG( + "view::traits extracts vertex type.", + "[graph][graph::view]", + ((bool dummy, class Expected, class Graph), dummy, Expected, Graph), + (true, int, GenericBasicView), + (true, std::string, GenericBasicView), + (true, std::string, GenericWeightedView) +) +{ + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); +} + +TEMPLATE_TEST_CASE_SIG( + "concepts::basic_graph determines, whether the given type satisfies the minimal requirements.", + "[graph][graph::concepts]", + ((bool expected, class Graph), expected, Graph), + (true, GenericBasicView), + (true, GenericWeightedView), + (true, BasicViewMock), + (true, EmptyViewStub), + (true, BasicViewStub), + (true, WeightedViewStub) +) +{ + STATIC_REQUIRE(expected == sg::concepts::basic_graph); +} + TEST_CASE( - "graph::view::edges serves as a customization point, returning the outgoing edges of the given node.", + "graph::view::out_edges serves as a customization point, returning the outgoing edges of the given vertex.", "[graph][graph::view]") { - const GenericBasicNode node{"Hello, World!"}; + const std::string vertex{"Hello, World!"}; const std::vector> expected{ {"Edge0"}, {"Edge1"}, @@ -90,56 +131,25 @@ TEST_CASE( SECTION("Access via the member function.") { - const member_fun_get_edges mock{}; - REQUIRE_CALL(mock, edges(node)) + const member_fun_out_edges mock{}; + REQUIRE_CALL(mock, out_edges(vertex)) .RETURN(expected); - REQUIRE_THAT(sg::view::edges(mock, node), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(sg::view::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); } SECTION("Access via the free function.") { - const free_fun_get_edges mock{}; - REQUIRE_CALL(mock, get_edges(node)) + const free_fun_out_edges mock{}; + REQUIRE_CALL(mock, get_out_edges(vertex)) .RETURN(expected); - REQUIRE_THAT(sg::view::edges(mock, node), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(sg::view::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); } SECTION("Access via customized function.") { - const customized_get_edges mock{}; - REQUIRE_CALL(mock, get_edges(node)) + const customized_out_edges mock{}; + REQUIRE_CALL(mock, get_out_edges(vertex)) .RETURN(expected); - REQUIRE_THAT(sg::view::edges(mock, node), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(sg::view::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); } } - -TEMPLATE_TEST_CASE_SIG( - "view::traits extracts edge type.", - "[graph][graph::view]", - ((bool dummy, class Expected, class Graph), dummy, Expected, Graph), - (true, GenericBasicEdge, GenericBasicView), - (true, GenericBasicEdge, GenericBasicView), - (true, GenericWeightedEdge, GenericWeightedView) -) -{ - STATIC_REQUIRE(std::same_as::edge_type>); - STATIC_REQUIRE(std::same_as>); -} - -TEMPLATE_TEST_CASE_SIG( - "concepts::view_for determines, whether the view type satisfies the minimal requirements of the specified node.", - "[graph][graph::concepts]", - ((bool expected, class Graph, class Node), expected, Graph, Node), - (false, GenericBasicView, GenericBasicNode), - (true, GenericBasicView, GenericBasicNode), - (true, GenericWeightedView, GenericBasicNode), - - (false, GenericBasicView, GenericRankedNode), - (false, GenericWeightedView, GenericRankedNode), - (true, GenericWeightedView, GenericRankedNode), - - (true, BasicViewMock, GenericBasicNode) -) -{ - STATIC_REQUIRE(expected == sg::concepts::view_for); -} From 24e1dee4722d82fe359e90bafa9507d59ee20faa Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 9 Oct 2023 21:18:20 +0200 Subject: [PATCH 236/256] fix: please msvc v142 --- include/Simple-Utility/graph/View.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/View.hpp index e82ff3152..a902489eb 100644 --- a/include/Simple-Utility/graph/View.hpp +++ b/include/Simple-Utility/graph/View.hpp @@ -66,7 +66,7 @@ namespace sl::graph::detail // ReSharper disable CppRedundantTemplateKeyword // ReSharper disable CppRedundantTypenameKeyword template - requires requires(const View& view, const view::vertex_t& vertex) + requires requires(const View& view, const typename view::template vertex_t& vertex) { { view.out_edges(vertex) } -> std::ranges::input_range; requires std::convertible_to< @@ -83,7 +83,7 @@ namespace sl::graph::detail } template - requires requires(const View& view, const view::vertex_t& vertex) + requires requires(const View& view, const typename view::template vertex_t& vertex) { { out_edges(view, vertex) } -> std::ranges::input_range; requires std::convertible_to< @@ -102,7 +102,7 @@ namespace sl::graph::detail struct out_edges_fn { template - requires requires(const View& view, const view::vertex_t& vertex, const priority_tag<2> tag) + requires requires(const View& view, const typename view::template vertex_t& vertex, const priority_tag<2> tag) { { detail::out_edges(view, vertex, tag) } -> std::ranges::input_range; requires std::convertible_to< @@ -139,10 +139,10 @@ namespace sl::graph::concepts // ReSharper disable CppRedundantTypenameKeyword requires vertex::vertex_type>; requires edge::edge_type>; - { view::out_edges(view, std::declval&>()) } -> std::ranges::input_range; + { view::out_edges(view, std::declval&>()) } -> std::ranges::input_range; requires std::convertible_to< std::ranges::range_value_t&>>, + detail::out_edges_fn, const T&, const typename view::template vertex_t&>>, typename view::template edge_t>; // ReSharper restore CppRedundantTemplateKeyword // ReSharper restore CppRedundantTypenameKeyword From 3e0fcc0195e3cf3df42de706441aae1488e5e960 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 12 Oct 2023 12:48:25 +0200 Subject: [PATCH 237/256] refactor: rename view symbols to graph --- benchmarks/graph/AStarBoostComparison.cpp | 4 +- include/Simple-Utility/graph/AStarSearch.hpp | 10 +-- .../graph/BreadthFirstSearch.hpp | 6 +- .../Simple-Utility/graph/DepthFirstSearch.hpp | 6 +- .../graph/{View.hpp => Graph.hpp} | 88 +++++++++---------- include/Simple-Utility/graph/Traverse.hpp | 54 ++++++------ .../graph/UniformCostSearch.hpp | 6 +- tests/graph/AStarSearch.cpp | 8 +- tests/graph/BreadthFirstSearch.cpp | 16 ++-- tests/graph/CMakeLists.txt | 2 +- tests/graph/Defines.hpp | 23 +++-- tests/graph/DepthFirstSearch.cpp | 16 ++-- tests/graph/{View.cpp => Graph.cpp} | 76 ++++++---------- tests/graph/Traverse.cpp | 10 +-- tests/graph/UniformCostSearch.cpp | 8 +- 15 files changed, 160 insertions(+), 173 deletions(-) rename include/Simple-Utility/graph/{View.hpp => Graph.hpp} (50%) rename tests/graph/{View.cpp => Graph.cpp} (54%) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index 8ed92b052..b99468271 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -264,7 +264,7 @@ void random_maze(maze& m, const std::uint32_t seed) ############################## */ template <> -struct sl::graph::view::traits> +struct sl::graph::graph::traits> { using edge_type = CommonWeightedEdge; using vertex_type = edge::vertex_t; @@ -274,7 +274,7 @@ struct sl::graph::view::traits> template <> struct sl::graph::customize::out_edges_fn> { - using edge_type = view::edge_t>; + using edge_type = graph::edge_t>; using vertex_type = edge::vertex_t; using weight_type = edge::weight_t; diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index 85a0a04ea..e9a4cbd88 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -165,18 +165,18 @@ namespace sl::graph::astar }; template < - concepts::basic_graph View, + concepts::basic_graph Graph, typename Heuristic, - concepts::ranked_node Node = CommonNode>, edge::weight_t>>, + concepts::ranked_node Node = CommonNode>, edge::weight_t>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::heuristic_for using Range = IterableTraverser< - graph::detail::BasicTraverser< + sl::graph::detail::BasicTraverser< Node, - View, + Graph, queue::CommonPriorityQueue, Tracker, - graph::detail::default_explorer_t< + sl::graph::detail::default_explorer_t< Node, NodeFactory>>>; } diff --git a/include/Simple-Utility/graph/BreadthFirstSearch.hpp b/include/Simple-Utility/graph/BreadthFirstSearch.hpp index 06e5aa5b1..73e1b8bdd 100644 --- a/include/Simple-Utility/graph/BreadthFirstSearch.hpp +++ b/include/Simple-Utility/graph/BreadthFirstSearch.hpp @@ -24,14 +24,14 @@ namespace sl::graph::dfs using CommonNode = CommonBasicNode; template < - concepts::basic_graph View, - concepts::basic_node Node = CommonNode>>, + concepts::basic_graph Graph, + concepts::basic_node Node = CommonNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires (!concepts::ranked_node) using Range = IterableTraverser< detail::BasicTraverser< Node, - View, + Graph, queue::CommonQueue, Tracker, detail::default_explorer_t>>>; diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index 8a3220ebe..0fc216bf1 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -24,14 +24,14 @@ namespace sl::graph::dfs using CommonNode = CommonBasicNode; template < - concepts::basic_graph View, - concepts::basic_node Node = CommonNode>>, + concepts::basic_graph Graph, + concepts::basic_node Node = CommonNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires (!concepts::ranked_node) using Range = IterableTraverser< detail::BasicTraverser< Node, - View, + Graph, queue::CommonStack, Tracker, detail::default_explorer_t>>>; diff --git a/include/Simple-Utility/graph/View.hpp b/include/Simple-Utility/graph/Graph.hpp similarity index 50% rename from include/Simple-Utility/graph/View.hpp rename to include/Simple-Utility/graph/Graph.hpp index a902489eb..d6b6251bf 100644 --- a/include/Simple-Utility/graph/View.hpp +++ b/include/Simple-Utility/graph/Graph.hpp @@ -16,7 +16,7 @@ // ReSharper disable once CppUnusedIncludeDirective #include // std::ranges::input_range, etc. -namespace sl::graph::view +namespace sl::graph::graph { template struct traits; @@ -45,76 +45,76 @@ namespace sl::graph::customize namespace sl::graph::detail { - template - requires requires { customize::out_edges_fn{}; } + template + requires requires { customize::out_edges_fn{}; } && std::ranges::input_range, const View&, const view::vertex_t&>> + customize::out_edges_fn, const Graph&, const graph::vertex_t&>> && std::convertible_to< std::ranges::range_reference_t, const View&, const view::vertex_t&>>, - view::edge_t> + customize::out_edges_fn, const Graph&, const graph::vertex_t&>>, + graph::edge_t> constexpr decltype(auto) out_edges( - const View& view, - const view::vertex_t& vertex, + const Graph& graph, + const graph::vertex_t& vertex, const priority_tag<2> - ) noexcept(noexcept(customize::out_edges_fn{}(view, vertex))) + ) noexcept(noexcept(customize::out_edges_fn{}(graph, vertex))) { - return customize::out_edges_fn{}(view, vertex); + return customize::out_edges_fn{}(graph, vertex); } // pleases msvc v142 // ReSharper disable CppRedundantTemplateKeyword // ReSharper disable CppRedundantTypenameKeyword - template - requires requires(const View& view, const typename view::template vertex_t& vertex) + template + requires requires(const Graph& graph, const typename graph::template vertex_t& vertex) { - { view.out_edges(vertex) } -> std::ranges::input_range; + { graph.out_edges(vertex) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_reference_t, - typename view::template edge_t>; + std::ranges::range_reference_t, + typename graph::template edge_t>; } constexpr decltype(auto) out_edges( - const View& view, - const view::vertex_t& vertex, + const Graph& graph, + const graph::vertex_t& vertex, const priority_tag<1> - ) noexcept(noexcept(view.out_edges(vertex))) + ) noexcept(noexcept(graph.out_edges(vertex))) { - return view.out_edges(vertex); + return graph.out_edges(vertex); } - template - requires requires(const View& view, const typename view::template vertex_t& vertex) + template + requires requires(const Graph& graph, const typename graph::template vertex_t& vertex) { - { out_edges(view, vertex) } -> std::ranges::input_range; + { out_edges(graph, vertex) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_reference_t, - typename view::template edge_t>; + std::ranges::range_reference_t, + typename graph::template edge_t>; } constexpr decltype(auto) out_edges( - const View& view, - const view::vertex_t& vertex, + const Graph& graph, + const graph::vertex_t& vertex, const priority_tag<0> - ) noexcept(noexcept(out_edges(view, vertex))) + ) noexcept(noexcept(out_edges(graph, vertex))) { - return out_edges(view, vertex); + return out_edges(graph, vertex); } struct out_edges_fn { - template - requires requires(const View& view, const typename view::template vertex_t& vertex, const priority_tag<2> tag) + template + requires requires(const Graph& graph, const typename graph::template vertex_t& vertex, const priority_tag<2> tag) { - { detail::out_edges(view, vertex, tag) } -> std::ranges::input_range; + { detail::out_edges(graph, vertex, tag) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_reference_t, - typename view::template edge_t>; + std::ranges::range_reference_t, + typename graph::template edge_t>; } constexpr decltype(auto) operator ()( - const View& view, - const view::vertex_t& vertex - ) const noexcept(noexcept(detail::out_edges(view, vertex, priority_tag<2>{}))) + const Graph& graph, + const graph::vertex_t& vertex + ) const noexcept(noexcept(detail::out_edges(graph, vertex, priority_tag<2>{}))) { - return detail::out_edges(view, vertex, priority_tag<2>{}); + return detail::out_edges(graph, vertex, priority_tag<2>{}); } }; @@ -122,7 +122,7 @@ namespace sl::graph::detail // ReSharper restore CppRedundantTypenameKeyword } -namespace sl::graph::view +namespace sl::graph::graph { inline constexpr detail::out_edges_fn out_edges{}; } @@ -132,18 +132,18 @@ namespace sl::graph::concepts template concept basic_graph = sl::concepts::unqualified && std::destructible - && requires(const T& view) + && requires(const T& graph) { // fixes compile error on msvc v142 // ReSharper disable CppRedundantTemplateKeyword // ReSharper disable CppRedundantTypenameKeyword - requires vertex::vertex_type>; - requires edge::edge_type>; - { view::out_edges(view, std::declval&>()) } -> std::ranges::input_range; + requires vertex::vertex_type>; + requires edge::edge_type>; + { graph::out_edges(graph, std::declval&>()) } -> std::ranges::input_range; requires std::convertible_to< std::ranges::range_value_t&>>, - typename view::template edge_t>; + detail::out_edges_fn, const T&, const typename graph::template vertex_t&>>, + typename graph::template edge_t>; // ReSharper restore CppRedundantTemplateKeyword // ReSharper restore CppRedundantTypenameKeyword }; diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index d1ec34412..53696ef41 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -11,10 +11,10 @@ #include "Simple-Utility/Config.hpp" #include "Simple-Utility/functional/Tuple.hpp" #include "Simple-Utility/graph/Edge.hpp" +#include "Simple-Utility/graph/Graph.hpp" #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Queue.hpp" #include "Simple-Utility/graph/Tracker.hpp" -#include "Simple-Utility/graph/View.hpp" #include #include @@ -26,28 +26,28 @@ namespace sl::graph::concepts { - template + template concept explorer = basic_node - && basic_graph + && basic_graph && tracker_for> && sl::concepts::unqualified && std::destructible - && requires(const T& explorer, const Node& node, const View& view, Tracker& tracker) + && requires(const T& explorer, const Node& node, const Graph& view, Tracker& tracker) { { std::invoke(explorer, node::vertex(node), tracker) } -> std::convertible_to; { std::invoke(explorer, view, node, tracker) } -> std::ranges::input_range; requires std::convertible_to< - std::ranges::range_reference_t>, + std::ranges::range_reference_t>, Node>; }; - template - concept traverser_kernel = basic_graph + template + concept traverser_kernel = basic_graph && sl::concepts::unqualified && std::destructible && requires( T& kernel, - const View& view, + const Graph& view, const Explorer& explorer, Queue& queue, Tracker& tracker @@ -138,14 +138,14 @@ namespace sl::graph::detail std::invoke_result_t< NodeFactory, const Node&, - view::edge_t>, + graph::edge_t>, Node> [[nodiscard]] constexpr auto operator ()(const View& graph, const Node& current, Tracker& tracker) const { return std::invoke( CollectorStrategy{}, - view::out_edges(graph, node::vertex(current)), + graph::out_edges(graph, node::vertex(current)), current, m_NodeFactory, tracker); @@ -206,9 +206,9 @@ namespace sl::graph::detail struct PreOrderKernel { - template + template [[nodiscard]] - constexpr auto operator()(const View& view, const Explorer& explorer, Queue& queue, Tracker& tracker) const + constexpr auto operator()(const Graph& graph, const Explorer& explorer, Queue& queue, Tracker& tracker) const { using node_type = std::remove_cvref_t; @@ -233,7 +233,7 @@ namespace sl::graph::detail { queue::insert( queue, - std::invoke(explorer, view, *result, tracker)); + std::invoke(explorer, graph, *result, tracker)); } return result; @@ -242,19 +242,19 @@ namespace sl::graph::detail template < concepts::basic_node Node, - concepts::basic_graph View, + concepts::basic_graph Graph, concepts::queue_for QueueStrategy, concepts::tracker_for> TrackingStrategy, - concepts::explorer ExplorationStrategy = default_explorer_t>, - concepts::traverser_kernel KernelStrategy = PreOrderKernel> - requires concepts::edge_for, Node> + concepts::explorer ExplorationStrategy = default_explorer_t>, + concepts::traverser_kernel KernelStrategy = PreOrderKernel> + requires concepts::edge_for, Node> class BasicTraverser { public: using node_type = Node; - using edge_type = view::edge_t; + using edge_type = graph::edge_t; using vertex_type = node::vertex_t; - using view_type = View; + using graph_type = Graph; using queue_type = QueueStrategy; using tracker_type = TrackingStrategy; @@ -266,18 +266,18 @@ namespace sl::graph::detail BasicTraverser& operator =(BasicTraverser&&) = default; template < - typename... ViewArgs, + typename... GraphArgs, typename... QueueArgs, typename... TrackerArgs, typename... ExplorerArgs> - requires std::constructible_from + requires std::constructible_from && std::constructible_from && std::constructible_from && std::constructible_from [[nodiscard]] explicit constexpr BasicTraverser( const vertex_type& origin, - std::tuple viewArgs, + std::tuple graphArgs, std::tuple queueArgs, std::tuple trackerArgs, std::tuple explorerArgs @@ -285,7 +285,7 @@ namespace sl::graph::detail : m_Explorer{std::make_from_tuple(std::move(explorerArgs))}, m_Queue{std::make_from_tuple(std::move(queueArgs))}, m_Tracker{std::make_from_tuple(std::move(trackerArgs))}, - m_View{std::make_from_tuple(std::move(viewArgs))} + m_Graph{std::make_from_tuple(std::move(graphArgs))} { assert(queue::empty(m_Queue) && "Queue already contains elements."); @@ -295,7 +295,7 @@ namespace sl::graph::detail [[nodiscard]] constexpr std::optional next() { - return std::invoke(m_Kernel, m_View, m_Explorer, m_Queue, m_Tracker); + return std::invoke(m_Kernel, m_Graph, m_Explorer, m_Queue, m_Tracker); } [[nodiscard]] @@ -311,9 +311,9 @@ namespace sl::graph::detail } [[nodiscard]] - constexpr const view_type& view() const noexcept + constexpr const graph_type& view() const noexcept { - return m_View; + return m_Graph; } private: @@ -321,7 +321,7 @@ namespace sl::graph::detail KernelStrategy m_Kernel{}; queue_type m_Queue; tracker_type m_Tracker; - view_type m_View; + graph_type m_Graph; }; } diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp index 25c4cfb0c..d10bc22d6 100644 --- a/include/Simple-Utility/graph/UniformCostSearch.hpp +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -24,13 +24,13 @@ namespace sl::graph::ucs using CommonNode = CommonRankedNode; template < - concepts::basic_graph View, - concepts::ranked_node Node = CommonNode>, edge::weight_t>>, + concepts::basic_graph Graph, + concepts::ranked_node Node = CommonNode>, edge::weight_t>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> using Range = IterableTraverser< detail::BasicTraverser< Node, - View, + Graph, queue::CommonPriorityQueue, Tracker, detail::default_explorer_t>>>; diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index e0b151247..a5268be73 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -88,7 +88,7 @@ namespace TEMPLATE_TEST_CASE( "astar::Range visits all reachable vertices.", "[graph][graph::astar]", - WeightedViewStub) + WeightedGraphStub) { using Node = sg::astar::CommonNode; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toCommonAStarNode))); @@ -108,7 +108,7 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "astar::Range node can be decorated with DepthNodeDecorator.", "[graph][graph::astar]", - WeightedViewStub + WeightedGraphStub ) { using Node = sg::decorator::DepthNode>; @@ -129,7 +129,7 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "astar::Range node can be decorated with PredecessorNodeDecorator.", "[graph][graph::astar]", - WeightedViewStub + WeightedGraphStub ) { using Node = sg::decorator::PredecessorNode>; @@ -150,7 +150,7 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "astar::Range can be used with arbitrary decorated nodes.", "[graph][graph::astar]", - WeightedViewStub + WeightedGraphStub ) { using Node = sg::decorator::DepthNode>>; diff --git a/tests/graph/BreadthFirstSearch.cpp b/tests/graph/BreadthFirstSearch.cpp index 905c39a79..ac6368147 100644 --- a/tests/graph/BreadthFirstSearch.cpp +++ b/tests/graph/BreadthFirstSearch.cpp @@ -45,8 +45,8 @@ namespace TEMPLATE_TEST_CASE( "bfs::Range visits all reachable vertices.", "[graph][graph::bfs]", - BasicViewStub, - WeightedViewStub + BasicGraphStub, + WeightedGraphStub ) { using Node = sg::CommonBasicNode; @@ -61,8 +61,8 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "bfs::Range can be used with depth decorated nodes.", "[graph][graph::bfs]", - BasicViewStub, - WeightedViewStub + BasicGraphStub, + WeightedGraphStub ) { using Node = sg::decorator::DepthNode>; @@ -77,8 +77,8 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "bfs::Range can be used with predecessor decorated nodes.", "[graph][graph::bfs]", - BasicViewStub, - WeightedViewStub + BasicGraphStub, + WeightedGraphStub ) { using Node = sg::decorator::PredecessorNode>; @@ -93,8 +93,8 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "bfs::Range can be used with arbitrary decorated nodes.", "[graph][graph::bfs]", - BasicViewStub, - WeightedViewStub + BasicGraphStub, + WeightedGraphStub ) { using Node = sg::decorator::DepthNode>>; diff --git a/tests/graph/CMakeLists.txt b/tests/graph/CMakeLists.txt index 0d7923cf2..9323546f6 100644 --- a/tests/graph/CMakeLists.txt +++ b/tests/graph/CMakeLists.txt @@ -7,10 +7,10 @@ target_sources( "DepthFirstSearch.cpp" "Edge.cpp" "Formatter.cpp" + "Graph.cpp" "Node.cpp" "Queue.cpp" "Tracker.cpp" "Traverse.cpp" "UniformCostSearch.cpp" - "View.cpp" ) diff --git a/tests/graph/Defines.hpp b/tests/graph/Defines.hpp index 6c9675a1b..59a4384c6 100644 --- a/tests/graph/Defines.hpp +++ b/tests/graph/Defines.hpp @@ -13,9 +13,9 @@ #include "Simple-Utility/graph/Common.hpp" #include "Simple-Utility/graph/Edge.hpp" #include "Simple-Utility/graph/Formatter.hpp" +#include "Simple-Utility/graph/Graph.hpp" #include "Simple-Utility/graph/Node.hpp" #include "Simple-Utility/graph/Tracker.hpp" -#include "Simple-Utility/graph/View.hpp" #include "Simple-Utility/test_util/Catch2Ext.hpp" #include "Simple-Utility/test_util/TrompeloeilExt.hpp" @@ -113,7 +113,7 @@ class TrackerMock }; template -class BasicViewMock +class BasicGraphMock { public: inline static constexpr bool trompeloeil_movable_mock = true; @@ -124,8 +124,21 @@ class BasicViewMock MAKE_CONST_MOCK1(out_edges, std::vector(const vertex_type&)); }; +template +class WeightedGraphMock +{ +public: + inline static constexpr bool trompeloeil_movable_mock = true; + + using vertex_type = Vertex; + using weight_type = Weight; + using edge_type = GenericWeightedEdge; + + MAKE_CONST_MOCK1(out_edges, std::vector(const vertex_type&)); +}; + template -class EmptyViewStub +class EmptyGraphStub { public: using vertex_type = Vertex; @@ -151,7 +164,7 @@ inline static const std::unordered_map> graph{ {9, {4}} }; -struct BasicViewStub +struct BasicGraphStub { using vertex_type = std::string; using edge_type = sg::CommonBasicEdge; @@ -175,7 +188,7 @@ struct BasicViewStub } }; -struct WeightedViewStub +struct WeightedGraphStub { using vertex_type = std::string; using edge_type = sg::CommonWeightedEdge; diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 7db4df319..12602adfa 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -45,8 +45,8 @@ namespace TEMPLATE_TEST_CASE( "dfs::Range visits all reachable vertices.", "[graph][graph::dfs]", - BasicViewStub, - WeightedViewStub + BasicGraphStub, + WeightedGraphStub ) { using Node = sg::CommonBasicNode; @@ -61,8 +61,8 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "dfs::Range can be used with depth decorated nodes.", "[graph][graph::dfs]", - BasicViewStub, - WeightedViewStub + BasicGraphStub, + WeightedGraphStub ) { using Node = sg::decorator::DepthNode>; @@ -77,8 +77,8 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "dfs::Range can be used with predecessor decorated nodes.", "[graph][graph::dfs]", - BasicViewStub, - WeightedViewStub + BasicGraphStub, + WeightedGraphStub ) { using Node = sg::decorator::PredecessorNode>; @@ -93,8 +93,8 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "dfs::Range can be used with arbitrary decorated nodes.", "[graph][graph::dfs]", - BasicViewStub, - WeightedViewStub + BasicGraphStub, + WeightedGraphStub ) { using Node = sg::decorator::DepthNode>>; diff --git a/tests/graph/View.cpp b/tests/graph/Graph.cpp similarity index 54% rename from tests/graph/View.cpp rename to tests/graph/Graph.cpp index 6ac6e7fdf..0b5014747 100644 --- a/tests/graph/View.cpp +++ b/tests/graph/Graph.cpp @@ -3,7 +3,7 @@ // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) -#include "Simple-Utility/graph/View.hpp" +#include "Simple-Utility/graph/Graph.hpp" #include #include @@ -40,31 +40,6 @@ namespace MAKE_CONST_MOCK1(get_out_edges, std::vector(const vertex_type&)); }; - - template - struct GenericBasicView - { - using vertex_type = Vertex; - using edge_type = GenericBasicEdge; - - static std::vector out_edges(const vertex_type&) - { - return {}; - } - }; - - template - struct GenericWeightedView - { - using vertex_type = Vertex; - using weight_type = Weight; - using edge_type = GenericWeightedEdge; - - static std::vector out_edges(const vertex_type&) - { - return {}; - } - }; } template <> @@ -78,49 +53,48 @@ struct sl::graph::customize::out_edges_fn }; TEMPLATE_TEST_CASE_SIG( - "view::traits extracts edge type.", - "[graph][graph::view]", + "graph::traits extracts edge type.", + "[graph][graph::graph]", ((bool dummy, class Expected, class Graph), dummy, Expected, Graph), - (true, GenericBasicEdge, GenericBasicView), - (true, GenericBasicEdge, GenericBasicView), - (true, GenericWeightedEdge, GenericWeightedView) + (true, GenericBasicEdge, BasicGraphMock), + (true, GenericBasicEdge, BasicGraphMock), + (true, GenericWeightedEdge, WeightedGraphMock) ) { - STATIC_REQUIRE(std::same_as::edge_type>); - STATIC_REQUIRE(std::same_as>); + STATIC_REQUIRE(std::same_as::edge_type>); + STATIC_REQUIRE(std::same_as>); } TEMPLATE_TEST_CASE_SIG( - "view::traits extracts vertex type.", - "[graph][graph::view]", + "graph::traits extracts vertex type.", + "[graph][graph::graph]", ((bool dummy, class Expected, class Graph), dummy, Expected, Graph), - (true, int, GenericBasicView), - (true, std::string, GenericBasicView), - (true, std::string, GenericWeightedView) + (true, int, BasicGraphMock), + (true, std::string, BasicGraphMock), + (true, std::string, WeightedGraphMock) ) { - STATIC_REQUIRE(std::same_as::vertex_type>); - STATIC_REQUIRE(std::same_as>); + STATIC_REQUIRE(std::same_as::vertex_type>); + STATIC_REQUIRE(std::same_as>); } TEMPLATE_TEST_CASE_SIG( "concepts::basic_graph determines, whether the given type satisfies the minimal requirements.", "[graph][graph::concepts]", ((bool expected, class Graph), expected, Graph), - (true, GenericBasicView), - (true, GenericWeightedView), - (true, BasicViewMock), - (true, EmptyViewStub), - (true, BasicViewStub), - (true, WeightedViewStub) + (true, BasicGraphMock), + (true, WeightedGraphMock), + (true, EmptyGraphStub), + (true, BasicGraphStub), + (true, WeightedGraphStub) ) { STATIC_REQUIRE(expected == sg::concepts::basic_graph); } TEST_CASE( - "graph::view::out_edges serves as a customization point, returning the outgoing edges of the given vertex.", - "[graph][graph::view]") + "graph::graph::out_edges serves as a customization point, returning the outgoing edges of the given vertex.", + "[graph][graph::graph]") { const std::string vertex{"Hello, World!"}; const std::vector> expected{ @@ -134,7 +108,7 @@ TEST_CASE( const member_fun_out_edges mock{}; REQUIRE_CALL(mock, out_edges(vertex)) .RETURN(expected); - REQUIRE_THAT(sg::view::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(sg::graph::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); } SECTION("Access via the free function.") @@ -142,7 +116,7 @@ TEST_CASE( const free_fun_out_edges mock{}; REQUIRE_CALL(mock, get_out_edges(vertex)) .RETURN(expected); - REQUIRE_THAT(sg::view::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(sg::graph::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); } SECTION("Access via customized function.") @@ -150,6 +124,6 @@ TEST_CASE( const customized_out_edges mock{}; REQUIRE_CALL(mock, get_out_edges(vertex)) .RETURN(expected); - REQUIRE_THAT(sg::view::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(sg::graph::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); } } diff --git a/tests/graph/Traverse.cpp b/tests/graph/Traverse.cpp index cd081bc25..942aea69e 100644 --- a/tests/graph/Traverse.cpp +++ b/tests/graph/Traverse.cpp @@ -25,7 +25,7 @@ using DefaultNode = GenericBasicNode; using DefaultEdge = GenericBasicEdge; using DefaultQueue = QueueMock; using DefaultTracker = TrackerMock>; -using DefaultView = BasicViewMock>; +using DefaultView = BasicGraphMock>; using DefaultTraverser = sg::detail::BasicTraverser< DefaultNode, DefaultView, @@ -34,7 +34,7 @@ using DefaultTraverser = sg::detail::BasicTraverser< using MovableTraverser = sg::detail::BasicTraverser< DefaultNode, - EmptyViewStub, + EmptyGraphStub, EmptyQueueStub, sg::tracker::Null>; @@ -87,7 +87,7 @@ TEMPLATE_LIST_TEST_CASE( TestExplorers ) { - STATIC_REQUIRE(sg::concepts::explorer, TrackerMock>); + STATIC_REQUIRE(sg::concepts::explorer, TrackerMock>); } TEMPLATE_LIST_TEST_CASE( @@ -127,7 +127,7 @@ TEMPLATE_LIST_TEST_CASE( })); CHECK(std::ssize(edgeCollection) == std::ssize(visited)); - const BasicViewMock view{}; + const BasicGraphMock view{}; // clang, honestly. I hate you. This workaround is necessary for the whole range clang[11, 14] + clangCl const std::vector& edgeCollectionRef = edgeCollection; REQUIRE_CALL(view, out_edges(sg::node::vertex(current))) @@ -168,7 +168,7 @@ TEMPLATE_LIST_TEST_CASE( { using Explorer = sg::detail::BufferedExplorer>; - STATIC_REQUIRE(sg::concepts::traverser_kernel, Explorer, QueueMock, TrackerMock>); + STATIC_REQUIRE(sg::concepts::traverser_kernel, Explorer, QueueMock, TrackerMock>); } TEST_CASE( diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp index 49a194227..beeb572f9 100644 --- a/tests/graph/UniformCostSearch.cpp +++ b/tests/graph/UniformCostSearch.cpp @@ -46,7 +46,7 @@ namespace TEMPLATE_TEST_CASE( "ucs::Range visits all reachable vertices.", "[graph][graph::ucs]", - WeightedViewStub + WeightedGraphStub ) { using Node = sg::CommonRankedNode; @@ -61,7 +61,7 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "ucs::Range node can be decorated with DepthNode.", "[graph][graph::ucs]", - WeightedViewStub + WeightedGraphStub ) { using Node = sg::decorator::DepthNode>; @@ -76,7 +76,7 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "ucs::Range node can be decorated with PredecessorNode.", "[graph][graph::ucs]", - WeightedViewStub + WeightedGraphStub ) { using Node = sg::decorator::PredecessorNode>; @@ -91,7 +91,7 @@ TEMPLATE_TEST_CASE( TEMPLATE_TEST_CASE( "ucs::Range can be used with arbitrary decorated nodes.", "[graph][graph::ucs]", - WeightedViewStub + WeightedGraphStub ) { using Node = sg::decorator::DepthNode>>; From c3d399ff2440bf302e7653074f7cb140f1acc511 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 12 Oct 2023 15:58:25 +0200 Subject: [PATCH 238/256] docs: add graph/Common.hpp documentation --- include/Simple-Utility/graph/Common.hpp | 110 ++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index e37010453..5fd73f37d 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -13,13 +13,103 @@ #include +/** + * \defgroup GROUP_GRAPH graph + * + * \brief This library offers various graph related algorithms. + * \details + * # Design philosophy + * This library is built completely on concepts, thus almost anything is exchangeable by users. Some parts may be worth the effort, some may not. + * Symbols in ``detail`` namespace can be used, but are less secure and may require some more insights into the internals to be used correctly. + * + * The (as far as I'm aware of) unique core idea of this library is to *somehow* sort the vertices of an arbitrary graph as sequential range. The actual + * algorithm then defines the *somehow* in an exchangeable manner. + * + * Many graph libraries utilize the visitor approach, where the visitor serves as the major customization point and gets notified about pre-defined + * events during the whole algorithm runtime. These visitor may or may not be able to abort the algorithm, e.g. if they reached their goal. For example + * boost requires the visitor throwing an exception (see [boost graph faq](https://www.boost.org/doc/libs/1_83_0/libs/graph/doc/faq.html)), which is not + * what I would consider a clean design. + * + * The major issue I see with the visitor approach is, that this pollutes the actual algorithm with too much complexity, as it must be versatile enough to + * satisfy any (or at least many) user requirements, while optimally disabling the optional features, the user does not need. It's really difficult to think about + * any possible use-case the algorithm may be utilized for and then making it generic enough, while still offering top-notch performance, being versatile + * and having a clean design. + * The iterative approach of this library therefore concentrates on being a cursor into a graph and notifying the user about the currently referred vertex + * (including possibly additional information). The users themselves can than decide how to proceed, e.g. if and how to store the yielded state or simply + * throwing the whole algorithm state away, because the job is done. Having the algorithm state present as an actual object makes it trivial to run the + * algorithm for a specific amount of steps (or until a predicate isn't satisfied, or whatever condition one may come up with) and continue later on. + * + * Nevertheless, this library neither aims to replace any existing graph library nor does it want to compete with them in performance regards. Other + * libraries are (and probably stay) more feature rich, faster and more polished. + * + * ## Concepts + * All algorithms utilize at least some of the general concepts. The term concept doesn't necessarily mean a c++ concept, its rather from a design perspective. + * Nevertheless, most of these design concepts are indeed implemented as c++ concepts. + * + * ### Graph + * A graph consists of multiple elements (vertices) and multiple edges. Most of the algorithms allow graphs to be dynamically explored during the traversal, + * thus they can be generated on the fly. + * + * ### Vertex + * A vertex denotes a unique graph element and is used by algorithms to query the graph for further information (e.g. neighboring vertices). + * + * ### Edges + * A edge denotes a connection between two neighboring vertices of a graph and may contain a ``weight``, which is used by some algorithms, to select the best + * alternatives out of the known edges. + * + * ### Node + * A node is an info structure generated by traversing algorithms, which is used to keep track of the exploration state. After each exploration step the current + * node is returned to the caller, which may cache it in its own container or simply throw it away. + * + * Nodes consist of at least a vertex, which they are related to, but may also contain additional information provided by the algorithm. + * In fact, the algorithms require a minimal property set, but users are free to extend them with their own properties. + * + * ### Tracker + * In general, a Graph may contain loops or merging branches, which will eventually lead to multiple discovery of the same vertices. The trackers task is then + * to identify these multiple discoveries and decide if the current vertex should be further investigated or skipped. There are three exploration states, a vertex + * can be in: + * - ``unknown``: The vertex is not known by the tracker and should be further investigated. + * - ``discovered``: The tracker already knows about the vertex, but there may be still a better solution, thus it should be further investigated. + * - ``visited``: The vertex has already been visited and should be skipped. + * + * The actual tracker implementation is usually one of the first optimizations users can apply. Per default, a hash map is used, which is a good general + * purpose solution, but isn't the most efficient for many cases. E.g. a grid graph could utilize a 2d bool array, which will then lead to a constant lookup time. + * + * ### Queue + * A queue stores the discovered nodes and returns them back in any order it desires, thus queue doesn't necessarily mean a FIFO structure. In fact, while the breadth- + * first-search is indeed built on top of an actual queue, the depth-first-search for example utilizes a stack (LIFO) container. A queue therefore has major influence + * about the algorithms behaviour and should therefore only exchanged with an equivalent container. + * + * ### Traverser + * The traverser couples everything together and can be queried for the next vertex, which will then either return the next visited node, or a null-object if there were + * no pending nodes left. + * + * A traverser can be decorated with ``IterableTraverser``, which in fact provides ``begin`` and ``end`` members, and can then be used as a input-range with any existing + * algorithms. + */ + namespace sl::graph::concepts { + /** + * \defgroup GROUP_GRAPH_CONCEPTS concepts + * \ingroup GROUP_GRAPH + * \brief Contains general library concepts. + *\{ + */ + + /** + * \brief Checks, whether the given type satisfies the vertex type requirements. + * \tparam T Type to check. + */ template concept vertex = sl::concepts::unqualified && std::equality_comparable && std::copyable; + /** + * \brief Checks, whether the given type satisfies the weight type requirements. + * \tparam T Type to check. + */ template concept weight = sl::concepts::unqualified && std::copyable @@ -28,22 +118,42 @@ namespace sl::graph::concepts && sl::concepts::plus_assign && sl::concepts::minus_assign; + /** + * \brief Checks, whether the given type satisfies the rank type requirements. + * \tparam T Type to check. + */ template concept rank = sl::concepts::unqualified && std::regular && std::totally_ordered; + /** + * \brief Checks, whether the given type contains a ``vertex_type`` member alias. + * \tparam T Type to check. + */ template concept readable_vertex_type = requires { typename T::vertex_type; } && vertex; + /** + * \brief Checks, whether the given type contains a ``weight_type`` member alias. + * \tparam T Type to check. + */ template concept readable_weight_type = requires { typename T::weight_type; } && weight; + /** + * \brief Checks, whether the given type contains a ``rank_type`` member alias. + * \tparam T Type to check. + */ template concept readable_rank_type = requires { typename T::rank_type; } && rank; + + /** + * \} + */ } #endif From 967744a15dcbc1d9a6719de871fe7545b54dbea4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 12 Oct 2023 16:21:32 +0200 Subject: [PATCH 239/256] feat: std::reference_wrapper graph mixin --- benchmarks/graph/AStarBoostComparison.cpp | 12 +++--- .../mixins/graph/std_reference_wrapper.hpp | 37 +++++++++++++++++++ tests/graph/Graph.cpp | 37 +++++++++++++++++-- 3 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 include/Simple-Utility/graph/mixins/graph/std_reference_wrapper.hpp diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index b99468271..eb5a3ebc1 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -15,8 +15,8 @@ #include #include -#include #include +#include #include #include @@ -26,6 +26,7 @@ #include "../Defines.hpp" #include "Simple-Utility/graph/AStarSearch.hpp" +#include "Simple-Utility/graph/mixins/graph/std_reference_wrapper.hpp" /* Most of this code is directly taken from https://github.com/boostorg/graph/blob/develop/example/astar_maze.cpp * and slightly modernized. @@ -264,7 +265,7 @@ void random_maze(maze& m, const std::uint32_t seed) ############################## */ template <> -struct sl::graph::graph::traits> +struct sl::graph::graph::traits { using edge_type = CommonWeightedEdge; using vertex_type = edge::vertex_t; @@ -272,19 +273,20 @@ struct sl::graph::graph::traits> }; template <> -struct sl::graph::customize::out_edges_fn> +struct sl::graph::customize::out_edges_fn { - using edge_type = graph::edge_t>; + using edge_type = graph::edge_t; using vertex_type = edge::vertex_t; using weight_type = edge::weight_t; + [[nodiscard]] auto operator ()(const maze& m, const vertex_type& current) const { const auto& g = m.get_grid(); const auto [edgesBegin, edgesEnd] = out_edges(current, g); return ranges::subrange{edgesBegin, edgesEnd} - | ranges::views::transform([&](const auto& e) { return edge_type{target(e, g), 1.}; }); + | ranges::views::transform([&](const auto& e) { return edge_type{target(e, g), 1.}; }); } }; diff --git a/include/Simple-Utility/graph/mixins/graph/std_reference_wrapper.hpp b/include/Simple-Utility/graph/mixins/graph/std_reference_wrapper.hpp new file mode 100644 index 000000000..905076e99 --- /dev/null +++ b/include/Simple-Utility/graph/mixins/graph/std_reference_wrapper.hpp @@ -0,0 +1,37 @@ +// Copyright Dominic Koepke 2019 - 2023. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SIMPLE_UTILITY_GRAPH_MIXINS_GRAPH_STD_REFERENCE_WRAPPER_HPP +#define SIMPLE_UTILITY_GRAPH_MIXINS_GRAPH_STD_REFERENCE_WRAPPER_HPP + +#pragma once + +#include + +#include "Simple-Utility/graph/Graph.hpp" + +template + requires sl::graph::concepts::basic_graph> +struct sl::graph::graph::traits> +{ + using edge_type = edge_t>; + using vertex_type = vertex_t>; +}; + +template + requires sl::graph::concepts::basic_graph> +struct sl::graph::customize::out_edges_fn> +{ + using edge_type = graph::edge_t>; + using vertex_type = graph::vertex_t>; + + [[nodiscard]] + constexpr auto operator ()(const Graph& graph, const vertex_type& current) const + { + return graph::out_edges(graph, current); + } +}; + +#endif diff --git a/tests/graph/Graph.cpp b/tests/graph/Graph.cpp index 0b5014747..123408ab3 100644 --- a/tests/graph/Graph.cpp +++ b/tests/graph/Graph.cpp @@ -4,6 +4,7 @@ // https://www.boost.org/LICENSE_1_0.txt) #include "Simple-Utility/graph/Graph.hpp" +#include "Simple-Utility/graph/mixins/graph/std_reference_wrapper.hpp" #include #include @@ -58,7 +59,9 @@ TEMPLATE_TEST_CASE_SIG( ((bool dummy, class Expected, class Graph), dummy, Expected, Graph), (true, GenericBasicEdge, BasicGraphMock), (true, GenericBasicEdge, BasicGraphMock), - (true, GenericWeightedEdge, WeightedGraphMock) + (true, GenericWeightedEdge, WeightedGraphMock), + (true, GenericBasicEdge, std::reference_wrapper>), + (true, GenericBasicEdge, std::reference_wrapper>) ) { STATIC_REQUIRE(std::same_as::edge_type>); @@ -71,7 +74,9 @@ TEMPLATE_TEST_CASE_SIG( ((bool dummy, class Expected, class Graph), dummy, Expected, Graph), (true, int, BasicGraphMock), (true, std::string, BasicGraphMock), - (true, std::string, WeightedGraphMock) + (true, std::string, WeightedGraphMock), + (true, int, std::reference_wrapper>), + (true, int, std::reference_wrapper>) ) { STATIC_REQUIRE(std::same_as::vertex_type>); @@ -86,7 +91,9 @@ TEMPLATE_TEST_CASE_SIG( (true, WeightedGraphMock), (true, EmptyGraphStub), (true, BasicGraphStub), - (true, WeightedGraphStub) + (true, WeightedGraphStub), + (true, std::reference_wrapper>), + (true, std::reference_wrapper>) ) { STATIC_REQUIRE(expected == sg::concepts::basic_graph); @@ -94,7 +101,8 @@ TEMPLATE_TEST_CASE_SIG( TEST_CASE( "graph::graph::out_edges serves as a customization point, returning the outgoing edges of the given vertex.", - "[graph][graph::graph]") + "[graph][graph::graph]" +) { const std::string vertex{"Hello, World!"}; const std::vector> expected{ @@ -127,3 +135,24 @@ TEST_CASE( REQUIRE_THAT(sg::graph::out_edges(mock, vertex), Catch::Matchers::RangeEquals(expected)); } } + +TEST_CASE( + "std::reference_wrapper forwards calls to graph::out_edges to the actual graph object.", + "[graph][graph::graph]" +) +{ + const std::vector>> expected{{41}, {43}, {44}}; + BasicGraphMock graph{}; + REQUIRE_CALL(graph, out_edges(42)) + .RETURN(expected); + + SECTION("as std::ref") + { + REQUIRE_THAT(sg::graph::out_edges(std::ref(graph), 42), Catch::Matchers::RangeEquals(expected)); + } + + SECTION("as std::cref") + { + REQUIRE_THAT(sg::graph::out_edges(std::cref(graph), 42), Catch::Matchers::RangeEquals(expected)); + } +} From 012ebcb18b66147f2b63a76cf88c289e703bcb46 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 12 Oct 2023 21:31:40 +0200 Subject: [PATCH 240/256] fix: add missing includes --- include/Simple-Utility/concepts/stl_extensions.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/Simple-Utility/concepts/stl_extensions.hpp b/include/Simple-Utility/concepts/stl_extensions.hpp index 7cf0d0f10..90becb299 100644 --- a/include/Simple-Utility/concepts/stl_extensions.hpp +++ b/include/Simple-Utility/concepts/stl_extensions.hpp @@ -12,6 +12,7 @@ #include #include +#include // ReSharper disable CppClangTidyClangDiagnosticDocumentation // ReSharper disable CppIdenticalOperandsInBinaryExpression @@ -285,6 +286,7 @@ namespace sl::concepts #ifdef SL_UTILITY_HAS_STD_FORMAT #include +#include namespace sl::concepts { From 13099d0f32e7a641b7998e7ede411e8792978419 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 12 Oct 2023 21:54:32 +0200 Subject: [PATCH 241/256] fix: add missing version include --- include/Simple-Utility/Config.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Simple-Utility/Config.hpp b/include/Simple-Utility/Config.hpp index 01b33b0ba..a26f88b81 100644 --- a/include/Simple-Utility/Config.hpp +++ b/include/Simple-Utility/Config.hpp @@ -30,6 +30,7 @@ "." SL_UTILITY_XSTR(SL_UTILITY_VERSION_MINOR) \ "." SL_UTILITY_XSTR(SL_UTILITY_VERSION_PATCH) +#include #if defined(_MSC_VER) || (__cpp_lib_format >= 201907L) #define SL_UTILITY_HAS_STD_FORMAT From d61c4546fe941bcc17412bee36da4e0a9509859a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 12 Oct 2023 21:54:56 +0200 Subject: [PATCH 242/256] refactor: restructure Catch2Ext tests --- tests/test_util/Catch2Ext.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_util/Catch2Ext.cpp b/tests/test_util/Catch2Ext.cpp index 31575152c..be7a88a3b 100644 --- a/tests/test_util/Catch2Ext.cpp +++ b/tests/test_util/Catch2Ext.cpp @@ -7,6 +7,7 @@ #include #include +#include TEST_CASE("catch_ext::RangesEmpty matches empty ranges.", "[test_util][test_util::catch2]") { @@ -16,7 +17,7 @@ TEST_CASE("catch_ext::RangesEmpty matches empty ranges.", "[test_util][test_util TEST_CASE("catch_ext::RangesEmpty::describe prints a description.", "[test_util][test_util::catch2]") { - REQUIRE_THAT(catch_ext::RangesEmpty{}.describe(), !catch_ext::RangesEmpty{}); + REQUIRE_THAT(catch_ext::RangesEmpty{}.describe(), Catch::Matchers::Equals("Empty")); } #ifdef SL_UTILITY_HAS_STD_FORMAT @@ -44,7 +45,7 @@ TEST_CASE("Catch::StringMaker is extended by std::format compatible types.", "[t { STATIC_CHECK(sl::concepts::formattable); - REQUIRE("TestType: 42" == Catch::StringMaker{}.convert(TestType{42})); + REQUIRE_THAT(Catch::StringMaker{}.convert(TestType{42}), Catch::Matchers::Equals("TestType: 42")); } #endif From 1fb43c5090c68c6ce13fb72cb58420d8d8bab3c1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 12 Oct 2023 22:26:03 +0200 Subject: [PATCH 243/256] fix: SL_UTILITY_HAS_RANGES_VIEWS macro for clang --- include/Simple-Utility/Config.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/Config.hpp b/include/Simple-Utility/Config.hpp index a26f88b81..82885bd1a 100644 --- a/include/Simple-Utility/Config.hpp +++ b/include/Simple-Utility/Config.hpp @@ -37,7 +37,9 @@ #endif #if defined(_MSC_VER) || (__cpp_lib_ranges >= 202110L) - #define SL_UTILITY_HAS_RANGES_VIEWS + #if not defined(__clang__) || __clang_major__ >= 16L + #define SL_UTILITY_HAS_RANGES_VIEWS + #endif #endif #endif From f399d6ce4341f2b1356fa53b5ef8c943d0ec5197 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 15 Oct 2023 15:48:59 +0200 Subject: [PATCH 244/256] docs: add documentation for graph::customize --- include/Simple-Utility/graph/Common.hpp | 28 ++++++++++++++++++++++++ tests/graph/Edge.cpp | 29 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 5fd73f37d..9e96ce68f 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -86,7 +86,35 @@ * * A traverser can be decorated with ``IterableTraverser``, which in fact provides ``begin`` and ``end`` members, and can then be used as a input-range with any existing * algorithms. + * + * ## Customization points + * As already pointed out, the library does utilize concepts very much. But to be fully exchangeable, the library introduces another layer of indirection in the + * sub-namespace ``graph:customize``. All templates in this namespace may be specialized for user types and any such specialization will be prioritized by the + * library. + */ + +// ReSharper disable CppDoxygenUnresolvedReference +/** + * \defgroup GROUP_GRAPH_CUSTOMIZATION_POINT customization points + * \ingroup GROUP_GRAPH + * + * \brief Customization points offered by the library. + * \details The library offers several customization points to the users, which may be used to make custom or third-party types interface compliant. + * In fact a special implementation strategy is used, so that each customization point favors the user defined customization point specialization in cases + * where an appropriate alternative would exist. All what has to be done is, to introduce a template specialization for the desired entry point. The specialization + * must adhere to the expectations of the customization point and should not alter observable behavior in any way. + * + * ## Example + * For example, given the type ``ThirdPartyEdge`` which shall be utilized as an edge type for this library. + * \snippet tests/graph/Edge.cpp ThirdPartyEdge definition + * + * As you can see, it offers a ``vertex`` member function but the library expects a ``destination`` member function instead. Given its a type of a third party you + * can not simply alter the function name, nor its wise to add a free function into a third party namespace. Fortunately the library offers a third option: The + * customization points. In this case its ``sl::graph::customize::destination_fn``, which one can specialize here. + * \snippet tests/graph/Edge.cpp ThirdPartyEdge destination_fn specialization + * And that's it,``ThirdPartyEdge`` can now be used as an edge type in this library. */ +// ReSharper restore CppDoxygenUnresolvedReference namespace sl::graph::concepts { diff --git a/tests/graph/Edge.cpp b/tests/graph/Edge.cpp index 3f84435da..e6aabd603 100644 --- a/tests/graph/Edge.cpp +++ b/tests/graph/Edge.cpp @@ -87,6 +87,34 @@ struct sg::customize::weight_fn } }; +//! [ThirdPartyEdge definition] +class ThirdPartyEdge +{ +public: + using vertex_type = int; + + vertex_type vertex() const + { + return m_Vertex; + } + +private: + vertex_type m_Vertex{}; +}; +//! [ThirdPartyEdge definition] + +//! [ThirdPartyEdge destination_fn specialization] +template <> +struct sl::graph::customize::destination_fn +{ + using vertex_type = typename ThirdPartyEdge::vertex_type; + vertex_type operator ()(const ThirdPartyEdge& edge) const + { + return edge.vertex(); + } +}; +//! [ThirdPartyEdge destination_fn specialization] + TEST_CASE("graph::edge::destination serves as a customization point accessing the destination.", "[graph][detail]") { const int expected = GENERATE(take(5, random(0, std::numeric_limits::max()))); @@ -188,6 +216,7 @@ TEMPLATE_TEST_CASE_SIG( (false, member_fun_weight), (false, free_fun_weight), (false, custom_fun_weight), + (true, ThirdPartyEdge), (true, GenericBasicEdge), (true, GenericWeightedEdge), (true, sg::CommonBasicEdge), From 8269cac2fe74cd888678cfae0fc726bf5513c219 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 15 Oct 2023 15:49:27 +0200 Subject: [PATCH 245/256] fix: make LazyExplorer the default, when possible --- include/Simple-Utility/graph/Traverse.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Traverse.hpp b/include/Simple-Utility/graph/Traverse.hpp index 53696ef41..6c40390c3 100644 --- a/include/Simple-Utility/graph/Traverse.hpp +++ b/include/Simple-Utility/graph/Traverse.hpp @@ -198,7 +198,7 @@ namespace sl::graph::detail using LazyExplorer = BasicExplorer; template - using default_explorer_t = BufferedExplorer; + using default_explorer_t = LazyExplorer; #else template using default_explorer_t = BufferedExplorer; From f82851cb27e9c6498ea2c5f3672acbeaa4b81b47 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 15 Oct 2023 15:49:50 +0200 Subject: [PATCH 246/256] docs: add documentation for graph/Graph.hpp --- include/Simple-Utility/graph/Graph.hpp | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/include/Simple-Utility/graph/Graph.hpp b/include/Simple-Utility/graph/Graph.hpp index d6b6251bf..ef2349769 100644 --- a/include/Simple-Utility/graph/Graph.hpp +++ b/include/Simple-Utility/graph/Graph.hpp @@ -18,15 +18,37 @@ namespace sl::graph::graph { + /** + * \defgroup GROUP_GRAPH_GRAPH graph + * \ingroup GROUP_GRAPH + * \brief Contains graph related definitions. + *\{ + */ + + /** + * \brief Primary template is purposely undefined. + */ template struct traits; + /** + * \brief Convenience alias, exposing the ``type`` member alias of the \ref sl::graph::graph::traits "traits" type. + * \tparam T Type to retrieve the info for. + */ template using edge_t = typename traits::edge_type; + /** + * \brief Convenience alias, exposing the ``type`` member alias of the \ref sl::graph::graph::traits "traits" type. + * \tparam T Type to retrieve the info for. + */ template using vertex_t = typename traits::vertex_type; + /** + * \brief General trait specialization for graphs, which have both, a valid ``vertex_type`` and ``edge_type`` member alias. + * \tparam T + */ template requires concepts::readable_vertex_type && requires { requires concepts::edge; } @@ -35,10 +57,18 @@ namespace sl::graph::graph using vertex_type = typename T::vertex_type; using edge_type = typename T::edge_type; }; + + /** + * \} + */ } namespace sl::graph::customize { + /** + * \brief Primary template for the ``out_edges`` customization point. Is purposely undefined. + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT_OUT_EDGES + */ template struct out_edges_fn; } @@ -124,11 +154,40 @@ namespace sl::graph::detail namespace sl::graph::graph { + /** + * \defgroup GROUP_GRAPH_CUSTOMIZATION_POINT_OUT_EDGES out_edges + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT + * \ingroup GROUP_GRAPH_GRAPH + * \brief Queries the outgoing edges of a specific vertex from a graph. + * \details This function internally dispatches the call in regards of the following priority list: + * - ``graph::customize::out_edges_fn`` specialization + * - ``out_edges`` member function + * - ``out_edges`` free function (with ADL enabled) + * + * Specialized ``out_edges_fn`` should offer an ``operator ()`` definition matching the following signature: + * \code{.cpp} + * input_edge_view operator ()(const Graph&, const sl::graph::graph::vertex_t&) const; + * \endcode + * ``input_edge_view`` may be any type satisfying the ``std::ranges::input_range`` concept and having a ``reference_type`` convertible to + * the ``sl::graph::graph::edge_t`` type. ``Graph`` itself is the user type, for which the entry point is specialized for. + *\{ + */ + + /** + * \brief Customization point, querying the outgoing edges of a specific vertex from a graph. + */ inline constexpr detail::out_edges_fn out_edges{}; + + /** + * \} + */ } namespace sl::graph::concepts { + /** + * \brief Determines, whether the given type satisfies the requirements of a graph type. + */ template concept basic_graph = sl::concepts::unqualified && std::destructible From fceff9c478ce70daf3fab817f979ea6de9f8b73e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 16 Oct 2023 13:03:11 +0200 Subject: [PATCH 247/256] docs: extend graph sub-section documentation --- include/Simple-Utility/graph/Common.hpp | 3 ++- include/Simple-Utility/graph/Graph.hpp | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index 9e96ce68f..b16480224 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -19,7 +19,7 @@ * \brief This library offers various graph related algorithms. * \details * # Design philosophy - * This library is built completely on concepts, thus almost anything is exchangeable by users. Some parts may be worth the effort, some may not. + * This library is built completely on concepts, thus almost everything is exchangeable by users. Some parts may be worth the effort, some may not. * Symbols in ``detail`` namespace can be used, but are less secure and may require some more insights into the internals to be used correctly. * * The (as far as I'm aware of) unique core idea of this library is to *somehow* sort the vertices of an arbitrary graph as sequential range. The actual @@ -49,6 +49,7 @@ * ### Graph * A graph consists of multiple elements (vertices) and multiple edges. Most of the algorithms allow graphs to be dynamically explored during the traversal, * thus they can be generated on the fly. + * For more info see \ref GROUP_GRAPH_GRAPH graph sub-section. * * ### Vertex * A vertex denotes a unique graph element and is used by algorithms to query the graph for further information (e.g. neighboring vertices). diff --git a/include/Simple-Utility/graph/Graph.hpp b/include/Simple-Utility/graph/Graph.hpp index ef2349769..a6a21a1b5 100644 --- a/include/Simple-Utility/graph/Graph.hpp +++ b/include/Simple-Utility/graph/Graph.hpp @@ -22,6 +22,13 @@ namespace sl::graph::graph * \defgroup GROUP_GRAPH_GRAPH graph * \ingroup GROUP_GRAPH * \brief Contains graph related definitions. + * \details A graph is a compilation of vertices and edges. Vertices are uniquely identifiable elements, which are connected via edges. + * A minimal graph can be queried for outgoing edges of a specific vertex, one at a time. Thus, the graph doesn't have to be know from + * the get go; it can be generated during algorithm runs. + * + * Algorithms expect graphs by value, but its generally enough to provide a shallow graph (aka view) to the library, which just references + * an actual graph object (e.g. a graph wrapped as ``std::reference_wrapper`` is fine). Thus, this library sometimes refers to graphs as + * ``view``. *\{ */ From 07f33e2bdd0e68d84cfa4ae1694ba08910218a33 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 16 Oct 2023 13:24:17 +0200 Subject: [PATCH 248/256] docs: add graph::edge documentation --- include/Simple-Utility/graph/Common.hpp | 9 +- include/Simple-Utility/graph/Edge.hpp | 168 ++++++++++++++++++++---- include/Simple-Utility/graph/Graph.hpp | 4 +- 3 files changed, 151 insertions(+), 30 deletions(-) diff --git a/include/Simple-Utility/graph/Common.hpp b/include/Simple-Utility/graph/Common.hpp index b16480224..ef2bb7420 100644 --- a/include/Simple-Utility/graph/Common.hpp +++ b/include/Simple-Utility/graph/Common.hpp @@ -110,13 +110,20 @@ * \snippet tests/graph/Edge.cpp ThirdPartyEdge definition * * As you can see, it offers a ``vertex`` member function but the library expects a ``destination`` member function instead. Given its a type of a third party you - * can not simply alter the function name, nor its wise to add a free function into a third party namespace. Fortunately the library offers a third option: The + * can not simply alter the function name, nor is it wise to add a free function into a third party namespace. Fortunately the library offers a third option: The * customization points. In this case its ``sl::graph::customize::destination_fn``, which one can specialize here. * \snippet tests/graph/Edge.cpp ThirdPartyEdge destination_fn specialization * And that's it,``ThirdPartyEdge`` can now be used as an edge type in this library. */ // ReSharper restore CppDoxygenUnresolvedReference +/** + * \defgroup GROUP_GRAPH_COMMON_TYPES common types + * \ingroup GROUP_GRAPH + * \brief Contains common types, which can be utilized for algorithms. + * \details The contained types are just default types, which can be utilized for algorithms, but may also be easily exchanged with user types. + */ + namespace sl::graph::concepts { /** diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index 4944373de..bfaf7c4aa 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -12,11 +12,80 @@ #include "Simple-Utility/concepts/stl_extensions.hpp" #include "Simple-Utility/graph/Common.hpp" +namespace sl::graph::edge +{ + /** + * \defgroup GROUP_GRAPH_EDGE edge + * \ingroup GROUP_GRAPH + * \brief Contains edge related definitions. + * \details An edge connects two vertices inside a graph. A minimal edge contains a ``destination`` vertex; a weighted edge contains also a + * ``weight`` property. + * + * \{ + */ + + /** + * \brief Primary template is purposely undefined. + */ + template + struct traits; + + /** + * \brief Convenience alias, exposing the ``vertex_type`` member alias of the \ref sl::graph::edge::traits "traits" type. + * \tparam Edge Type to retrieve the info for. + */ + template + using vertex_t = typename traits::vertex_type; + + /** + * \brief Convenience alias, exposing the ``weight_type`` member alias of the \ref sl::graph::edge::traits "traits" type. + * \tparam Edge Type to retrieve the info for. + */ + template + using weight_t = typename traits::weight_type; + + /** + * \brief General trait specialization for graphs, which contains a valid ``vertex_type`` member alias. + * \tparam T + */ + template + requires concepts::readable_vertex_type + struct traits + { + using vertex_type = typename T::vertex_type; + }; + + /** + * \brief General trait specialization for graphs, which contains both, a valid ``vertex_type`` and ``weight_type`` member alias. + * \tparam T + */ + template + requires concepts::readable_vertex_type + && concepts::readable_weight_type + struct traits + { + using vertex_type = typename T::vertex_type; + using weight_type = typename T::weight_type; + }; + + /** + * \} + */ +} + namespace sl::graph::customize { + /** + * \brief Primary template for the ``weight`` customization point. Is purposely undefined. + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT_WEIGHT + */ template struct weight_fn; + /** + * \brief Primary template for the ``destination`` customization point. Is purposely undefined. + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT_DESTINATION + */ template struct destination_fn; } @@ -135,27 +204,61 @@ namespace sl::graph::edge::detail namespace sl::graph::edge { + /** + * \defgroup GROUP_GRAPH_CUSTOMIZATION_POINT_DESTINATION destination + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT + * \ingroup GROUP_GRAPH_EDGE + * \brief Queries the edge for its destination vertex. + * \details This function internally dispatches the call in regards of the following priority list: + * - ``graph::customize::destination_fn`` specialization + * - ``destination`` member variable + * - ``destination`` member function + * - ``destination`` free function (with ADL enabled) + * + * Specialized ``destination_fn`` should offer an ``operator ()`` definition matching the following signature: + * \code{.cpp} + * sl::graph::edge::vertex_t operator ()(const Edge&) const; + * \endcode + * ``Edge`` itself is the user type, for which the entry point is specialized for. + *\{ + */ + + /** + * \brief Customization point, retrieving the destination vertex of a the given edge. + */ inline constexpr detail::destination_fn destination{}; - inline constexpr detail::weight_fn weight{}; - template - struct traits; + /** + * \} + */ - template - requires concepts::readable_vertex_type - struct traits - { - using vertex_type = typename T::vertex_type; - }; + /** + * \defgroup GROUP_GRAPH_CUSTOMIZATION_POINT_WEIGHT weight + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT + * \ingroup GROUP_GRAPH_EDGE + * \brief Queries the edge for its weight. + * \details This function internally dispatches the call in regards of the following priority list: + * - ``graph::customize::weight_fn`` specialization + * - ``weight`` member variable + * - ``weight`` member function + * - ``weight`` free function (with ADL enabled) + * + * Specialized ``weight_fn`` should offer an ``operator ()`` definition matching the following signature: + * \code{.cpp} + * sl::graph::edge::weight_t operator ()(const Edge&) const; + * \endcode + * ``Edge`` itself is the user type, for which the entry point is specialized for. + *\{ + */ - template - requires concepts::readable_vertex_type - && concepts::readable_weight_type - struct traits - { - using vertex_type = typename T::vertex_type; - using weight_type = typename T::weight_type; - }; + /** + * \brief Customization point, retrieving the weight of a the given edge. + */ + inline constexpr detail::weight_fn weight{}; + + /** + * \} + */ } namespace sl::graph::concepts @@ -183,17 +286,18 @@ namespace sl::graph::concepts }; } -namespace sl::graph::edge -{ - template - using vertex_t = typename traits::vertex_type; - - template - using weight_t = typename traits::weight_type; -} - namespace sl::graph { + /** + * \addtogroup GROUP_GRAPH_COMMON_TYPES + * \{ + */ + + /** + * \brief A basic edge type. + * \tparam Vertex The used vertex type. + * \note This type is also equality comparable, which is not an actual requirement for basic edge types. + */ template struct CommonBasicEdge { @@ -202,9 +306,15 @@ namespace sl::graph vertex_type destination; [[nodiscard]] - friend bool operator==(const CommonBasicEdge&, const CommonBasicEdge&) = default; + friend bool operator ==(const CommonBasicEdge&, const CommonBasicEdge&) = default; }; + /** + * \brief A weighted edge type. + * \tparam Vertex The used vertex type. + * \tparam Weight The used weight type. + * \note This type is also equality comparable, which is not an actual requirement for weighted edge types. + */ template struct CommonWeightedEdge { @@ -217,6 +327,10 @@ namespace sl::graph [[nodiscard]] friend bool operator==(const CommonWeightedEdge&, const CommonWeightedEdge&) = default; }; + + /** + * \} + */ } #endif diff --git a/include/Simple-Utility/graph/Graph.hpp b/include/Simple-Utility/graph/Graph.hpp index a6a21a1b5..c2b49a6fc 100644 --- a/include/Simple-Utility/graph/Graph.hpp +++ b/include/Simple-Utility/graph/Graph.hpp @@ -39,14 +39,14 @@ namespace sl::graph::graph struct traits; /** - * \brief Convenience alias, exposing the ``type`` member alias of the \ref sl::graph::graph::traits "traits" type. + * \brief Convenience alias, exposing the ``edge_type`` member alias of the \ref sl::graph::graph::traits "traits" type. * \tparam T Type to retrieve the info for. */ template using edge_t = typename traits::edge_type; /** - * \brief Convenience alias, exposing the ``type`` member alias of the \ref sl::graph::graph::traits "traits" type. + * \brief Convenience alias, exposing the ``vertex_type`` member alias of the \ref sl::graph::graph::traits "traits" type. * \tparam T Type to retrieve the info for. */ template From 03658d1243f61f869f32fc5cba45a2b0652247c7 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 11 Nov 2023 10:54:13 +0100 Subject: [PATCH 249/256] refactor: rename Range typedefs to Stream --- benchmarks/graph/AStarBoostComparison.cpp | 6 ++-- include/Simple-Utility/graph/AStarSearch.hpp | 2 +- .../graph/BreadthFirstSearch.hpp | 2 +- .../Simple-Utility/graph/DepthFirstSearch.hpp | 2 +- .../graph/UniformCostSearch.hpp | 2 +- tests/graph/AStarSearch.cpp | 32 +++++++++---------- tests/graph/BreadthFirstSearch.cpp | 32 +++++++++---------- tests/graph/DepthFirstSearch.cpp | 32 +++++++++---------- tests/graph/UniformCostSearch.cpp | 32 +++++++++---------- 9 files changed, 71 insertions(+), 71 deletions(-) diff --git a/benchmarks/graph/AStarBoostComparison.cpp b/benchmarks/graph/AStarBoostComparison.cpp index eb5a3ebc1..e2be92395 100644 --- a/benchmarks/graph/AStarBoostComparison.cpp +++ b/benchmarks/graph/AStarBoostComparison.cpp @@ -295,13 +295,13 @@ std::optional> sl_graph_solve(const maze& m) namespace sg = sl::graph; using Node = sg::decorator::PredecessorNode>; - using Range = sg::astar::Range< + using Stream = sg::astar::Stream< std::reference_wrapper, euclidean_heuristic, Node, sg::tracker::CommonHashMap>; - Range range{ + Stream stream{ m.source(), std::make_tuple(std::ref(m)), std::tuple{}, @@ -310,7 +310,7 @@ std::optional> sl_graph_solve(const maze& m) }; std::unordered_map nodes{}; - for (const auto& node : range) + for (const auto& node : stream) { nodes.emplace(node.vertex, node); if (node.vertex == m.goal()) diff --git a/include/Simple-Utility/graph/AStarSearch.hpp b/include/Simple-Utility/graph/AStarSearch.hpp index e9a4cbd88..85f3f2bfb 100644 --- a/include/Simple-Utility/graph/AStarSearch.hpp +++ b/include/Simple-Utility/graph/AStarSearch.hpp @@ -170,7 +170,7 @@ namespace sl::graph::astar concepts::ranked_node Node = CommonNode>, edge::weight_t>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires concepts::heuristic_for - using Range = IterableTraverser< + using Stream = IterableTraverser< sl::graph::detail::BasicTraverser< Node, Graph, diff --git a/include/Simple-Utility/graph/BreadthFirstSearch.hpp b/include/Simple-Utility/graph/BreadthFirstSearch.hpp index 73e1b8bdd..fc2c52bd4 100644 --- a/include/Simple-Utility/graph/BreadthFirstSearch.hpp +++ b/include/Simple-Utility/graph/BreadthFirstSearch.hpp @@ -28,7 +28,7 @@ namespace sl::graph::dfs concepts::basic_node Node = CommonNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires (!concepts::ranked_node) - using Range = IterableTraverser< + using Stream = IterableTraverser< detail::BasicTraverser< Node, Graph, diff --git a/include/Simple-Utility/graph/DepthFirstSearch.hpp b/include/Simple-Utility/graph/DepthFirstSearch.hpp index 0fc216bf1..a16ec7ddf 100644 --- a/include/Simple-Utility/graph/DepthFirstSearch.hpp +++ b/include/Simple-Utility/graph/DepthFirstSearch.hpp @@ -28,7 +28,7 @@ namespace sl::graph::dfs concepts::basic_node Node = CommonNode>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> requires (!concepts::ranked_node) - using Range = IterableTraverser< + using Stream = IterableTraverser< detail::BasicTraverser< Node, Graph, diff --git a/include/Simple-Utility/graph/UniformCostSearch.hpp b/include/Simple-Utility/graph/UniformCostSearch.hpp index d10bc22d6..4523fe1bb 100644 --- a/include/Simple-Utility/graph/UniformCostSearch.hpp +++ b/include/Simple-Utility/graph/UniformCostSearch.hpp @@ -27,7 +27,7 @@ namespace sl::graph::ucs concepts::basic_graph Graph, concepts::ranked_node Node = CommonNode>, edge::weight_t>>, concepts::tracker_for> Tracker = tracker::CommonHashMap>> - using Range = IterableTraverser< + using Stream = IterableTraverser< detail::BasicTraverser< Node, Graph, diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index a5268be73..2af541ef9 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -86,27 +86,27 @@ namespace } TEMPLATE_TEST_CASE( - "astar::Range visits all reachable vertices.", + "astar::Stream visits all reachable vertices.", "[graph][graph::astar]", WeightedGraphStub) { using Node = sg::astar::CommonNode; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toCommonAStarNode))); - sg::astar::Range range{ + sg::astar::Stream stream{ origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{sg::astar::NodeFactory{Heuristic{destination}}} }; - STATIC_CHECK(std::ranges::input_range); + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::UnorderedRangeEquals(expected)); } TEMPLATE_TEST_CASE( - "astar::Range node can be decorated with DepthNodeDecorator.", + "astar::Stream node can be decorated with DepthNodeDecorator.", "[graph][graph::astar]", WeightedGraphStub ) @@ -114,20 +114,20 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toDepthAStarNode))); - sg::astar::Range range{ + sg::astar::Stream stream{ origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{sg::astar::NodeFactory{Heuristic{destination}}} }; - STATIC_CHECK(std::ranges::input_range); + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::UnorderedRangeEquals(expected)); } TEMPLATE_TEST_CASE( - "astar::Range node can be decorated with PredecessorNodeDecorator.", + "astar::Stream node can be decorated with PredecessorNodeDecorator.", "[graph][graph::astar]", WeightedGraphStub ) @@ -135,20 +135,20 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin, destination] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorAStarNode))); - sg::astar::Range range{ + sg::astar::Stream stream{ origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{sg::astar::NodeFactory{Heuristic{destination}}} }; - STATIC_CHECK(std::ranges::input_range); + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::UnorderedRangeEquals(expected)); } TEMPLATE_TEST_CASE( - "astar::Range can be used with arbitrary decorated nodes.", + "astar::Stream can be used with arbitrary decorated nodes.", "[graph][graph::astar]", WeightedGraphStub ) @@ -156,14 +156,14 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>>; const auto& [expected, origin, destination] = GENERATE(from_range(testResults)); - sg::astar::Range range{ + sg::astar::Stream stream{ origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{sg::astar::NodeFactory{Heuristic{destination}}} }; - STATIC_CHECK(std::ranges::input_range); + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::UnorderedRangeEquals(expected)); } diff --git a/tests/graph/BreadthFirstSearch.cpp b/tests/graph/BreadthFirstSearch.cpp index ac6368147..a96ce6864 100644 --- a/tests/graph/BreadthFirstSearch.cpp +++ b/tests/graph/BreadthFirstSearch.cpp @@ -43,7 +43,7 @@ namespace } TEMPLATE_TEST_CASE( - "bfs::Range visits all reachable vertices.", + "bfs::Stream visits all reachable vertices.", "[graph][graph::bfs]", BasicGraphStub, WeightedGraphStub @@ -52,14 +52,14 @@ TEMPLATE_TEST_CASE( using Node = sg::CommonBasicNode; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::RangeEquals(expected)); } TEMPLATE_TEST_CASE( - "bfs::Range can be used with depth decorated nodes.", + "bfs::Stream can be used with depth decorated nodes.", "[graph][graph::bfs]", BasicGraphStub, WeightedGraphStub @@ -68,14 +68,14 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::RangeEquals(expected)); } TEMPLATE_TEST_CASE( - "bfs::Range can be used with predecessor decorated nodes.", + "bfs::Stream can be used with predecessor decorated nodes.", "[graph][graph::bfs]", BasicGraphStub, WeightedGraphStub @@ -84,14 +84,14 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::RangeEquals(expected)); } TEMPLATE_TEST_CASE( - "bfs::Range can be used with arbitrary decorated nodes.", + "bfs::Stream can be used with arbitrary decorated nodes.", "[graph][graph::bfs]", BasicGraphStub, WeightedGraphStub @@ -100,8 +100,8 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::RangeEquals(expected)); } diff --git a/tests/graph/DepthFirstSearch.cpp b/tests/graph/DepthFirstSearch.cpp index 12602adfa..a19a0edc8 100644 --- a/tests/graph/DepthFirstSearch.cpp +++ b/tests/graph/DepthFirstSearch.cpp @@ -43,7 +43,7 @@ namespace } TEMPLATE_TEST_CASE( - "dfs::Range visits all reachable vertices.", + "dfs::Stream visits all reachable vertices.", "[graph][graph::dfs]", BasicGraphStub, WeightedGraphStub @@ -52,14 +52,14 @@ TEMPLATE_TEST_CASE( using Node = sg::CommonBasicNode; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::RangeEquals(expected)); } TEMPLATE_TEST_CASE( - "dfs::Range can be used with depth decorated nodes.", + "dfs::Stream can be used with depth decorated nodes.", "[graph][graph::dfs]", BasicGraphStub, WeightedGraphStub @@ -68,14 +68,14 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::RangeEquals(expected)); } TEMPLATE_TEST_CASE( - "dfs::Range can be used with predecessor decorated nodes.", + "dfs::Stream can be used with predecessor decorated nodes.", "[graph][graph::dfs]", BasicGraphStub, WeightedGraphStub @@ -84,14 +84,14 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorBasicNode))); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::RangeEquals(expected)); } TEMPLATE_TEST_CASE( - "dfs::Range can be used with arbitrary decorated nodes.", + "dfs::Stream can be used with arbitrary decorated nodes.", "[graph][graph::dfs]", BasicGraphStub, WeightedGraphStub @@ -100,8 +100,8 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); - sg::dfs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::dfs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::RangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::RangeEquals(expected)); } diff --git a/tests/graph/UniformCostSearch.cpp b/tests/graph/UniformCostSearch.cpp index beeb572f9..b5f73ec4f 100644 --- a/tests/graph/UniformCostSearch.cpp +++ b/tests/graph/UniformCostSearch.cpp @@ -44,7 +44,7 @@ namespace } TEMPLATE_TEST_CASE( - "ucs::Range visits all reachable vertices.", + "ucs::Stream visits all reachable vertices.", "[graph][graph::ucs]", WeightedGraphStub ) @@ -52,14 +52,14 @@ TEMPLATE_TEST_CASE( using Node = sg::CommonRankedNode; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toCommonRankedNode))); - sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::ucs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::UnorderedRangeEquals(expected)); } TEMPLATE_TEST_CASE( - "ucs::Range node can be decorated with DepthNode.", + "ucs::Stream node can be decorated with DepthNode.", "[graph][graph::ucs]", WeightedGraphStub ) @@ -67,14 +67,14 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toDepthRankedNode))); - sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::ucs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::UnorderedRangeEquals(expected)); } TEMPLATE_TEST_CASE( - "ucs::Range node can be decorated with PredecessorNode.", + "ucs::Stream node can be decorated with PredecessorNode.", "[graph][graph::ucs]", WeightedGraphStub ) @@ -82,14 +82,14 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::PredecessorNode>; const auto& [expected, origin] = GENERATE(from_range(slice_test_expectations(testResults, toPredecessorRankedNode))); - sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::ucs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::UnorderedRangeEquals(expected)); } TEMPLATE_TEST_CASE( - "ucs::Range can be used with arbitrary decorated nodes.", + "ucs::Stream can be used with arbitrary decorated nodes.", "[graph][graph::ucs]", WeightedGraphStub ) @@ -97,8 +97,8 @@ TEMPLATE_TEST_CASE( using Node = sg::decorator::DepthNode>>; const auto& [expected, origin] = GENERATE(from_range(testResults)); - sg::ucs::Range range{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; - STATIC_CHECK(std::ranges::input_range); + sg::ucs::Stream stream{origin, std::tuple{TestType{}}, std::tuple{}, std::tuple{}, std::tuple{}}; + STATIC_CHECK(std::ranges::input_range); - REQUIRE_THAT(buffer_nodes(range), Catch::Matchers::UnorderedRangeEquals(expected)); + REQUIRE_THAT(buffer_nodes(stream), Catch::Matchers::UnorderedRangeEquals(expected)); } From 6beafc9aec20c74de606298766761c3caebf7330 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 11 Nov 2023 11:11:36 +0100 Subject: [PATCH 250/256] test: add astar::CommonNode test cases --- tests/graph/AStarSearch.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/graph/AStarSearch.cpp b/tests/graph/AStarSearch.cpp index 2af541ef9..6bb944798 100644 --- a/tests/graph/AStarSearch.cpp +++ b/tests/graph/AStarSearch.cpp @@ -85,6 +85,37 @@ namespace }; } +TEST_CASE("astar::CommonNode members have expected values.", "[graph][graph::astar]") +{ + using Node = sg::astar::CommonNode; + const auto vertex = GENERATE("42", "Hello, World!"); + const auto cost = GENERATE(1337, 42); + const auto estimatedPendingCost = GENERATE(1338, 41); + + const Node node{.vertex = vertex, .cost = cost, .estimatedPendingCost = estimatedPendingCost}; + + REQUIRE(node.vertex == vertex); + REQUIRE(node.cost == cost); + REQUIRE(node.estimatedPendingCost == estimatedPendingCost); +} + +TEST_CASE("astar::CommonNode is equality comparable.", "[graph][graph::astar]") +{ + using Node = sg::astar::CommonNode; + const auto& [expectedEquality, first, second] = GENERATE( + (table)({ + {true, {"42", 1337, 1338}, {"42", 1337, 1338}}, + {false, {"41", 1337, 1338}, {"42", 1337, 1338}}, + {false, {"42", 1336, 1338}, {"42", 1337, 1338}}, + {false, {"42", 1337, 1339}, {"42", 1337, 1338}} + })); + + REQUIRE(expectedEquality == (first == second)); + REQUIRE(expectedEquality == (second == first)); + REQUIRE(expectedEquality != (first != second)); + REQUIRE(expectedEquality != (second != first)); +} + TEMPLATE_TEST_CASE( "astar::Stream visits all reachable vertices.", "[graph][graph::astar]", From 7c63547f8242a83525ab8b8e589dd91f65c5d40e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 11 Nov 2023 14:28:22 +0100 Subject: [PATCH 251/256] docs: fix edge docs --- include/Simple-Utility/graph/Edge.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index bfaf7c4aa..521fedf6c 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -45,7 +45,7 @@ namespace sl::graph::edge using weight_t = typename traits::weight_type; /** - * \brief General trait specialization for graphs, which contains a valid ``vertex_type`` member alias. + * \brief General trait specialization for edges, which contain a valid ``vertex_type`` member alias. * \tparam T */ template @@ -56,7 +56,7 @@ namespace sl::graph::edge }; /** - * \brief General trait specialization for graphs, which contains both, a valid ``vertex_type`` and ``weight_type`` member alias. + * \brief General trait specialization for edges, which contain both, a valid ``vertex_type`` and ``weight_type`` member alias. * \tparam T */ template From 68e41e03cc273367a4100b2e3335264c7bda8f1b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 11 Nov 2023 15:24:03 +0100 Subject: [PATCH 252/256] docs: add missing concepts::edge and concepts::weighted_edge docs --- include/Simple-Utility/graph/Edge.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index 521fedf6c..a79c5d4a3 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -263,6 +263,10 @@ namespace sl::graph::edge namespace sl::graph::concepts { + /** + * \brief Determines, whether the given type satisfies the requirements. + * \tparam T Type to check. + */ template concept edge = sl::concepts::unqualified && std::copyable @@ -275,6 +279,10 @@ namespace sl::graph::concepts { edge::destination(edge) } -> std::convertible_to::vertex_type>; }; + /** + * \brief Determines, whether the given type satisfies the requirements. + * \tparam T Type to check. + */ template concept weighted_edge = edge && weight::weight_type> From d428e919b4c897f395ba776c9be43838a1b2f710 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 11 Nov 2023 15:24:18 +0100 Subject: [PATCH 253/256] docs: add documentation for graph/Node.hpp --- include/Simple-Utility/graph/Node.hpp | 148 ++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index c571059ac..009c18c22 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -19,9 +19,17 @@ namespace sl::graph::customize { + /** + * \brief Primary template for the ``vertex`` customization point. Is purposely undefined. + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT_VERTEX + */ template struct vertex_fn; + /** + * \brief Primary template for the ``rank`` customization point. Is purposely undefined. + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT_RANK + */ template struct rank_fn; } @@ -137,18 +145,103 @@ namespace sl::graph::detail namespace sl::graph::node { + /** + * \defgroup GROUP_GRAPH_CUSTOMIZATION_POINT_VERTEX vertex + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT + * \ingroup GROUP_GRAPH_NODE + * \brief Queries the node for its related vertex. + * \details This function internally dispatches the call in regards of the following priority list: + * - ``graph::customize::vertex_fn`` specialization + * - ``vertex`` member variable + * - ``vertex`` member function + * - ``vertex`` free function (with ADL enabled) + * + * Specialized ``vertex_fn`` should offer an ``operator ()`` definition matching the following signature: + * \code{.cpp} + * sl::graph::node::vertex_t operator ()(const Node&) const; + * \endcode + * ``Node`` itself is the user type, for which the entry point is specialized for. + *\{ + */ + + /** + * \brief Customization point, retrieving the related vertex of a the given node. + */ + inline constexpr detail::vertex_fn vertex{}; + + /** + * \} + */ + + /** + * \defgroup GROUP_GRAPH_CUSTOMIZATION_POINT_RANK rank + * \ingroup GROUP_GRAPH_CUSTOMIZATION_POINT + * \ingroup GROUP_GRAPH_NODE + * \brief Queries the node for its rank. + * \details This function internally dispatches the call in regards of the following priority list: + * - ``graph::customize::rank_fn`` specialization + * - ``rank`` member variable + * - ``rank`` member function + * - ``rank`` free function (with ADL enabled) + * + * Specialized ``rank_fn`` should offer an ``operator ()`` definition matching the following signature: + * \code{.cpp} + * sl::graph::node::rank_t operator ()(const Node&) const; + * \endcode + * ``Node`` itself is the user type, for which the entry point is specialized for. + *\{ + */ + + /** + * \brief Customization point, retrieving the rank of a the given node. + */ + inline constexpr detail::rank_fn rank{}; + /** + * \} + */ + + /** + * \defgroup GROUP_GRAPH_NODE node + * \ingroup GROUP_GRAPH + * \brief Contains node related definitions. + * \details A node represents the state of a vertex during an algorithm run. A minimal node contains its referred vertex but may also contain + * addition information. + * + * In general a node is built by a node_factory, which receives the source node and one of its edges and then builds the sub-node with the provided + * information. In fact the specified node for the algorithm determines the minimal information a graph must provide via its returned edges. + * Or more formally:
+ *
information(node) ∩ information(edge) = information(node)
+ * + * \{ + */ + + /** + * \brief Primary template is purposely undefined. + */ template struct traits; + /** + * \brief Convenience alias, exposing the ``vertex_type`` member alias of the \ref sl::graph::node::traits "traits" type. + * \tparam Node Type to retrieve the info for. + */ template using vertex_t = typename traits::vertex_type; + /** + * \brief Convenience alias, exposing the ``rank_type`` member alias of the \ref sl::graph::node::traits "traits" type. + * \tparam Node Type to retrieve the info for. + */ template using rank_t = typename traits::rank_type; + /** + * \brief General trait specialization for nodes, which contain a valid ``vertex_type`` member alias. + * \tparam T + */ template requires concepts::readable_vertex_type struct traits @@ -156,6 +249,10 @@ namespace sl::graph::node using vertex_type = typename T::vertex_type; }; + /** + * \brief General trait specialization for nodes, which contain both, a valid ``vertex_type`` and ``rank_type`` member alias. + * \tparam T + */ template requires concepts::readable_vertex_type && concepts::readable_rank_type @@ -164,10 +261,18 @@ namespace sl::graph::node using vertex_type = typename T::vertex_type; using rank_type = typename T::rank_type; }; + + /** + * \} + */ } namespace sl::graph::concepts { + /** + * \brief Determines, whether the given type satisfies the requirements. + * \tparam T Type to check. + */ template concept basic_node = sl::concepts::unqualified && std::copyable @@ -181,6 +286,10 @@ namespace sl::graph::concepts { node::vertex(node) } -> std::convertible_to>; // pleases msvc v142 }; + /** + * \brief Determines, whether the given type satisfies the requirements. + * \tparam Node Type to check. + */ template concept ranked_node = basic_node && requires { typename node::traits::rank_type; } @@ -192,6 +301,11 @@ namespace sl::graph::concepts { node::rank(node) } -> std::convertible_to>; // pleases msvc v142 }; + /** + * \brief Determines, whether the ``Edge`` type provides the minimal information required by the ``Node`` type. + * \tparam Edge Type to check. + * \tparam Node Baseline for the minimal required information. + */ template concept edge_for = basic_node && edge @@ -206,6 +320,10 @@ namespace sl::graph::concepts namespace sl::graph { + /** + * \brief Generic node type, satisfying the requirements of the ``basic_node`` concept. + * \tparam Vertex Vertex type. + */ template struct CommonBasicNode { @@ -217,6 +335,11 @@ namespace sl::graph friend bool operator==(const CommonBasicNode&, const CommonBasicNode&) = default; }; + /** + * \brief Generic node type, satisfying the requirements of the ``ranked_node`` concept. + * \tparam Vertex Vertex type. + * \tparam Rank Rank type. + */ template struct CommonRankedNode { @@ -233,6 +356,10 @@ namespace sl::graph namespace sl::graph::decorator { + /** + * \brief Node decorator, extending the provided Node type with a ``predecessor`` property. + * \tparam Node The decorated node type. + */ template struct PredecessorNode : public Node @@ -245,9 +372,21 @@ namespace sl::graph::decorator friend bool operator ==(const PredecessorNode&, const PredecessorNode&) = default; }; + /** + * \brief Primary template node factory decorator, extending the given base node-factory. Purposely undefined. + * \tparam Node The node type. + * \tparam BaseNodeFactory The base node-factory template. + * \details This complexity is necessary, so users can freely nest library-provided and user-defined + * node decorators in arbitrary depth. + */ template typename BaseNodeFactory> class NodeFactory; + /** + * \brief Node factory decorator specialization for PredecessorNode decorator. + * \tparam Node The decorated node type. + * \tparam BaseNodeFactory The base node-factory template. + */ template typename BaseNodeFactory> class NodeFactory, BaseNodeFactory> : private BaseNodeFactory @@ -286,6 +425,10 @@ namespace sl::graph::decorator } }; + /** + * \brief Node decorator, extending the provided Node type with a ``depth`` property. + * \tparam Node The decorated node type. + */ template struct DepthNode : public Node @@ -298,6 +441,11 @@ namespace sl::graph::decorator friend bool operator ==(const DepthNode&, const DepthNode&) = default; }; + /** + * \brief Node factory decorator specialization for DepthNode decorator. + * \tparam Node The decorated node type. + * \tparam BaseNodeFactory The base node-factory template. + */ template typename BaseNodeFactory> class NodeFactory, BaseNodeFactory> : private BaseNodeFactory From d5b17a73ca22cdbeb381cda0ce718071d6ba2fb0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 11 Nov 2023 15:48:06 +0100 Subject: [PATCH 254/256] docs: fix some typos --- include/Simple-Utility/graph/Edge.hpp | 4 ++-- include/Simple-Utility/graph/Node.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/graph/Edge.hpp b/include/Simple-Utility/graph/Edge.hpp index a79c5d4a3..5d6e36b47 100644 --- a/include/Simple-Utility/graph/Edge.hpp +++ b/include/Simple-Utility/graph/Edge.hpp @@ -224,7 +224,7 @@ namespace sl::graph::edge */ /** - * \brief Customization point, retrieving the destination vertex of a the given edge. + * \brief Customization point, retrieving the destination vertex of the given edge. */ inline constexpr detail::destination_fn destination{}; @@ -252,7 +252,7 @@ namespace sl::graph::edge */ /** - * \brief Customization point, retrieving the weight of a the given edge. + * \brief Customization point, retrieving the weight of the given edge. */ inline constexpr detail::weight_fn weight{}; diff --git a/include/Simple-Utility/graph/Node.hpp b/include/Simple-Utility/graph/Node.hpp index 009c18c22..8fc1aac2b 100644 --- a/include/Simple-Utility/graph/Node.hpp +++ b/include/Simple-Utility/graph/Node.hpp @@ -165,7 +165,7 @@ namespace sl::graph::node */ /** - * \brief Customization point, retrieving the related vertex of a the given node. + * \brief Customization point, retrieving the related vertex of the given node. */ inline constexpr detail::vertex_fn vertex{}; @@ -194,7 +194,7 @@ namespace sl::graph::node */ /** - * \brief Customization point, retrieving the rank of a the given node. + * \brief Customization point, retrieving the rank of the given node. */ inline constexpr detail::rank_fn rank{}; From cae391a356e3d005095a6ea9b201856e0cb181a3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 11 Nov 2023 15:56:52 +0100 Subject: [PATCH 255/256] docs: please doxgen on recursive inheritance --- include/Simple-Utility/Utility.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/Simple-Utility/Utility.hpp b/include/Simple-Utility/Utility.hpp index ebef8dc01..26cd0cc72 100644 --- a/include/Simple-Utility/Utility.hpp +++ b/include/Simple-Utility/Utility.hpp @@ -28,7 +28,9 @@ namespace sl */ template struct priority_tag + /** \cond */ : public priority_tag + /** \endcond */ { }; From e4ab927d50275ff80c707af0297f4f085731cb00 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 11 Nov 2023 15:58:08 +0100 Subject: [PATCH 256/256] docs: upgrade Doxyfile.in to new version --- docs/Doxyfile.in | 204 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 141 insertions(+), 63 deletions(-) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 2e20b2edf..44b0bd84c 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -1,4 +1,4 @@ -# Doxyfile 1.9.6 +# Doxyfile 1.9.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -363,6 +363,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 5 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -487,6 +498,14 @@ LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -872,7 +891,14 @@ WARN_IF_UNDOC_ENUM_VAL = NO # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO @@ -951,12 +977,12 @@ INPUT_FILE_ENCODING = # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, -# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C -# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -1042,9 +1068,6 @@ EXCLUDE_PATTERNS = # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = sl:*:detail @@ -1427,15 +1450,6 @@ HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1455,6 +1469,13 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1585,6 +1606,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -2073,9 +2104,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2096,14 +2134,6 @@ LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2269,7 +2299,7 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. @@ -2280,6 +2310,28 @@ GENERATE_AUTOGEN_DEF = NO # Configuration options related to Sqlite3 output #--------------------------------------------------------------------------- +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if an a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2422,15 +2474,15 @@ TAGFILES = GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2444,16 +2496,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2462,7 +2507,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. @@ -2515,13 +2560,15 @@ DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a -# graph for each documented class showing the direct and indirect inheritance -# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, -# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set -# to TEXT the direct and indirect inheritance relations will be shown as texts / -# links. -# Possible values are: NO, YES, TEXT and GRAPH. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = YES @@ -2529,15 +2576,21 @@ CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. See also the chapter Grouping -# in the manual. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2597,7 +2650,9 @@ TEMPLATE_RELATIONS = YES # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2606,7 +2661,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2646,7 +2704,10 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2662,7 +2723,7 @@ DIR_GRAPH_MAX_DEPTH = 1 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2699,11 +2760,12 @@ DOT_PATH = @DOXY_DOT_PATH@ DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2780,3 +2842,19 @@ GENERATE_LEGEND = YES # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS =