From 2bec62b88e848fd60982e24c45668b3a01e6ab57 Mon Sep 17 00:00:00 2001 From: Morwenn Date: Sun, 8 Sep 2024 21:59:03 +0200 Subject: [PATCH] Adapt docs & tests to mutable sorters (#104) --- docs/Sorter-adapters.md | 8 ++---- docs/Writing-a-sorter.md | 6 ++-- tests/utility/adapter_storage.cpp | 47 ++++--------------------------- 3 files changed, 10 insertions(+), 51 deletions(-) diff --git a/docs/Sorter-adapters.md b/docs/Sorter-adapters.md index 7eb34162..a8452feb 100644 --- a/docs/Sorter-adapters.md +++ b/docs/Sorter-adapters.md @@ -14,12 +14,11 @@ constexpr auto sort = indirect_adapter(quick_sort); ``` Most of the library's *sorter adapters* can store the passed *sorters* in their internals, allowing them to use adapt *stateful sorters*. Unless explicitly mentioned otherwise in an adapter's description, it is safe to assume that the *sorter adapters* in the library have the following properties: -* The *sorter adapter* stores a copy of every passed sorters in its internals and uses those copy when needed. If every *original sorter* is empty and default-constructible, then the *sorter adapter* is also empty and default-constructible. -* If the *sorter adapter* adapts a single *sorter*, then it has a member function called `get()` which returns a reference to the internal *sorter* whose reference and `const` qualifications match those of the *sorter adapter* instance. If the *sorter adapter* is empty and default-constructible, then a default-constructed instance of the type of the *original sorter* is returned instead. +* The *sorter adapter* stores a copy of every passed sorters in its internals and uses those copy when needed. If every *adapted sorter* is empty and default-constructible, then the *resulting sorter* is also empty and default-constructible. +* If the *sorter adapter* adapts a single *sorter*, then it has a member function called `get()` which returns a reference to the internal *sorter* whose reference and `const` qualifications match those of the *resulting sorter* instance. If the *resulting adapter* is empty and default-constructible, then a default-constructed instance of the type of the *adapted sorter* is returned instead. +* The `operator()` of the *resulting sorter* has cv-qualifications similar to those of the *adapted sorter*. This is generally achieved by taking an explicit `this` template parameter. * If the *sorter adapter* is empty and default-constructible, then it can be converted to any function pointer whose signature matches that of its `operator()`. -It is worth noting that in the current state of things, sorters & adapters are expected to have a `const operator()`, and thus don't play nice with *mutable sorters*. There are plans to properly handle *mutable sorters* in the future: you can track [the corresponding issue][issue-104]. - ## Available sorter adapters The following sorter adapters and fixed-size sorter adapters are available in the library: @@ -321,7 +320,6 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen [hybrid-adapter]: Sorter-adapters.md#hybrid_adapter [is-always-stable]: Sorter-traits.md#is_always_stable [is-stable]: Sorter-traits.md#is_stable - [issue-104]: https://github.com/Morwenn/cpp-sort/issues/104 [iterator-category]: Sorter-traits.md#iterator_category [iterator-tags]: https://en.cppreference.com/w/cpp/iterator/iterator_tags [low-moves-sorter]: Fixed-size-sorters.md#low_moves_sorter diff --git a/docs/Writing-a-sorter.md b/docs/Writing-a-sorter.md index 9a3d87e1..e7cdfe1b 100644 --- a/docs/Writing-a-sorter.md +++ b/docs/Writing-a-sorter.md @@ -61,9 +61,7 @@ Now, let's define a set of rules to apply when writing sorters. These rules don' **Rule 1.3:** a *sorter implementation* shall provide at least an overload of `operator()` that takes a pair of iterators. Overloads of `operator()` taking an iterable can be provided as well when they add value to the *sorter* (optimization, correctness, better error messages...) but should never totally replace the overload taking a pair of iterators. -**Rule 1.4:** *sorters* shall be immutable and every overload of `operator()` shall explicitly be marked `const` (make sure to check twice: forgetting to `const`-qualify them can cause hundreds of lines of cryptic error messages). Some parts of the library *may* accept mutable sorters, but that's never guaranteed unless specified otherwise. - -**Rule 1.5:** *sorter* implementers are encouraged but not required to provide a default instance of their *sorters* for convenience. `inline` variables can be used to avoid ODR-related problems. +**Rule 1.4:** *sorter* implementers are encouraged but not required to provide a default instance of their *sorters* for convenience. `inline` variables can be used to avoid ODR-related problems. ## Category of iterators @@ -406,7 +404,7 @@ The library also contains another stability-related type trait, [`is_stable`][is ## Stateful sorters -To date (version 1.5.0), every *sorter* provided by **cpp-sort** is a *stateless sorter*, which means that it is an empty type which does not carry any state between construction and subsequent invocations. However, the library is also designed to handle *stateful sorters*: those are sorters which might not be empty and which might carry a state that can change between invocations (even though the invocation itself isn't allowed to change the state as per Rule 1.4). +To date (version 2.0.0), every *sorter* provided by **cpp-sort** is a *stateless sorter*, which means that it is an empty type which does not carry any state between construction and subsequent invocations. However, the library is also designed to handle *stateful sorters*: those are sorters which might carry a state that can change between invocations, or even when calling the *sorter*. There are no specific rules to differentiate the way *stateful* and *stateless sorters* are written, but the distinction matters because specific components of the library will act differently when exposed to a *stateful* or *stateless* sorter: * `sorter_facade` only provides conversions to different kinds of function pointers when the *sorter implementation* is empty (stateless) and default-constructible. diff --git a/tests/utility/adapter_storage.cpp b/tests/utility/adapter_storage.cpp index e44cb5a8..d49ccef5 100644 --- a/tests/utility/adapter_storage.cpp +++ b/tests/utility/adapter_storage.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace { @@ -101,44 +102,6 @@ namespace cppsort::sorter_facade(a, b) {} }; - - //////////////////////////////////////////////////////////// - // Mutable sorter - - struct mutable_sorter_impl - { - int dummy1=0, dummy2=0; - - mutable_sorter_impl() = default; - constexpr mutable_sorter_impl(int a, int b): - dummy1(a), dummy2(b) - {} - - template< - typename Iterator, - typename Compare = std::less<> - > - requires (not cppsort::is_projection_iterator_v) - auto operator()(Iterator first, Iterator last, Compare compare={}) - -> void - { - dummy1 = 3; - std::sort(std::move(first), std::move(last), std::move(compare)); - dummy2 = 11; - } - - using iterator_category = std::random_access_iterator_tag; - using is_always_stable = std::false_type; - }; - - struct mutable_sorter: - cppsort::sorter_facade - { - mutable_sorter() = default; - mutable_sorter(int a, int b): - cppsort::sorter_facade(a, b) - {} - }; } TEST_CASE( "test correct adapter_storage behavior", "[adapter_storage]" ) @@ -165,12 +128,12 @@ TEST_CASE( "test correct adapter_storage behavior", "[adapter_storage]" ) SECTION( "with a mutable sorter" ) { - mutable_sorter original_sorter(5, 8); + mutable_sorter original_sorter; auto adapted_sorter = dummy_adapter(original_sorter); adapted_sorter(arr); - CHECK( std::is_sorted(std::begin(arr), std::end(arr)) ); - CHECK( adapted_sorter.get().dummy1 == 3 ); - CHECK( adapted_sorter.get().dummy2 == 11 ); + CHECK( std::is_sorted(arr.begin(), arr.end()) ); + CHECK( adapted_sorter.get().before_sort == mutable_state::modified ); + CHECK( adapted_sorter.get().after_sort == mutable_state::modified ); } }