Skip to content

Commit

Permalink
hybrid_adapter: first-class support for contiguous iterators
Browse files Browse the repository at this point in the history
Make hybrid_adapter dispatch look at the "iterator concept" of the
passed collection, which might be std::contiguous_iterator_tag.
  • Loading branch information
Morwenn committed Jan 8, 2024
1 parent e9163ac commit 0097187
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 20 deletions.
16 changes: 9 additions & 7 deletions docs/Sorter-adapters.md
Expand Up @@ -74,27 +74,27 @@ Adapting any *sorter* with `drop_merge_adapter` effectively makes it [*Rem*-adap
#include <cpp-sort/adapters/hybrid_adapter.h>
```

The goal of this sorter adapter is to aggregate several sorters into one unique sorter. The new sorter will call the appropriate sorting algorithm based on the iterator category of the collection to sort. If several of the aggregated sorters have the same iterator category, the first to appear in the template parameter list will be chosen, unless some SFINAE condition prevents it from being used. As long as the iterator categories are different, the order of the sorters in the parameter pack does not matter.
The goal of this sorter adapter is to aggregate several *adapted sorters* into a unique *resulting sorter*, which dispatches calls to the appropriate *adapted sorter* based on its iterator category and that of the collection to sort. If several of the *adapted sorters* that would be picked have the same iterator category, the first to appear in the template parameter list is chosen, unless some SFINAE condition prevents it from being picked by overload resolution. As long as the iterator categories are different, the order of the sorters in the parameter pack does not matter.

For example, the following sorter should call a pattern-defeating quicksort to sort a random-access collection, a vergesort to sort a bidirectional collection and a bubble sort to sort a forward collection:
For example, the following sorter calls a pattern-defeating quicksort to sort random-access collections, an insertion sort to sort bidirectional collections, and a bubble sort to sort forward collections:

```cpp
using general_purpose_sorter = hybrid_adapter<
bubble_sorter,
verge_sorter,
insertion_sorter,
pdq_sorter
>;
```

This adapter uses `cppsort::iterator_category` to check the iterator category of the sorters to aggregate. Therefore, if you write a sorter and want it to be usable with `hybrid_adapter`, you will need your sorter to provide an `iterator_category` type alias corresponding to one of the standard iterator tags. If you write specific sorters that only work with some specific types, you might want to SFINAE out the overloads of `operator()` when they are not valid instead of triggering a hard error. Doing so will allow to use them with fallback sorters in `hybrid_adapter` to handle the cases where the type to sort cannot be handled by your sorter.
This adapter uses [`cppsort::iterator_category`][iterator-category] to check the iterator category of the sorters to aggregate. Therefore, if you write a sorter and want it to be usable with `hybrid_adapter`, you need your sorter to provide an `iterator_category` type alias corresponding to one of the [standard iterator tags][iterator-tags]. If you write specific sorters that only work with some specific types, you might want to SFINAE out the overloads of `operator()` when they are not valid instead of triggering a hard error: doing so will allow to use them with fallback sorters in `hybrid_adapter` to handle the cases where the type to sort cannot be handled by your sorter.

`hybrid_adapter` returns the result of the *adapted sorter* called if any.

If `hybrid_adapter` is nested in another `hybrid_adapter`, those are flattened: for example `hybrid_adapter<A, hybrid_adapter<B, C>, D>` is flattened to `hybrid_adapter<A, B, C, D>`. This unwrapping exists so that the iterator categories of the sorters in the inner `hybrid_adapter` are seen by the outer one, and not only the fused iterator category of the inner `hybrid_adapter`.
If `hybrid_adapter` is nested in another `hybrid_adapter`, those are automatically flattened: for example `hybrid_adapter<A, hybrid_adapter<B, C>, D>` is flattened to `hybrid_adapter<A, B, C, D>`. This unwrapping exists so that the iterator categories of the sorters in the inner `hybrid_adapter` are seen by the outer one, and not only the fused iterator category of the inner `hybrid_adapter`.

If `hybrid_adapter` is wrapped into [`stable_adapter`][stable-adapter], it wraps every *adapted sorter* into `stable_adapter`, forwarding it to better get the specific behaviour f some sorters or adapters when wrapped into it.
If `hybrid_adapter` is wrapped into [`stable_adapter`][stable-adapter], it wraps every *adapted sorter* into `stable_adapter`, forwarding it to better get the specific behaviour of some sorters or adapters when wrapped into it.

The *resulting sorter*'s `is_always_stable` is `std::true_type` if and only if every *adapted sorter*'s `is_always_stable` is `std::true_type`. `is_stable` is specialized so that it will return the stability of the called *adapted sorter* with the given parameters. The iterator category of the *resulting sorter* is the most permissive iterator category among the *adapted sorters*.
The *resulting sorter*'s `is_always_stable` is `std::true_type` if and only if every *adapted sorter*'s `is_always_stable` is `std::true_type`. `is_stable` is specialized so that it returns the stability of the called *adapted sorter* with the given parameters. The iterator category of the *resulting sorter* is the most permissive iterator category among the *adapted sorters*.

### `indirect_adapter`

Expand Down Expand Up @@ -322,6 +322,8 @@ When wrapped into [`stable_adapter`][stable-adapter], it has a slightly differen
[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
[mountain-sort]: https://github.com/Morwenn/mountain-sort
[probe-mono]: Measures-of-presortedness.md#mono
Expand Down
7 changes: 5 additions & 2 deletions include/cpp-sort/adapters/hybrid_adapter.h
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2022 Morwenn
* Copyright (c) 2015-2024 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_ADAPTERS_HYBRID_ADAPTER_H_
Expand Down Expand Up @@ -48,6 +48,9 @@ namespace cppsort
template<typename>
std::size_t iterator_category_value;

template<>
inline constexpr std::size_t iterator_category_value<std::contiguous_iterator_tag> = 3;

template<>
inline constexpr std::size_t iterator_category_value<std::random_access_iterator_tag> = 2;

Expand All @@ -61,7 +64,7 @@ namespace cppsort
template<typename Iterator, std::size_t N>
using choice_for_it = choice<
(iterator_category_value<
iterator_category_t<Iterator>
iterator_concept_t<Iterator>
> + 1) * N - 1
>;

Expand Down
5 changes: 4 additions & 1 deletion include/cpp-sort/detail/iterator_traits.h
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2022 Morwenn
* Copyright (c) 2016-2024 Morwenn
* SPDX-License-Identifier: MIT
*/
#ifndef CPPSORT_DETAIL_ITERATOR_TRAITS_H_
Expand All @@ -24,6 +24,9 @@ namespace cppsort::detail
template<typename Iterator>
using iterator_category_t = typename std::iterator_traits<Iterator>::iterator_category;

template<typename Iterator>
using iterator_concept_t = typename mstd::detail::iter_concept<Iterator>;

// Additional common type to use instead of value_t
template<typename Iterator>
using rvalue_type_t = std::remove_cvref_t<mstd::iter_rvalue_reference_t<Iterator>>;
Expand Down
38 changes: 28 additions & 10 deletions tests/adapters/hybrid_adapter_many_sorters.cpp
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2022 Morwenn
* Copyright (c) 2018-2024 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <iterator>
Expand All @@ -14,10 +14,23 @@ namespace
enum class sorter_type
{
bidirectional,
random_access
random_access,
contiguous
};

template<int N>
struct contiguous_sorter_impl
{
template<typename Iterator>
auto operator()(Iterator, Iterator) const
-> sorter_type
{
return sorter_type::contiguous;
}

using iterator_category = std::contiguous_iterator_tag;
};

struct random_access_sorter_impl
{
template<typename Iterator>
Expand All @@ -43,8 +56,12 @@ namespace
};

template<int N>
struct contiguous_sorter:
cppsort::sorter_facade<contiguous_sorter_impl<N>>
{};

struct random_access_sorter:
cppsort::sorter_facade<random_access_sorter_impl<N>>
cppsort::sorter_facade<random_access_sorter_impl>
{};

struct bidirectional_sorter:
Expand All @@ -61,17 +78,18 @@ TEST_CASE( "hybrid_adapter with many sorters",

cppsort::hybrid_adapter<
bidirectional_sorter,
random_access_sorter<0>,
random_access_sorter<1>,
random_access_sorter<2>,
random_access_sorter<3>,
random_access_sorter<4>,
random_access_sorter<5>
random_access_sorter,
contiguous_sorter<0>,
contiguous_sorter<1>,
contiguous_sorter<2>,
contiguous_sorter<3>,
contiguous_sorter<4>,
contiguous_sorter<5>
> sorter;

std::vector<int> vec;
auto res1 = sorter(vec);
CHECK( res1 == sorter_type::random_access );
CHECK( res1 == sorter_type::contiguous );

std::list<int> li;
auto res2 = sorter(li);
Expand Down

0 comments on commit 0097187

Please sign in to comment.