diff --git a/.forgejo/workflows/do-nothing.yml b/.forgejo/workflows/do-nothing.yml new file mode 100644 index 00000000..f8726764 --- /dev/null +++ b/.forgejo/workflows/do-nothing.yml @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Morwenn +# SPDX-License-Identifier: MIT + +# Codeberg seems to run the repository's GitHub actions when +# there is no Forgejo workflow: the goa of this file is to +# skip that callback +name: Do Nothing + +on: + workflow_dispatch: diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index afc529ac..53ddf4d9 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -40,7 +40,7 @@ jobs: sanitize: address,undefined steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Configure CMake working-directory: ${{runner.workspace}} diff --git a/.github/workflows/build-mingw.yml b/.github/workflows/build-mingw.yml index 8fbf99ac..34f9f2ad 100644 --- a/.github/workflows/build-mingw.yml +++ b/.github/workflows/build-mingw.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2025 Morwenn +# Copyright (c) 2021-2026 Morwenn # SPDX-License-Identifier: MIT name: MinGW-w64 Builds @@ -31,7 +31,7 @@ jobs: build_type: [Debug, Release] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Configure CMake shell: pwsh diff --git a/.github/workflows/build-msvc.yml b/.github/workflows/build-msvc.yml index 85b0bdbb..d586d035 100644 --- a/.github/workflows/build-msvc.yml +++ b/.github/workflows/build-msvc.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2025 Morwenn +# Copyright (c) 2021-2026 Morwenn # SPDX-License-Identifier: MIT name: MSVC Builds @@ -36,7 +36,7 @@ jobs: build_tools: '-T ClangCL' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Configure CMake shell: pwsh @@ -44,7 +44,6 @@ jobs: run: | cmake -H${{github.event.repository.name}} -Bbuild ` -DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} ` - -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ` -G"Visual Studio 17 2022" -A x64 ${{matrix.config.build_tools}} ` -DCPPSORT_BUILD_EXAMPLES=ON diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 892a51b7..f27b419c 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2025 Morwenn +# Copyright (c) 2021-2026 Morwenn # SPDX-License-Identifier: MIT name: Ubuntu Builds @@ -41,7 +41,7 @@ jobs: sanitize: address,undefined steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install GCC if: ${{matrix.cxx == 'g++-9'}} diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 76b3f7c5..ea918dff 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2025 Morwenn +# Copyright (c) 2020-2026 Morwenn # SPDX-License-Identifier: MIT name: Coverage Upload to Codecov @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout project - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install LCOV run: sudo apt-get install -y lcov diff --git a/.github/workflows/mirror-to-codeberg.yml b/.github/workflows/mirror-to-codeberg.yml index d13a05b6..416b1c49 100644 --- a/.github/workflows/mirror-to-codeberg.yml +++ b/.github/workflows/mirror-to-codeberg.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Morwenn +# Copyright (c) 2025-2026 Morwenn # SPDX-License-Identifier: MIT name: Mirror Commits to Codeberg @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index a7f3a45b..08813f71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.8.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -project(cpp-sort VERSION 1.17.2 LANGUAGES CXX) +project(cpp-sort VERSION 1.17.3 LANGUAGES CXX) include(CMakePackageConfigHelpers) include(cpp-sort-utils) diff --git a/README.md b/README.md index 4385e4d6..e9c5efc0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![cpp-sort logo](docs/images/cpp-sort-logo.svg) -[![Latest Release](https://img.shields.io/badge/release-1.17.2-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.17.2) -[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.17.2-blue.svg)](https://conan.io/center/recipes/cpp-sort?version=1.17.2) +[![Latest Release](https://img.shields.io/badge/release-1.17.3-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.17.3) +[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.17.3-blue.svg)](https://conan.io/center/recipes/cpp-sort?version=1.17.3) [![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/develop/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort) [![Pitchfork Layout](https://img.shields.io/badge/standard-PFL-orange.svg)](https://github.com/vector-of-bool/pitchfork) diff --git a/conanfile.py b/conanfile.py index 40320a6e..314055b8 100644 --- a/conanfile.py +++ b/conanfile.py @@ -16,7 +16,7 @@ class CppSortConan(ConanFile): name = "cpp-sort" - version = "1.17.2" + version = "1.17.3" description = "Sorting algorithms & related tools" license = "MIT" url = "https://github.com/Morwenn/cpp-sort" diff --git a/docs/Changelog.md b/docs/Changelog.md index 8619db38..da7d19c0 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -68,6 +68,11 @@ When compiled with C++17, **cpp-sort** might gain a few additional features depe When compiled with C++20, **cpp-sort** might gain a few additional features depending on the level of C++20 support provided by the compiler. The availability of those features depends on the presence of corresponding [feature-testing macros][feature-test-macros] when possible, even though some checks are more granular. Don't hesitate to open an issue if your compiler and standard library supports one of those features but it doesn't seem to work in **cpp-sort**. +**Correctness improvements:** +* When possible, the total order predicates [`total_less` and `total_greater`][total-greater-less] use [`std::strong_order`][std-strong-order] to compare floating-point numbers, leading to a supposedly correct implementation of the IEEE *totalOrder* operator. In C++17 and earlier, those comparators don't actually implement a proper total order: they are unable to differentiate between quiet and signaling NaNs, and connot read NaN payloads. + + The feature-test macro `__cpp_lib_three_way_comparison` can be used to check whether `std::strong_order` is available. + **New features:** * When available, [`std::identity`][std-identity] benefits from dedicated support wherever [`utility::identity`][cpp-sort-function-objects] is supported, with equivalent semantics. @@ -110,5 +115,7 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe [std-ranges-greater]: https://en.cppreference.com/w/cpp/utility/functional/ranges/greater [std-ranges-less]: https://en.cppreference.com/w/cpp/utility/functional/ranges/less [std-string-view]: https://en.cppreference.com/w/cpp/string/basic_string_view + [std-strong-order]: https://en.cppreference.com/w/cpp/utility/compare/strong_order [std-swap]: https://en.cppreference.com/w/cpp/algorithm/swap + [total-greater-less]: Comparators.md#total-order-comparators [utility-iter-move]: Miscellaneous-utilities.md#iter_move-and-iter_swap diff --git a/docs/Comparators.md b/docs/Comparators.md index 31673f6e..e8d84681 100644 --- a/docs/Comparators.md +++ b/docs/Comparators.md @@ -11,7 +11,7 @@ Every non-refined comparator described below is also a [transparent comparator][ #include ``` -The comparators `total_less` and `total_order` are [customization points][custom-point] implementing a [total order][total-order], inspired by the similar functions described in [P0100][P0100]. The provided functions handle built-in integer out of the box (using the built-in relational operators) and implement IEEE 754's `totalOrder` for floating point numbers (from lesser to greater): +The comparators `total_less` and `total_order` are [customization points][custom-point] implementing a [total order][total-order], inspired by the similar functions described in [P0100][P0100]. The provided functions handle built-in integer types out of the box (using the built-in relational operators) and attempt to implement IEEE 754's `totalOrder` for floating point numbers (from lesser to greater): * positive quiet NaNs * positive signaling NaNs * positive infinity @@ -23,7 +23,9 @@ The comparators `total_less` and `total_order` are [customization points][custom * negative signaling NaNs * negative quiet NaNs -That said, the comparators are currently unable to discriminate between quiet and signaling NaNs, so they are considered to be *equivalent*. When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). +That said, the comparators are unable to discriminate between quiet and signaling NaNs, and have no way to inspect the bit patterns of NaNs to compare them. All NaNs that share a same sign are therefore considered to be *equivalent* in C++17. In C++20 however, the comparators rely on [`std::strong_order`][std-strong-order] when available, which correctly implements `totalOrder` for IEEE 754 floating point numbers. + +When it doesn't handle a type natively and ADL doesn't find any suitable `total_less` function in a class namespace, `cppsort::total_less` does *not* fall back to `operator<`; see [P0100][P0100] for the rationale (it applies to the whole `total_*` family of customization points). Total order comparators are considered as [generating branchless code][branchless-traits] when comparing instances of a type that satisfies [`std::is_integral`][std-is-integral]. @@ -31,6 +33,8 @@ Total order comparators are considered as [generating branchless code][branchles *Changed in version 1.13.1:* support for `[un]signed __int128`. +*Changed in version 1.17.3:* `total_less` and `total_greater` now correctly implement IEEE 754 `totalOrder` in C++20 mode, when the feature-test macro `__cpp_lib_three_way_comparison` is defined. + ### Weak order comparators ```cpp diff --git a/docs/Fixed-size-sorters.md b/docs/Fixed-size-sorters.md index dd9ce78d..5c313fd1 100644 --- a/docs/Fixed-size-sorters.md +++ b/docs/Fixed-size-sorters.md @@ -153,7 +153,7 @@ Size | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: **CEs** | 0 | 1 | 3 | 5 | 9 | 12 | 16 | 19 | 25 | 29 | 35 | 39 | 45 | 51 | 56 | 60 **Size** | **17** | **18** | **19** | **20** | **21** | **22** | **23** | **24** | **25** | **26** | **27** | **28** | **29** | **30** | **31** | **32** -**CEs** | 71 | 77 | 85 | 91 | 99 | 106 | 114 | 120 | 130 | 139 | 147 | 155 | 164 | 172 | 180 | 185 +**CEs** | 71 | 77 | 85 | 91 | 99 | 106 | 114 | 120 | 130 | 138 | 147 | 155 | 164 | 172 | 180 | 185 **Size** | **33** | **34** | **35** | **36** | **37** | **38** | **39** | **40** | **41** | **42** | **43** | **44** | **45** | **46** | **47** | **48** **CEs** | 199 | 209 | 220 | 227 | 240 | 250 | 259 | 265 | 282 | 291 | 303 | 309 | 324 | 332 | 340 | 346 **Size** | **49** | **50** | **51** | **52** | **53** | **54** | **55** | **56** | **57** | **58** | **59** | **60** | **61** | **62** | **63** | **64** diff --git a/docs/Home.md b/docs/Home.md index 6258a6a3..094349af 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,6 +1,6 @@ ![cpp-sort logo](images/cpp-sort-logo.svg) -Welcome to the **cpp-sort 1.17.2** documentation! +Welcome to the **cpp-sort 1.17.3** documentation! This wiki contains documentation about the library: basic documentation about the many sorting tools and how to use them, documentation about the additional utilities provided by the library, as well as a few tutorials about writing your own sorters or sorter adapters. This main page explains a few general things that didn't quite fit in other parts of the documentation. diff --git a/docs/Tooling.md b/docs/Tooling.md index bc7c46e3..c8d336ad 100644 --- a/docs/Tooling.md +++ b/docs/Tooling.md @@ -63,10 +63,10 @@ Note: when `CPPSORT_ENABLE_AUDITS` is `ON`, assertions in the library are enable conan search cpp-sort --remote=conancenter ``` -And then install any version to your local cache as follows (here with version 1.17.2): +And then install any version to your local cache as follows (here with version 1.17.3): ```sh -conan install --requires=cpp-sort/1.17.2 +conan install --requires=cpp-sort/1.17.3 ``` The packages downloaded from conan-center are minimal and only contain the files required to use **cpp-sort** as a library: the headers, CMake files and licensing information. If you need anything else you have to create your own package with the `conanfile.py` available in this repository. diff --git a/include/cpp-sort/comparators/partial_greater.h b/include/cpp-sort/comparators/partial_greater.h index fe5c2bf9..972d8e0e 100644 --- a/include/cpp-sort/comparators/partial_greater.h +++ b/include/cpp-sort/comparators/partial_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_PARTIAL_GREATER_H_ @@ -33,9 +33,12 @@ namespace cppsort // Generic overload: a weak order is also a partial order template - auto partial_greater(const T& lhs, const T& rhs) + constexpr auto partial_greater(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::weak_greater(lhs, rhs))) - -> decltype(cppsort::weak_greater(lhs, rhs)) + -> detail::enable_if_t< + not std::is_floating_point::value, + decltype(cppsort::weak_greater(lhs, rhs)) + > { return cppsort::weak_greater(lhs, rhs); } diff --git a/include/cpp-sort/comparators/partial_less.h b/include/cpp-sort/comparators/partial_less.h index 7bebbaf9..77c1c405 100644 --- a/include/cpp-sort/comparators/partial_less.h +++ b/include/cpp-sort/comparators/partial_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_PARTIAL_LESS_H_ @@ -33,9 +33,12 @@ namespace cppsort // Generic overload: a weak order is also a partial order template - auto partial_less(const T& lhs, const T& rhs) + constexpr auto partial_less(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::weak_less(lhs, rhs))) - -> decltype(cppsort::weak_less(lhs, rhs)) + -> detail::enable_if_t< + not std::is_floating_point::value, + decltype(cppsort::weak_less(lhs, rhs)) + > { return cppsort::weak_less(lhs, rhs); } diff --git a/include/cpp-sort/comparators/total_greater.h b/include/cpp-sort/comparators/total_greater.h index ffe62a7f..b2f70929 100644 --- a/include/cpp-sort/comparators/total_greater.h +++ b/include/cpp-sort/comparators/total_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_TOTAL_GREATER_H_ @@ -8,14 +8,19 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include #include #include #include #include -#include "../detail/floating_point_weight.h" #include "../detail/type_traits.h" +#if defined(__cpp_lib_three_way_comparison) +# include +#else +# include +# include "../detail/floating_point_weight.h" +#endif + namespace cppsort { namespace detail @@ -37,6 +42,11 @@ namespace cppsort auto total_greater(T lhs, T rhs) -> detail::enable_if_t::value, bool> { +#if defined(__cpp_lib_three_way_comparison) + // Full implementation of totalOrder + return std::is_gt(std::strong_order(lhs, rhs)); +#else + // Approximation, unable to differentiate NaNs if (std::isfinite(lhs) && std::isfinite(rhs)) { if (lhs == 0 && rhs == 0) { return std::signbit(rhs) && not std::signbit(lhs); @@ -47,6 +57,7 @@ namespace cppsort int lhs_weight = total_weight(lhs); int rhs_weight = total_weight(rhs); return lhs_weight > rhs_weight; +#endif } //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/comparators/total_less.h b/include/cpp-sort/comparators/total_less.h index 76e9bafb..7ee33b59 100644 --- a/include/cpp-sort/comparators/total_less.h +++ b/include/cpp-sort/comparators/total_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_TOTAL_LESS_H_ @@ -8,14 +8,20 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// -#include #include #include +#include #include #include -#include "../detail/floating_point_weight.h" #include "../detail/type_traits.h" +#if defined(__cpp_lib_three_way_comparison) +# include +#else +# include +# include "../detail/floating_point_weight.h" +#endif + namespace cppsort { namespace detail @@ -37,6 +43,11 @@ namespace cppsort auto total_less(T lhs, T rhs) -> detail::enable_if_t::value, bool> { +#if defined(__cpp_lib_three_way_comparison) + // Full implementation of totalOrder + return std::is_lt(std::strong_order(lhs, rhs)); +#else + // Approximation, unable to differentiate NaNs if (std::isfinite(lhs) && std::isfinite(rhs)) { if (lhs == 0 && rhs == 0) { return std::signbit(lhs) && not std::signbit(rhs); @@ -47,6 +58,7 @@ namespace cppsort int lhs_weight = total_weight(lhs); int rhs_weight = total_weight(rhs); return lhs_weight < rhs_weight; +#endif } //////////////////////////////////////////////////////////// diff --git a/include/cpp-sort/comparators/weak_greater.h b/include/cpp-sort/comparators/weak_greater.h index 00ed6930..dd9347c4 100644 --- a/include/cpp-sort/comparators/weak_greater.h +++ b/include/cpp-sort/comparators/weak_greater.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_WEAK_GREATER_H_ @@ -41,9 +41,12 @@ namespace cppsort // Generic overload: a total order is also a weak order template - auto weak_greater(const T& lhs, const T& rhs) + constexpr auto weak_greater(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::total_greater(lhs, rhs))) - -> decltype(cppsort::total_greater(lhs, rhs)) + -> detail::enable_if_t< + not std::is_floating_point::value, + decltype(cppsort::total_greater(lhs, rhs)) + > { return cppsort::total_greater(lhs, rhs); } diff --git a/include/cpp-sort/comparators/weak_less.h b/include/cpp-sort/comparators/weak_less.h index f4ecda3f..dc3c8d65 100644 --- a/include/cpp-sort/comparators/weak_less.h +++ b/include/cpp-sort/comparators/weak_less.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Morwenn + * Copyright (c) 2016-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_COMPARATORS_WEAK_LESS_H_ @@ -41,9 +41,12 @@ namespace cppsort // Generic overload: a total order is also a weak order template - auto weak_less(const T& lhs, const T& rhs) + constexpr auto weak_less(const T& lhs, const T& rhs) noexcept(noexcept(cppsort::total_less(lhs, rhs))) - -> decltype(cppsort::total_less(lhs, rhs)) + -> detail::enable_if_t< + not std::is_floating_point::value, + decltype(cppsort::total_less(lhs, rhs)) + > { return cppsort::total_less(lhs, rhs); } diff --git a/include/cpp-sort/detail/sorting_network/sort26.h b/include/cpp-sort/detail/sorting_network/sort26.h index 3b49b6d9..0d0481bf 100644 --- a/include/cpp-sort/detail/sorting_network/sort26.h +++ b/include/cpp-sort/detail/sorting_network/sort26.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2022 Morwenn + * Copyright (c) 2015-2026 Morwenn * SPDX-License-Identifier: MIT */ #ifndef CPPSORT_DETAIL_SORTING_NETWORK_SORT26_H_ @@ -26,132 +26,131 @@ namespace detail { iter_swap_if(first, first + 1, compare, projection); iter_swap_if(first + 2, first + 3, compare, projection); - iter_swap_if(first + 4, first + 21, compare, projection); - iter_swap_if(first + 5, first + 6, compare, projection); - iter_swap_if(first + 7, first + 8, compare, projection); - iter_swap_if(first + 9, first + 10, compare, projection); - iter_swap_if(first + 11, first + 12, compare, projection); - iter_swap_if(first + 13, first + 14, compare, projection); - iter_swap_if(first + 15, first + 16, compare, projection); - iter_swap_if(first + 17, first + 18, compare, projection); - iter_swap_if(first + 19, first + 20, compare, projection); + iter_swap_if(first + 4, first + 5, compare, projection); + iter_swap_if(first + 6, first + 7, compare, projection); + iter_swap_if(first + 8, first + 9, compare, projection); + iter_swap_if(first + 10, first + 11, compare, projection); + iter_swap_if(first + 12, first + 13, compare, projection); + iter_swap_if(first + 14, first + 15, compare, projection); + iter_swap_if(first + 16, first + 17, compare, projection); + iter_swap_if(first + 18, first + 19, compare, projection); + iter_swap_if(first + 20, first + 21, compare, projection); iter_swap_if(first + 22, first + 23, compare, projection); iter_swap_if(first + 24, first + 25, compare, projection); - iter_swap_if(first, first + 24, compare, projection); - iter_swap_if(first + 1, first + 25, compare, projection); - iter_swap_if(first + 3, first + 21, compare, projection); - iter_swap_if(first + 4, first + 22, compare, projection); + iter_swap_if(first, first + 2, compare, projection); + iter_swap_if(first + 1, first + 3, compare, projection); + iter_swap_if(first + 4, first + 6, compare, projection); iter_swap_if(first + 5, first + 7, compare, projection); - iter_swap_if(first + 6, first + 8, compare, projection); + iter_swap_if(first + 8, first + 10, compare, projection); iter_swap_if(first + 9, first + 11, compare, projection); - iter_swap_if(first + 10, first + 12, compare, projection); - iter_swap_if(first + 13, first + 15, compare, projection); iter_swap_if(first + 14, first + 16, compare, projection); - iter_swap_if(first + 17, first + 19, compare, projection); + iter_swap_if(first + 15, first + 17, compare, projection); iter_swap_if(first + 18, first + 20, compare, projection); - iter_swap_if(first, first + 4, compare, projection); - iter_swap_if(first + 2, first + 22, compare, projection); - iter_swap_if(first + 3, first + 23, compare, projection); - iter_swap_if(first + 5, first + 9, compare, projection); - iter_swap_if(first + 6, first + 10, compare, projection); - iter_swap_if(first + 7, first + 11, compare, projection); - iter_swap_if(first + 8, first + 12, compare, projection); - iter_swap_if(first + 13, first + 17, compare, projection); - iter_swap_if(first + 14, first + 18, compare, projection); - iter_swap_if(first + 15, first + 19, compare, projection); - iter_swap_if(first + 16, first + 20, compare, projection); - iter_swap_if(first + 21, first + 25, compare, projection); - iter_swap_if(first, first + 2, compare, projection); - iter_swap_if(first + 1, first + 3, compare, projection); - iter_swap_if(first + 4, first + 21, compare, projection); - iter_swap_if(first + 5, first + 13, compare, projection); - iter_swap_if(first + 6, first + 14, compare, projection); - iter_swap_if(first + 7, first + 15, compare, projection); - iter_swap_if(first + 8, first + 16, compare, projection); - iter_swap_if(first + 9, first + 17, compare, projection); - iter_swap_if(first + 10, first + 18, compare, projection); - iter_swap_if(first + 11, first + 19, compare, projection); - iter_swap_if(first + 12, first + 20, compare, projection); + iter_swap_if(first + 19, first + 21, compare, projection); iter_swap_if(first + 22, first + 24, compare, projection); iter_swap_if(first + 23, first + 25, compare, projection); - iter_swap_if(first, first + 5, compare, projection); - iter_swap_if(first + 1, first + 7, compare, projection); - iter_swap_if(first + 2, first + 4, compare, projection); - iter_swap_if(first + 3, first + 10, compare, projection); - iter_swap_if(first + 15, first + 22, compare, projection); - iter_swap_if(first + 18, first + 24, compare, projection); - iter_swap_if(first + 20, first + 25, compare, projection); - iter_swap_if(first + 21, first + 23, compare, projection); - iter_swap_if(first + 2, first + 9, compare, projection); + iter_swap_if(first, first + 4, compare, projection); + iter_swap_if(first + 1, first + 6, compare, projection); + iter_swap_if(first + 2, first + 5, compare, projection); iter_swap_if(first + 3, first + 7, compare, projection); - iter_swap_if(first + 4, first + 14, compare, projection); - iter_swap_if(first + 5, first + 13, compare, projection); - iter_swap_if(first + 6, first + 15, compare, projection); - iter_swap_if(first + 10, first + 19, compare, projection); - iter_swap_if(first + 11, first + 21, compare, projection); - iter_swap_if(first + 12, first + 20, compare, projection); - iter_swap_if(first + 16, first + 23, compare, projection); + iter_swap_if(first + 8, first + 14, compare, projection); + iter_swap_if(first + 9, first + 16, compare, projection); + iter_swap_if(first + 10, first + 15, compare, projection); + iter_swap_if(first + 11, first + 17, compare, projection); iter_swap_if(first + 18, first + 22, compare, projection); - iter_swap_if(first + 1, first + 5, compare, projection); - iter_swap_if(first + 2, first + 6, compare, projection); - iter_swap_if(first + 3, first + 15, compare, projection); - iter_swap_if(first + 7, first + 17, compare, projection); - iter_swap_if(first + 8, first + 18, compare, projection); - iter_swap_if(first + 10, first + 22, compare, projection); - iter_swap_if(first + 11, first + 13, compare, projection); - iter_swap_if(first + 12, first + 14, compare, projection); - iter_swap_if(first + 19, first + 23, compare, projection); - iter_swap_if(first + 20, first + 24, compare, projection); - iter_swap_if(first + 1, first + 2, compare, projection); - iter_swap_if(first + 4, first + 7, compare, projection); - iter_swap_if(first + 5, first + 11, compare, projection); + iter_swap_if(first + 19, first + 24, compare, projection); + iter_swap_if(first + 20, first + 23, compare, projection); + iter_swap_if(first + 21, first + 25, compare, projection); + iter_swap_if(first, first + 18, compare, projection); + iter_swap_if(first + 1, first + 19, compare, projection); + iter_swap_if(first + 2, first + 20, compare, projection); + iter_swap_if(first + 3, first + 21, compare, projection); + iter_swap_if(first + 4, first + 22, compare, projection); + iter_swap_if(first + 5, first + 23, compare, projection); + iter_swap_if(first + 6, first + 24, compare, projection); + iter_swap_if(first + 7, first + 25, compare, projection); + iter_swap_if(first + 9, first + 12, compare, projection); + iter_swap_if(first + 13, first + 16, compare, projection); + iter_swap_if(first + 3, first + 11, compare, projection); iter_swap_if(first + 8, first + 9, compare, projection); - iter_swap_if(first + 10, first + 12, compare, projection); - iter_swap_if(first + 13, first + 15, compare, projection); - iter_swap_if(first + 14, first + 20, compare, projection); - iter_swap_if(first + 16, first + 17, compare, projection); - iter_swap_if(first + 18, first + 21, compare, projection); - iter_swap_if(first + 23, first + 24, compare, projection); - iter_swap_if(first + 3, first + 8, compare, projection); - iter_swap_if(first + 4, first + 6, compare, projection); - iter_swap_if(first + 7, first + 16, compare, projection); - iter_swap_if(first + 9, first + 18, compare, projection); iter_swap_if(first + 10, first + 13, compare, projection); iter_swap_if(first + 12, first + 15, compare, projection); - iter_swap_if(first + 17, first + 22, compare, projection); - iter_swap_if(first + 19, first + 21, compare, projection); - iter_swap_if(first + 4, first + 5, compare, projection); - iter_swap_if(first + 6, first + 11, compare, projection); + iter_swap_if(first + 14, first + 22, compare, projection); + iter_swap_if(first + 16, first + 17, compare, projection); + iter_swap_if(first, first + 8, compare, projection); + iter_swap_if(first + 1, first + 9, compare, projection); + iter_swap_if(first + 2, first + 14, compare, projection); + iter_swap_if(first + 6, first + 12, compare, projection); + iter_swap_if(first + 7, first + 15, compare, projection); + iter_swap_if(first + 10, first + 18, compare, projection); + iter_swap_if(first + 11, first + 23, compare, projection); + iter_swap_if(first + 13, first + 19, compare, projection); + iter_swap_if(first + 16, first + 24, compare, projection); + iter_swap_if(first + 17, first + 25, compare, projection); + iter_swap_if(first + 1, first + 2, compare, projection); + iter_swap_if(first + 3, first + 18, compare, projection); + iter_swap_if(first + 4, first + 8, compare, projection); + iter_swap_if(first + 7, first + 22, compare, projection); + iter_swap_if(first + 17, first + 21, compare, projection); + iter_swap_if(first + 23, first + 24, compare, projection); + iter_swap_if(first + 3, first + 14, compare, projection); + iter_swap_if(first + 4, first + 10, compare, projection); + iter_swap_if(first + 5, first + 18, compare, projection); + iter_swap_if(first + 7, first + 20, compare, projection); + iter_swap_if(first + 8, first + 13, compare, projection); + iter_swap_if(first + 11, first + 22, compare, projection); + iter_swap_if(first + 12, first + 17, compare, projection); + iter_swap_if(first + 15, first + 21, compare, projection); + iter_swap_if(first + 1, first + 4, compare, projection); + iter_swap_if(first + 5, first + 6, compare, projection); iter_swap_if(first + 7, first + 9, compare, projection); - iter_swap_if(first + 14, first + 19, compare, projection); + iter_swap_if(first + 8, first + 10, compare, projection); + iter_swap_if(first + 15, first + 17, compare, projection); iter_swap_if(first + 16, first + 18, compare, projection); - iter_swap_if(first + 20, first + 21, compare, projection); + iter_swap_if(first + 19, first + 20, compare, projection); + iter_swap_if(first + 21, first + 24, compare, projection); + iter_swap_if(first + 2, first + 5, compare, projection); + iter_swap_if(first + 3, first + 10, compare, projection); + iter_swap_if(first + 6, first + 14, compare, projection); + iter_swap_if(first + 9, first + 13, compare, projection); + iter_swap_if(first + 11, first + 19, compare, projection); + iter_swap_if(first + 12, first + 16, compare, projection); + iter_swap_if(first + 15, first + 22, compare, projection); + iter_swap_if(first + 20, first + 23, compare, projection); + iter_swap_if(first + 2, first + 8, compare, projection); + iter_swap_if(first + 5, first + 7, compare, projection); + iter_swap_if(first + 6, first + 9, compare, projection); + iter_swap_if(first + 11, first + 12, compare, projection); + iter_swap_if(first + 13, first + 14, compare, projection); + iter_swap_if(first + 16, first + 19, compare, projection); + iter_swap_if(first + 17, first + 23, compare, projection); + iter_swap_if(first + 18, first + 20, compare, projection); iter_swap_if(first + 2, first + 4, compare, projection); - iter_swap_if(first + 3, first + 6, compare, projection); + iter_swap_if(first + 3, first + 5, compare, projection); + iter_swap_if(first + 6, first + 11, compare, projection); iter_swap_if(first + 7, first + 10, compare, projection); - iter_swap_if(first + 8, first + 11, compare, projection); - iter_swap_if(first + 9, first + 12, compare, projection); - iter_swap_if(first + 13, first + 16, compare, projection); - iter_swap_if(first + 14, first + 17, compare, projection); + iter_swap_if(first + 9, first + 16, compare, projection); + iter_swap_if(first + 12, first + 13, compare, projection); + iter_swap_if(first + 14, first + 19, compare, projection); iter_swap_if(first + 15, first + 18, compare, projection); - iter_swap_if(first + 19, first + 22, compare, projection); + iter_swap_if(first + 20, first + 22, compare, projection); iter_swap_if(first + 21, first + 23, compare, projection); iter_swap_if(first + 3, first + 4, compare, projection); iter_swap_if(first + 5, first + 8, compare, projection); iter_swap_if(first + 6, first + 7, compare, projection); - iter_swap_if(first + 9, first + 10, compare, projection); - iter_swap_if(first + 11, first + 13, compare, projection); - iter_swap_if(first + 12, first + 14, compare, projection); - iter_swap_if(first + 15, first + 16, compare, projection); + iter_swap_if(first + 9, first + 11, compare, projection); + iter_swap_if(first + 10, first + 12, compare, projection); + iter_swap_if(first + 13, first + 15, compare, projection); + iter_swap_if(first + 14, first + 16, compare, projection); iter_swap_if(first + 17, first + 20, compare, projection); iter_swap_if(first + 18, first + 19, compare, projection); iter_swap_if(first + 21, first + 22, compare, projection); iter_swap_if(first + 5, first + 6, compare, projection); iter_swap_if(first + 7, first + 8, compare, projection); - iter_swap_if(first + 9, first + 11, compare, projection); - iter_swap_if(first + 10, first + 13, compare, projection); - iter_swap_if(first + 12, first + 15, compare, projection); - iter_swap_if(first + 14, first + 16, compare, projection); + iter_swap_if(first + 9, first + 10, compare, projection); + iter_swap_if(first + 11, first + 12, compare, projection); + iter_swap_if(first + 13, first + 14, compare, projection); + iter_swap_if(first + 15, first + 16, compare, projection); iter_swap_if(first + 17, first + 18, compare, projection); iter_swap_if(first + 19, first + 20, compare, projection); iter_swap_if(first + 4, first + 5, compare, projection); @@ -168,22 +167,23 @@ namespace detail template CPPSORT_ATTRIBUTE_NODISCARD static constexpr auto index_pairs() noexcept - -> std::array, 139> + -> std::array, 138> { return {{ - {0, 1}, {2, 3}, {4, 21}, {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}, {17, 18}, {19, 20}, {22, 23}, {24, 25}, - {0, 24}, {1, 25}, {3, 21}, {4, 22}, {5, 7}, {6, 8}, {9, 11}, {10, 12}, {13, 15}, {14, 16}, {17, 19}, {18, 20}, - {0, 4}, {2, 22}, {3, 23}, {5, 9}, {6, 10}, {7, 11}, {8, 12}, {13, 17}, {14, 18}, {15, 19}, {16, 20}, {21, 25}, - {0, 2}, {1, 3}, {4, 21}, {5, 13}, {6, 14}, {7, 15}, {8, 16}, {9, 17}, {10, 18}, {11, 19}, {12, 20}, {22, 24}, {23, 25}, - {0, 5}, {1, 7}, {2, 4}, {3, 10}, {15, 22}, {18, 24}, {20, 25}, {21, 23}, - {2, 9}, {3, 7}, {4, 14}, {5, 13}, {6, 15}, {10, 19}, {11, 21}, {12, 20}, {16, 23}, {18, 22}, - {1, 5}, {2, 6}, {3, 15}, {7, 17}, {8, 18}, {10, 22}, {11, 13}, {12, 14}, {19, 23}, {20, 24}, - {1, 2}, {4, 7}, {5, 11}, {8, 9}, {10, 12}, {13, 15}, {14, 20}, {16, 17}, {18, 21}, {23, 24}, - {3, 8}, {4, 6}, {7, 16}, {9, 18}, {10, 13}, {12, 15}, {17, 22}, {19, 21}, - {4, 5}, {6, 11}, {7, 9}, {14, 19}, {16, 18}, {20, 21}, - {2, 4}, {3, 6}, {7, 10}, {8, 11}, {9, 12}, {13, 16}, {14, 17}, {15, 18}, {19, 22}, {21, 23}, - {3, 4}, {5, 8}, {6, 7}, {9, 10}, {11, 13}, {12, 14}, {15, 16}, {17, 20}, {18, 19}, {21, 22}, - {5, 6}, {7, 8}, {9, 11}, {10, 13}, {12, 15}, {14, 16}, {17, 18}, {19, 20}, + {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, {22, 23}, {24, 25}, + {0, 2}, {1, 3}, {4, 6}, {5, 7}, {8, 10}, {9, 11}, {14, 16}, {15, 17}, {18, 20}, {19, 21}, {22, 24}, {23, 25}, + {0, 4}, {1, 6}, {2, 5}, {3, 7}, {8, 14}, {9, 16}, {10, 15}, {11, 17}, {18, 22}, {19, 24}, {20, 23}, {21, 25}, + {0, 18}, {1, 19}, {2, 20}, {3, 21}, {4, 22}, {5, 23}, {6, 24}, {7, 25}, {9, 12}, {13, 16}, + {3, 11}, {8, 9}, {10, 13}, {12, 15}, {14, 22}, {16, 17}, + {0, 8}, {1, 9}, {2, 14}, {6, 12}, {7, 15}, {10, 18}, {11, 23}, {13, 19}, {16, 24}, {17, 25}, + {1, 2}, {3, 18}, {4, 8}, {7, 22}, {17, 21}, {23, 24}, + {3, 14}, {4, 10}, {5, 18}, {7, 20}, {8, 13}, {11, 22}, {12, 17}, {15, 21}, + {1, 4}, {5, 6}, {7, 9}, {8, 10}, {15, 17}, {16, 18}, {19, 20}, {21, 24}, + {2, 5}, {3, 10}, {6, 14}, {9, 13}, {11, 19}, {12, 16}, {15, 22}, {20, 23}, + {2, 8}, {5, 7}, {6, 9}, {11, 12}, {13, 14}, {16, 19}, {17, 23}, {18, 20}, + {2, 4}, {3, 5}, {6, 11}, {7, 10}, {9, 16}, {12, 13}, {14, 19}, {15, 18}, {20, 22}, {21, 23}, + {3, 4}, {5, 8}, {6, 7}, {9, 11}, {10, 12}, {13, 15}, {14, 16}, {17, 20}, {18, 19}, {21, 22}, + {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}, {17, 18}, {19, 20}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19}, {20, 21}, }}; } diff --git a/include/cpp-sort/version.h b/include/cpp-sort/version.h index b69d9ce3..c002dc2b 100644 --- a/include/cpp-sort/version.h +++ b/include/cpp-sort/version.h @@ -9,6 +9,6 @@ #define CPPSORT_VERSION_MAJOR 1 #define CPPSORT_VERSION_MINOR 17 -#define CPPSORT_VERSION_PATCH 2 +#define CPPSORT_VERSION_PATCH 3 #endif // CPPSORT_VERSION_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 827f1c6e..631f3d39 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -194,9 +194,11 @@ add_executable(main-tests comparators/case_insensitive_less.cpp comparators/flip_not.cpp comparators/natural_less.cpp + comparators/partial_order.cpp comparators/projection_compare.cpp - comparators/total_less.cpp + comparators/total_order.cpp comparators/transparent_comparators.cpp + comparators/weak_order.cpp # Distributions tests distributions/all_equal.cpp diff --git a/tests/comparators/partial_order.cpp b/tests/comparators/partial_order.cpp new file mode 100644 index 00000000..76f94d59 --- /dev/null +++ b/tests/comparators/partial_order.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "Partial ordering of integers", "[comparison]" ) +{ + long array[] = { -52, 1, 4, 32, 9, -9, -8, -7, 123456, -7, 12, -987 }; + + SECTION( "partial_less" ) + { + cppsort::heap_sort(array, cppsort::partial_less); + CHECK( std::is_sorted(std::begin(array), std::end(array)) ); + } + + SECTION( "partial_greater" ) + { + cppsort::heap_sort(array, cppsort::partial_greater); + CHECK( std::is_sorted(std::begin(array), std::end(array), std::greater<>{}) ); + } +} + +TEST_CASE( "Partial ordering of floating-point numbers", "[comparison]" ) +{ + constexpr double inf = std::numeric_limits::infinity(); + + // We can't do anything with NaNs in the partial order + double array[] = { +1.0, +inf, -1.0, -2.3, +0.0, -inf, +5.6, -0.0, +inf, -inf }; + + SECTION( "partial_less" ) + { + cppsort::heap_sort(array, cppsort::partial_less); + + CHECK( std::isinf(array[0]) ); + CHECK( std::signbit(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( std::signbit(array[1]) ); + CHECK( std::isinf(array[8]) ); + CHECK( not std::signbit(array[8]) ); + CHECK( std::isinf(array[9]) ); + CHECK( not std::signbit(array[9]) ); + + CHECK( std::is_sorted(std::begin(array), std::end(array)) ); + } + + SECTION( "partial_greater" ) + { + cppsort::heap_sort(array, cppsort::partial_greater); + + CHECK( std::isinf(array[0]) ); + CHECK( not std::signbit(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( not std::signbit(array[1]) ); + CHECK( std::isinf(array[8]) ); + CHECK( std::signbit(array[8]) ); + CHECK( std::isinf(array[9]) ); + CHECK( std::signbit(array[9]) ); + + CHECK( std::is_sorted(std::begin(array), std::end(array), std::greater<>{}) ); + } +} + +TEST_CASE( "Partial order customization point", "[comparison]" ) +{ + helpers::totally_comparable ta, tb; + helpers::weakly_comparable wa, wb; + helpers::partially_comparable pa, pb; + + // Ensure that overload resolution is correct + STATIC_CHECK( cppsort::partial_less(ta, tb) == helpers::compare_result::total_less ); + STATIC_CHECK( cppsort::partial_greater(ta, tb) == helpers::compare_result::total_greater ); + STATIC_CHECK( cppsort::partial_less(wa, wb) == helpers::compare_result::weak_less ); + STATIC_CHECK( cppsort::partial_greater(wa, wb) == helpers::compare_result::weak_greater ); + STATIC_CHECK( cppsort::partial_less(pa, pb) == helpers::compare_result::partial_less ); + STATIC_CHECK( cppsort::partial_greater(pa, pb) == helpers::compare_result::partial_greater ); +} diff --git a/tests/comparators/total_less.cpp b/tests/comparators/total_less.cpp deleted file mode 100644 index a661dd0a..00000000 --- a/tests/comparators/total_less.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2016-2022 Morwenn - * SPDX-License-Identifier: MIT - */ -#include -#include -#include -#include -#include - -TEST_CASE( "IEEE 754 totalOrder implementation", "[comparison]" ) -{ - static constexpr double nan = std::numeric_limits::quiet_NaN(); - static constexpr double inf = std::numeric_limits::infinity(); - - double array[] = { +1.0, +inf, -1.0, -nan, +0.0, -inf, +nan, -0.0 }; - cppsort::heap_sort(array, cppsort::total_less); - - // Check for IEEE 754 totalOrder, - // ignore quiet vs. signaling NaNs - CHECK( std::isnan(array[0]) ); - CHECK( std::signbit(array[0]) ); - CHECK( std::isinf(array[1]) ); - CHECK( std::signbit(array[1]) ); - CHECK( array[2] == -1.0 ); - CHECK( array[3] == 0.0 ); - CHECK( std::signbit(array[3]) ); - CHECK( array[4] == 0.0 ); - CHECK( not std::signbit(array[4]) ); - CHECK( array[5] == +1.0 ); - CHECK( std::isinf(array[6]) ); - CHECK( not std::signbit(array[6]) ); - CHECK( std::isnan(array[7]) ); - CHECK( not std::signbit(array[7]) ); -} diff --git a/tests/comparators/total_order.cpp b/tests/comparators/total_order.cpp new file mode 100644 index 00000000..99af3bdf --- /dev/null +++ b/tests/comparators/total_order.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016-2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "Total ordering of integers", "[comparison]" ) +{ + long array[] = { -52, 1, 4, 32, 9, -9, -8, -7, 123456, -7, 12, -987 }; + + SECTION( "total_less" ) + { + cppsort::heap_sort(array, cppsort::total_less); + CHECK( std::is_sorted(std::begin(array), std::end(array)) ); + } + + SECTION( "total_greater" ) + { + cppsort::heap_sort(array, cppsort::total_greater); + CHECK( std::is_sorted(std::begin(array), std::end(array), std::greater<>{}) ); + } +} + +TEST_CASE( "IEEE 754 totalOrder implementation", "[comparison]" ) +{ + constexpr double nan = std::numeric_limits::quiet_NaN(); + constexpr double inf = std::numeric_limits::infinity(); + + double array[] = { +1.0, +inf, -1.0, -nan, +0.0, -inf, +nan, -0.0 }; + + SECTION( "total_less" ) + { + cppsort::heap_sort(array, cppsort::total_less); + + // Check for IEEE 754 totalOrder, + // ignore quiet vs. signaling NaNs + CHECK( std::isnan(array[0]) ); + CHECK( std::signbit(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( std::signbit(array[1]) ); + CHECK( array[2] == -1.0 ); + CHECK( array[3] == 0.0 ); + CHECK( std::signbit(array[3]) ); + CHECK( array[4] == 0.0 ); + CHECK( not std::signbit(array[4]) ); + CHECK( array[5] == +1.0 ); + CHECK( std::isinf(array[6]) ); + CHECK( not std::signbit(array[6]) ); + CHECK( std::isnan(array[7]) ); + CHECK( not std::signbit(array[7]) ); + } + + SECTION( "total_greater" ) + { + cppsort::heap_sort(array, cppsort::total_greater); + + // Check for IEEE 754 totalOrder, + // ignore quiet vs. signaling NaNs + CHECK( not std::signbit(array[0]) ); + CHECK( std::isnan(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( not std::signbit(array[1]) ); + CHECK( array[2] == +1.0 ); + CHECK( array[3] == 0.0 ); + CHECK( not std::signbit(array[3]) ); + CHECK( array[4] == 0.0 ); + CHECK( std::signbit(array[4]) ); + CHECK( array[5] == -1.0 ); + CHECK( std::isinf(array[6]) ); + CHECK( std::signbit(array[6]) ); + CHECK( std::isnan(array[7]) ); + CHECK( std::signbit(array[7]) ); + } +} + +TEST_CASE( "Total order customization point", "[comparison]" ) +{ + helpers::totally_comparable ta, tb; + + // Ensure that overload resolution is correct + STATIC_CHECK( cppsort::total_less(ta, tb) == helpers::compare_result::total_less ); + STATIC_CHECK( cppsort::total_greater(ta, tb) == helpers::compare_result::total_greater ); +} diff --git a/tests/comparators/weak_order.cpp b/tests/comparators/weak_order.cpp new file mode 100644 index 00000000..766a93d0 --- /dev/null +++ b/tests/comparators/weak_order.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE( "Weak ordering of integers", "[comparison]" ) +{ + long array[] = { -52, 1, 4, 32, 9, -9, -8, -7, 123456, -7, 12, -987 }; + + SECTION( "weak_less" ) + { + cppsort::heap_sort(array, cppsort::weak_less); + CHECK( std::is_sorted(std::begin(array), std::end(array)) ); + } + + SECTION( "weak_greater" ) + { + cppsort::heap_sort(array, cppsort::weak_greater); + CHECK( std::is_sorted(std::begin(array), std::end(array), std::greater<>{}) ); + } +} + +TEST_CASE( "Weak ordering of floating-point numbers", "[comparison]" ) +{ + constexpr double inf = std::numeric_limits::infinity(); + + double array[] = { +1.0, +inf, -1.0, -std::nan("2"), +0.0, -inf, +std::nan("1"), -0.0 }; + + SECTION( "weak_less" ) + { + cppsort::heap_sort(array, cppsort::weak_less); + + // Check that equivalent values compare equivalent, + // regardless of their representation + CHECK( std::isnan(array[0]) ); + CHECK( std::signbit(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( std::signbit(array[1]) ); + CHECK( array[2] == -1.0 ); + CHECK( array[3] == 0.0 ); + CHECK( array[4] == 0.0 ); + CHECK( array[5] == +1.0 ); + CHECK( std::isinf(array[6]) ); + CHECK( not std::signbit(array[6]) ); + CHECK( std::isnan(array[7]) ); + CHECK( not std::signbit(array[7]) ); + } + + SECTION( "weak_greater" ) + { + cppsort::heap_sort(array, cppsort::weak_greater); + + // Check that equivalent values compare equivalent, + // regardless of their representation + CHECK( not std::signbit(array[0]) ); + CHECK( std::isnan(array[0]) ); + CHECK( std::isinf(array[1]) ); + CHECK( not std::signbit(array[1]) ); + CHECK( array[2] == +1.0 ); + CHECK( array[3] == 0.0 ); + CHECK( array[4] == 0.0 ); + CHECK( array[5] == -1.0 ); + CHECK( std::isinf(array[6]) ); + CHECK( std::signbit(array[6]) ); + CHECK( std::isnan(array[7]) ); + CHECK( std::signbit(array[7]) ); + } +} + +TEST_CASE( "Weak order customization point", "[comparison]" ) +{ + helpers::totally_comparable ta, tb; + helpers::weakly_comparable wa, wb; + + // Ensure that overload resolution is correct + STATIC_CHECK( cppsort::weak_less(ta, tb) == helpers::compare_result::total_less ); + STATIC_CHECK( cppsort::weak_greater(ta, tb) == helpers::compare_result::total_greater ); + STATIC_CHECK( cppsort::weak_less(wa, wb) == helpers::compare_result::weak_less ); + STATIC_CHECK( cppsort::weak_greater(wa, wb) == helpers::compare_result::weak_greater ); +} diff --git a/tests/testing-tools/comparators.h b/tests/testing-tools/comparators.h new file mode 100644 index 00000000..84e89f04 --- /dev/null +++ b/tests/testing-tools/comparators.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026 Morwenn + * SPDX-License-Identifier: MIT + */ +#ifndef CPPSORT_TESTSUITE_COMPARATORS_H_ +#define CPPSORT_TESTSUITE_COMPARATORS_H_ + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include + +namespace helpers +{ + // Identify the function that was called + enum struct compare_result + { + total_less, + total_greater, + weak_less, + weak_greater, + partial_less, + partial_greater, + }; + + // Collection of empty classes and empty comparison overloads used + // to identify what overload was selected during resolution + + struct totally_comparable + { + friend constexpr auto total_less(const totally_comparable&, + const totally_comparable&) + -> compare_result + { + return compare_result::total_less; + } + }; + + constexpr auto total_greater(const totally_comparable&, + const totally_comparable&) + -> compare_result + { + return compare_result::total_greater; + } + + struct weakly_comparable + { + friend constexpr auto weak_less(const weakly_comparable&, + const weakly_comparable&) + -> compare_result + { + return compare_result::weak_less; + } + }; + + constexpr auto weak_greater(const weakly_comparable&, + const weakly_comparable&) + -> compare_result + { + return compare_result::weak_greater; + } + + struct partially_comparable + { + friend constexpr auto partial_less(const partially_comparable&, + const partially_comparable&) + -> compare_result + { + return compare_result::partial_less; + } + }; + + constexpr auto partial_greater(const partially_comparable&, + const partially_comparable&) + -> compare_result + { + return compare_result::partial_greater; + } +} + +#endif // CPPSORT_TESTSUITE_COMPARATORS_H_ diff --git a/tools/generate_sorting_network.py b/tools/generate_sorting_network.py index 88f8ba88..f133387e 100644 --- a/tools/generate_sorting_network.py +++ b/tools/generate_sorting_network.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2022-2026 Morwenn +# SPDX-License-Identifier: MIT + import argparse import ast import textwrap @@ -69,9 +72,7 @@ def verify_network(pairs: list[tuple]): def generate_cxx(network: list[list[tuple]]): template = textwrap.dedent(""" - namespace cppsort - {{ - namespace detail + namespace cppsort::detail {{ template<> struct sorting_network_sorter_impl<{nb_inputs}> @@ -101,7 +102,7 @@ def generate_cxx(network: list[list[tuple]]): }}}}; }} }}; - }}}} + }} """) pairs = sum(network, [])