├── .clang-format ├── .github └── workflows │ └── ccpp.yml ├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include └── rx │ └── ranges.hpp └── test ├── benchmark.cpp ├── calendar.cpp └── test_ranges.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | Standard: Cpp11 4 | ColumnLimit: 100 5 | DisableFormat: false 6 | FixNamespaceComments: true 7 | 8 | # Indentation 9 | IndentWidth: 4 10 | AccessModifierOffset: -4 11 | ContinuationIndentWidth: 4 12 | IndentCaseLabels: true 13 | IndentWrappedFunctionNames: false 14 | ConstructorInitializerIndentWidth: 4 15 | NamespaceIndentation: Inner 16 | TabWidth: 4 17 | UseTab: Never 18 | 19 | # Spacing 20 | Cpp11BracedListStyle: true 21 | DerivePointerAlignment: false 22 | KeepEmptyLinesAtTheStartOfBlocks: true 23 | MaxEmptyLinesToKeep: 2 24 | SpaceAfterCStyleCast: false 25 | SpaceBeforeAssignmentOperators: true 26 | SpaceBeforeParens: ControlStatements 27 | SpaceBeforeCpp11BracedList: false 28 | SpaceInEmptyParentheses: false 29 | SpacesBeforeTrailingComments: 1 30 | SpacesInAngles: false 31 | SpacesInContainerLiterals: true 32 | SpacesInCStyleCastParentheses: false 33 | SpacesInParentheses: false 34 | SpacesInSquareBrackets: false 35 | 36 | # Alignment 37 | AlignAfterOpenBracket: AlwaysBreak 38 | AlignConsecutiveAssignments: false 39 | AlignConsecutiveDeclarations: false 40 | AlignEscapedNewlines: DontAlign 41 | AlignOperands: true 42 | AlignTrailingComments: true 43 | AllowAllParametersOfDeclarationOnNextLine: false 44 | # AllowAllArgumentsOnNextLine: false 45 | # AllowAllConstructorInitializersOnNextLine: true 46 | PointerAlignment: Left 47 | 48 | # Line breaks 49 | AllowShortBlocksOnASingleLine: false 50 | AllowShortCaseLabelsOnASingleLine: true 51 | AllowShortFunctionsOnASingleLine: Empty 52 | AllowShortIfStatementsOnASingleLine: false 53 | AllowShortLoopsOnASingleLine: false 54 | # AllowShortLambdasOnASingleLine: Inline 55 | AlwaysBreakAfterDefinitionReturnType: None 56 | AlwaysBreakAfterReturnType: None 57 | AlwaysBreakBeforeMultilineStrings: true 58 | AlwaysBreakTemplateDeclarations: true 59 | BinPackArguments: false 60 | BinPackParameters: false 61 | BraceWrapping: 62 | AfterClass: false 63 | AfterControlStatement: false 64 | AfterEnum: false 65 | AfterFunction: false 66 | AfterNamespace: false 67 | AfterObjCDeclaration: false 68 | AfterStruct: false 69 | AfterUnion: false 70 | BeforeCatch: true 71 | BeforeElse: false 72 | IndentBraces: false 73 | BreakBeforeBinaryOperators: NonAssignment 74 | BreakBeforeBraces: Custom 75 | BreakBeforeTernaryOperators: true 76 | BreakConstructorInitializers: BeforeComma 77 | BreakInheritanceList: BeforeComma 78 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 79 | ExperimentalAutoDetectBinPacking: false 80 | PenaltyBreakBeforeFirstCallParameter: 100 81 | PenaltyBreakComment: 300 82 | PenaltyBreakFirstLessLess: 120 83 | PenaltyBreakString: 1000 84 | PenaltyExcessCharacter: 1000000 85 | PenaltyReturnTypeOnItsOwnLine: 60 86 | # PenaltyBreakAssignment: 0 87 | 88 | # Comments 89 | CommentPragmas: '^[@\\].+' # Don't reflow Doxygen comments 90 | ReflowComments: true 91 | 92 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 93 | 94 | MacroBlockBegin: '' 95 | MacroBlockEnd: '' 96 | ... 97 | -------------------------------------------------------------------------------- /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: [] 7 | 8 | jobs: 9 | Linux-GCC9: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: install dependencies 14 | run: (sudo apt-get update && sudo apt-get install libboost-date-time-dev ninja-build cmake build-essential g++-9) 15 | - name: make build dir 16 | run: mkdir build 17 | - name: configure 18 | run: (cd build && cmake .. -G Ninja -DRX_TEST=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=/usr/bin/g++-9) 19 | - name: build 20 | run: ninja -C build 21 | - name: run tests 22 | run: build/rx-ranges-test 23 | - name: run calendar example 24 | run: build/rx-calendar 25 | Linux-Clang8: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v1 29 | - name: install dependencies 30 | run: (sudo apt-get update && sudo apt-get install libboost-date-time-dev ninja-build cmake build-essential clang-8) 31 | - name: make build dir 32 | run: mkdir build 33 | - name: configure 34 | run: (cd build && cmake .. -G Ninja -DRX_TEST=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=/usr/bin/clang++-8) 35 | - name: build 36 | run: ninja -C build 37 | - name: run tests 38 | run: build/rx-ranges-test 39 | - name: run calendar example 40 | run: build/rx-calendar 41 | Linux-Clang8-Libcxx: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v1 45 | - name: install dependencies 46 | run: (sudo apt-get update && sudo apt-get install libboost-date-time-dev ninja-build cmake build-essential clang-8 libc++-8-dev libc++abi-8-dev) 47 | - name: make build dir 48 | run: mkdir build 49 | - name: configure 50 | run: (cd build && cmake .. -G Ninja -DRX_TEST=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=/usr/bin/clang++-8 -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_FLAGS="-stdlib=libc++" -DCMAKE_EXE_LINKER_FLAGS="-lc++ -lm -lc -lgcc_s -lgcc") 51 | - name: build 52 | run: ninja -C build 53 | - name: run tests 54 | run: build/rx-ranges-test 55 | - name: run calendar example 56 | run: build/rx-calendar 57 | Windows-MSVC: 58 | runs-on: windows-latest 59 | steps: 60 | - uses: actions/checkout@v1 61 | - name: install dependencies 62 | run: '& "$Env:VCPKG_INSTALLATION_ROOT/vcpkg.exe" install boost-date-time:x64-windows' 63 | - name: make build dir 64 | run: md build 65 | - name: configure 66 | run: 'cd build; cmake .. -DCMAKE_TOOLCHAIN_FILE="$Env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows -G "Visual Studio 16 2019" -DCMAKE_PLATFORM_ARCH=x64 -DRX_TEST=ON; cd ..' 67 | - name: build 68 | run: cd build; cmake --build . --config Debug; cd .. 69 | - name: run tests 70 | run: build/Debug/rx-ranges-test.exe 71 | - name: run calendar example 72 | run: build/Debug/rx-calendar.exe 73 | macOS-Clang: 74 | runs-on: macos-latest 75 | steps: 76 | - uses: actions/checkout@v1 77 | - name: install dependencies 78 | run: brew install ninja boost 79 | - name: make build dir 80 | run: mkdir build 81 | - name: configure 82 | run: cd build && cmake .. -DCMAKE_BUILD_TYPE=Debug -DRX_TEST=ON -GNinja && cd .. 83 | - name: build 84 | run: ninja -C build 85 | - name: run tests 86 | run: build/rx-ranges-test 87 | - name: run calendar example 88 | run: build/rx-calendar 89 | Linux-GCC9-Benchmark: 90 | runs-on: ubuntu-latest 91 | steps: 92 | - uses: actions/checkout@v1 93 | - name: install dependencies 94 | run: (sudo apt-get update && sudo apt-get install libboost-date-time-dev ninja-build cmake build-essential g++-9) 95 | - name: make build dir 96 | run: mkdir build 97 | - name: configure 98 | run: (cd build && cmake .. -G Ninja -DRX_TEST=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=/usr/bin/g++-9) 99 | - name: build 100 | run: ninja -C build 101 | - name: run tests 102 | run: build/rx-ranges-test 103 | - name: run benchmarks 104 | run: build/rx-benchmark 105 | Linux-Clang8-Benchmark: 106 | runs-on: ubuntu-latest 107 | steps: 108 | - uses: actions/checkout@v1 109 | - name: install dependencies 110 | run: (sudo apt-get update && sudo apt-get install libboost-date-time-dev ninja-build cmake build-essential clang-8) 111 | - name: make build dir 112 | run: mkdir build 113 | - name: configure 114 | run: (cd build && cmake .. -G Ninja -DRX_TEST=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=/usr/bin/clang++-8 -DCMAKE_C_COMPILER=/usr/bin/clang-8) 115 | - name: build 116 | run: ninja -C build 117 | - name: run tests 118 | run: build/rx-ranges-test 119 | - name: run benchmarks 120 | run: build/rx-benchmark 121 | Windows-MSVC-Benchmark: 122 | runs-on: windows-latest 123 | steps: 124 | - uses: actions/checkout@v1 125 | - name: install dependencies 126 | run: '& "$Env:VCPKG_INSTALLATION_ROOT/vcpkg.exe" install boost-date-time:x64-windows' 127 | - name: make build dir 128 | run: md build 129 | - name: configure 130 | run: 'cd build; cmake .. -DCMAKE_TOOLCHAIN_FILE="$Env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows -G "Visual Studio 16 2019" -DCMAKE_PLATFORM_ARCH=x64 -DRX_TEST=ON; cd ..' 131 | - name: build 132 | run: cd build; cmake --build . --config Release; cd .. 133 | - name: run tests 134 | run: build/Release/rx-ranges-test.exe 135 | - name: run benchmarks 136 | run: build/Release/rx-benchmark.exe 137 | macOS-Clang-Benchmark: 138 | runs-on: macos-latest 139 | steps: 140 | - uses: actions/checkout@v1 141 | - name: install dependencies 142 | run: brew install ninja boost 143 | - name: make build dir 144 | run: mkdir build 145 | - name: configure 146 | run: cd build && cmake .. -DCMAKE_BUILD_TYPE=Release -DRX_TEST=ON -GNinja && cd .. 147 | - name: build 148 | run: ninja -C build 149 | - name: run tests 150 | run: build/rx-ranges-test 151 | - name: run benchmarks 152 | run: build/rx-benchmark 153 | Linux-Clang8-libcxx-Benchmark: 154 | runs-on: ubuntu-latest 155 | steps: 156 | - uses: actions/checkout@v1 157 | - name: install dependencies 158 | run: (sudo apt-get update && sudo apt-get install libboost-date-time-dev ninja-build cmake build-essential clang-8 libc++-8-dev libc++abi-8-dev) 159 | - name: make build dir 160 | run: mkdir build 161 | - name: configure 162 | run: (cd build && cmake .. -G Ninja -DRX_TEST=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=/usr/bin/clang++-8 -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_FLAGS="-stdlib=libc++" -DCMAKE_EXE_LINKER_FLAGS="-lc++ -lm -lc -lgcc_s -lgcc") 163 | - name: build 164 | run: ninja -C build 165 | - name: run tests 166 | run: build/rx-ranges-test 167 | - name: run benchmarks 168 | run: build/rx-benchmark 169 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | build.*/* 3 | 4 | # Created by Visual Studio Code 5 | .vscode/* 6 | 7 | # Created by Visual Studio "Open Folder" 8 | out/* 9 | .vs/* 10 | CMakeSettings.json -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Simon Ask Ulsnes -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Version 2.0.1 2 | 3 | Many optimizations, bug fixes, and new features. With this version, the popular "calendar" showcase 4 | for range libraries is supported (see [./test/calendar.cpp](calendar.cpp) for more). 5 | 6 | ### Other 7 | 8 | - ChainRange does not require the output type to be default-constructible. 9 | 10 | ## Version 2.0.0 11 | 12 | Many optimizations, bug fixes, and new features. With this version, the popular "calendar" showcase 13 | for range libraries is supported (see [./test/calendar.cpp](calendar.cpp) for more). 14 | 15 | ### Features 16 | 17 | - Algorithms added: 18 | - `chain()` 19 | - `cycle()` 20 | - `flatten()` 21 | - `for_each()` 22 | - `group_adjacent_by()` 23 | - `in_groups_of_exactly()` 24 | - `in_groups_of()` 25 | - `null_sink()` 26 | - `padded()` 27 | - `tee()` 28 | - `zip_longest()` 29 | 30 | ### Bugfixes 31 | 32 | - Overload resolution for `as_input_range()` was very confusing. This has been cleaned up. 33 | - Some operations that were expected to be idempotent were not (#1). 34 | - Combinators now support non-default-constructible value types, unless the output explicitly 35 | requires it (#7). 36 | - Fix `first()` after `sort()` (#8). 37 | - `sort()` can now be constructed with comparison predicates taking more than one constructor 38 | argument. 39 | - `sort()`/`min()`/`max()` support non-default-constructible and non-copyable predicates. 40 | 41 | ### Optimizations 42 | 43 | - When compilers support it, `__builtin_expect` is utilized in inner loops to aid code layout. 44 | - Idempotency is now only ensured when the input isn't already idempotent. 45 | - Fine-tuning of many internal loops to avoid unnecessary branches. 46 | - Where possible, the "empty base class" optimization is used. This mostly applies to combinators 47 | that take a standard comparison predicate (`std::less`, `std::equal_to`, etc.). 48 | - `group_adjacent_by()` no longer requires internal temporary storage in a vector (#6). 49 | 50 | ### Other 51 | 52 | - Grouping combinators now produce subranges instead of `std::array`s or `std::vector`s, eliminating 53 | the need to allocate temporary internal storage. 54 | - Added a benchmark suite using Google Benchmark. 55 | - Cleanup of internal naming conventions. 56 | - `T::is_finite` is no longer required for input ranges (defaults to `false` if missing). 57 | - `min()` and `max()` can now operate with a custom comparison function. 58 | 59 | ### Contributors 60 | 61 | - René Kijewski 62 | - Jules Ricou 63 | - Simon Ask Ulsnes 64 | 65 | ## Version 1.0.1 66 | 67 | Bug fixes and feature enhancements. Thanks for the valuable feedback from /r/cpp! 68 | 69 | ## Features 70 | 71 | - Added `enumerate(...)` as an alias for `zip(seq(), ...)`. 72 | - Added `reverse()` sink, which reverses the order of the input. 73 | - Renamed `first_n()` to `take()` (keeping `first_n()` as an alias). 74 | - Renamed `fill()` to `repeat()` (keeping `fill()` as an alias). 75 | - Renamed `fill_n()` to `repeat_n()` (keeping `fill_n()` as an alias). 76 | 77 | ### Bugfixes 78 | 79 | - Fixed a bug where `zip(...)` would not explicitly convert its inputs to ranges, meaning standard 80 | containers could not be used directly. 81 | - Fixed a bug where `zip(...)` would produce tuples containing expired references. 82 | 83 | ## Other 84 | 85 | - Added MIT license (See [./LICENSE](LICENSE)). 86 | - Updated README, including more examples. -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | if ("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.15.0") 3 | cmake_policy(SET CMP0092 NEW) # Disable default warning flags so we can override them in MSVC. 4 | endif() 5 | 6 | project(rx-ranges VERSION 2.0.0) 7 | 8 | option(RX_TEST ON) 9 | 10 | add_library(rx-ranges INTERFACE) 11 | add_library(rx::ranges ALIAS rx-ranges) 12 | target_compile_features(rx-ranges INTERFACE 13 | cxx_std_17 14 | ) 15 | target_sources(rx-ranges 16 | INTERFACE 17 | "${CMAKE_CURRENT_SOURCE_DIR}/include/rx/ranges.hpp" 18 | ) 19 | target_include_directories(rx-ranges 20 | INTERFACE include 21 | ) 22 | 23 | if (RX_TEST) 24 | # RelWithDebInfo falls back to Release, then MinSizeRel 25 | set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release MinSizeRel NoConfig "") 26 | # MinSizeRel falls back to Release, then RelWithDebInfo 27 | set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL MinSizeRel Release RelWithDebInfo NoConfig "") 28 | # Release falls back to RelWithDebInfo, then MinSizeRel 29 | set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE Release RelWithDebInfo MinSizeRel NoConfig "") 30 | 31 | # Note: Cloning ad-hoc, because we don't want to add submodules (interfering with other 32 | # projects using us as submodule and `--recursive`), and `ExternalProject` does not work well at 33 | # all for importing targets from other projects. 34 | if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/doctest") 35 | message(STATUS "Cloning onqtam/doctest...") 36 | execute_process( 37 | COMMAND git clone https://github.com/onqtam/doctest.git 38 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 39 | ) 40 | endif() 41 | if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/benchmark") 42 | message(STATUS "Cloning google/benchmark...") 43 | execute_process( 44 | COMMAND git clone https://github.com/google/benchmark.git 45 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 46 | ) 47 | endif() 48 | 49 | set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) 50 | set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) 51 | set(BENCHMARK_ENABLE_EXCEPTIONS ON CACHE BOOL "" FORCE) 52 | set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) 53 | mark_as_advanced(BENCHMARK_ENABLE_TESTING) 54 | mark_as_advanced(BENCHMARK_ENABLE_GTEST_TESTS) 55 | mark_as_advanced(BENCHMARK_ENABLE_EXCEPTIONS) 56 | mark_as_advanced(BENCHMARK_ENABLE_INSTALL) 57 | set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) 58 | 59 | set(DOCTEST_WITH_TESTS OFF CACHE BOOL "" FORCE) 60 | set(DOCTEST_WITH_MAIN_IN_STATIC_LIB OFF CACHE BOOL "" FORCE) 61 | set(DOCTEST_NO_INSTALL OFF CACHE BOOL "" FORCE) 62 | mark_as_advanced(DOCTEST_WITH_TESTS) 63 | mark_as_advanced(DOCTEST_WITH_MAIN_IN_STATIC_LIB) 64 | mark_as_advanced(DOCTEST_NO_INSTALL) 65 | 66 | add_subdirectory("${CMAKE_CURRENT_BINARY_DIR}/doctest") 67 | add_subdirectory("${CMAKE_CURRENT_BINARY_DIR}/benchmark") 68 | 69 | add_executable(rx-ranges-test test/test_ranges.cpp) 70 | target_link_libraries(rx-ranges-test PRIVATE rx::ranges doctest) 71 | target_compile_definitions(rx-ranges-test PRIVATE "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN") 72 | if (WIN32) 73 | target_compile_definitions(rx-ranges-test PRIVATE "_CRT_SECURE_NO_WARNINGS") 74 | target_compile_options(rx-ranges-test PRIVATE /W4 /WX /permissive-) 75 | else() 76 | target_compile_options(rx-ranges-test PRIVATE -Wall -Werror -Wpedantic) 77 | endif() 78 | 79 | set(Boost_NO_BOOST_CMAKE ON) 80 | find_package(Boost QUIET COMPONENTS date_time) 81 | if (Boost_FOUND) 82 | add_executable(rx-calendar test/calendar.cpp) 83 | if (WIN32) 84 | target_compile_definitions(rx-calendar PRIVATE "_CRT_SECURE_NO_WARNINGS") 85 | endif() 86 | target_link_libraries(rx-calendar PRIVATE rx::ranges Boost::date_time) 87 | else() 88 | message(WARNING "Boost not found, not building calendar example") 89 | endif() 90 | 91 | add_executable(rx-benchmark test/benchmark.cpp) 92 | if (WIN32) 93 | target_compile_definitions(rx-benchmark PRIVATE "_CRT_SECURE_NO_WARNINGS") 94 | endif() 95 | target_link_libraries(rx-benchmark PRIVATE rx::ranges benchmark) 96 | endif() 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Simon Ask Ulsnes 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simpler ranges for C++17 2 | 3 | Ranges in C++20 are looking unwieldy. 4 | 5 | This is a ranges-like library for C++17 that provides zero-overhead list comprehensions with a 6 | pipe-like syntax. 7 | 8 | Standard containers can be filtered, transformed, passed through various algorithms, optimizing to 9 | loops that would not be more efficient if written by hand. 10 | 11 | The goal is to provide the tools to write more readable loops, where the intent of the programmer 12 | is clearly communicated to the reader. Mentally simulating loops is a common but error-prone 13 | part of reading other people's code, and indeed your own code from 3 months ago. 14 | 15 | The library makes heavy use of modern C++17 features, so a compliant C++17 compiler is required. 16 | 17 | ## Features 18 | 19 | - Arbitrary composability. 20 | - Constexpr-friendly. 21 | - No unnecessary temporary heap allocations (`foo | sort() | to_vector()` only allocates into the 22 | resulting container). 23 | - Heap allocation minimization: `reserve()` is used on resulting containers, when possible. 24 | - Open-ended generators (non-terminating, infinite ranges). 25 | - Re-entrancy: A non-rvalue range can be used multiple times in a function. 26 | - Compatible with standard containers (anything that supports `std::begin()` and `std::end()`). 27 | - Compatible with standard algorithms (implicit conversion to iterator-like objects). 28 | - Simple extensibility with custom range adapters. Just implement the `InputRange` faux-concept. 29 | - Non-intrusive `operator|`. The ranges `foo | bar | baz` can be expressed as `baz(bar(foo))`, if 30 | using `operator|` would introduce ambiguous overloads. 31 | - No dependencies beyond the standard library. 32 | - Integration with foreign codebases (override hooks for `std::optional`, `std::remove_cvref_t`, 33 | assertions, etc.). Can easily be used as a submodule. 34 | - Compiler support for all major compilers (GCC, Clang, MSVC). 35 | - Zero-overhead, compared to manually written loops in optimized builds. 36 | - Header-only, and single-header. 37 | 38 | ## Limitations (non-goals) 39 | 40 | Other than usability concerns, these are the main differences from C++20 ranges. 41 | 42 | - Bidirectional ranges. Ranges can only be consumed linearly in the forward direction. 43 | - Random-access ranges. Ranges can only be consumed linearly in the forward direction. 44 | - Internally using iterators. The internal iteration objects are modeled with an "enumerator" 45 | concept instead (objects that provide `next()`, `get()`, `at_end()`, etc.), which simplifies 46 | custom extensions. Implicit, zero-overhead conversion to iterators is provided for 47 | compatibility with standard algorithms and the range-based for loop syntax. 48 | - Direct access to the data of underlying contiguous ranges (`data()` etc.). 49 | 50 | ## Compact generated code 51 | 52 | Different compilers employ different heuristics, impacting things like inlining decisions, 53 | auto-vectorization, register spilling, etc., and these tend to be highly sensitive to minor 54 | code changes. For this reason, it is very hard to provide any guarantees about the generated 55 | code. 56 | 57 | [This example on Godbolt](https://godbolt.org/z/skF3-v) is a good case study. We can make the 58 | following observations: 59 | 60 | - For a constant argument, GCC does constant-folding in both the loop-based and range-based 61 | versions. 62 | - Clang is able to constant-fold the range-based version, but not the loop-based version. 63 | - MSVC does not do any constant-folding, but *does* auto-vectorize both versions, generating very 64 | similar code. MSVC also chooses to spill many registers to the stack, which may or may not impact 65 | performance in vectorized code. 66 | - When the compilers are not able to constant-fold, the generated code is extremely similar for 67 | the loop-based version and the ranges-based version. 68 | 69 | ## Algorithms 70 | 71 | ### Generators 72 | 73 | Algorithms that generate ranges of elements. These are typically used in the beginning of a chain. 74 | 75 | - `cycle()`: Create an infinite range repeating the input elements in a loop. 76 | - `empty_range()`: A range that is always empty. 77 | - `fill_n()`: Generate `n` copies of a value (like `std::fill()`). 78 | - `fill()`: Generate infinite copies of a value. 79 | - `generate()`: Generate infinite elements by calling a user-provided function. 80 | - `iterator_range()`: Form a range from a pair of standard iterators. 81 | - `padded()`: Yield an infinite list of constant values once the input range is exhausted. 82 | - `seq()`: Generate an infinite sequence of elements of any arithmetic types. 83 | 84 | ### Combinators 85 | 86 | Algorithms that produce ranges from the output of other ranges. 87 | 88 | - `chain()`: Return values from multiple ranges, one after another. 89 | - `filter()`: Produce only elements from an input range where predicate returns true. 90 | - `first_n()`: Alias for `take()`. 91 | - `flatten()`: Return a range flattening one level of nesting in a range of ranges. 92 | - `group_adjacent_by()`: Produce subranges for which a user-provided function returns the same value 93 | for a sequence of elements. 94 | - `in_groups_of()`: Produce subranges containing at least one and at most `n` elements. 95 | - `in_groups_of_exactly()`: Produce subranges containing exactly `n` elements, discarding elements 96 | at the end if number of input elements is not divisible by `n`. 97 | - `skip_n()`: Produce all elements from an input range after skipping `n` elements. 98 | - `take()`: Produce the first `n` elements of an input range. 99 | - `tee()`: Copy values of a range into a container during iteration, forwarding the value unmodified 100 | to the next combinator. 101 | - `transform()`: Transform elements from an input range with lambda (like `std::transform()`). 102 | - `until()`: Produce elements from an input range until a predicate returns false. 103 | - `zip_longest()`: Produce tuples of values from multiple input ranges, until all of the ranges 104 | reach their end. 105 | - `zip()`: Produce tuples of values from multiple input ranges, until one of the ranges reaches its 106 | end. 107 | 108 | ### Aggregators 109 | 110 | Algorithms that produce or modify a single value from a range of elements. 111 | 112 | - `all_of()`: True if all elements matches predicate. 113 | - `any_of()`: True if any element matches predicate. 114 | - `append()`: Append the result of an input range to an arbitrary container supporting 115 | `emplace_back()`, `push_back()`, or `emplace()`. 116 | - `count()`: Count number of elements in an input range. 117 | - `first()`: Produce the first element as an `std::optional`. 118 | - `foldl()`: Fold-left with initial value and lambda (like `std::accumulate()`). 119 | - `for_each()`: Call function for each element in a range, returning `void`. 120 | - `max()`: Get the maximum element, according to `std::max()`, or `std::nullopt` if the input is 121 | empty. 122 | - `min()`: Get the minimum element, according to `std::min()`, or `std::nullopt` if the input is 123 | empty. 124 | - `none_of()`: True if no element matches predicate. 125 | - `sum()`: Sum elements of types that support `operator+`. 126 | 127 | ### Sinks 128 | 129 | Algorithms that operate on a full range of elements, and produce the output in a standard container. 130 | When multiple sinks are chained, they will all operate on the same destination container, avoiding 131 | temporary allocations. However, if a sink is chained with an aggregator or combinator, a temporary 132 | `std::vector` will be allocated to hold the result. 133 | 134 | - `null_sink()`: A sink that simply discards all elements of a range. 135 | - `reverse()`: Reverse the order of elements in the input. 136 | - `uniq()`: Reduce the output by consecutive equality with `std::unique()`. 137 | - `sort()`: Sort the elements of an input range with `std::sort()`. 138 | - `to_vector()`: Produce an `std::vector` with all elements of the input. 139 | - `to_list()`: Produce an `std::list` with all elements of the input. 140 | - `to_map()`: Produce an `std::map` from elements of the input. Note that the input type must be 141 | "tuple-like", where the first component of the tuple becomes the key, and the second component 142 | becomes the value. 143 | - `to_opt()`: Produce a single `std::optional` containing the last element of the input, or 144 | `std::nullopt` if the input was empty. 145 | - `to_set()`: Produce an `std::set` from elements of the input. 146 | 147 | ## Examples 148 | 149 | Generate a sequence of 15 odd integers: 150 | 151 | ~~~c++ 152 | for (auto x: seq() | filter([](int x) { return x % 2 == 1; }) | first_n(15)) { 153 | // do something with x 154 | } 155 | ~~~ 156 | 157 | Generate a map of 15 integers to their string representation. 158 | 159 | ~~~c++ 160 | // Note: Composing two infinite ranges. 161 | auto ints = seq() | filter([](int x) { return x % 2 == 1; }; 162 | auto strings = ints | transform([](int x) { return std::to_string(x); }); 163 | auto map = zip(ints, strings) | first_n(15) | to_map(); 164 | ~~~ 165 | 166 | Sort a list of integers converted to strings. Note that sorting happens in the 167 | resulting output vector. No additional storage is 168 | 169 | ~~~c++ 170 | auto ints = std::vector{{4, 1, 6, 2, 7, 4}}; 171 | // Note: The following line will not perform any computation yet. 172 | auto strings = ints | transform([](int x) { return std::to_string(x); }); 173 | // Note: The following line will materialize into a vector and call reserve() 174 | // based on the size of the input, resulting in a single allocation. 175 | auto sorted = strings | sort() | to_vector(); 176 | // => {"1"s, "2"s, "4"s, "4"s, "6"s, "7"s} 177 | ~~~ 178 | 179 | See [the tests](test/test_ranges.cpp) for more. 180 | 181 | 182 | ### Custom combinators 183 | 184 | If the algorithms included in rx::ranges do not cover your needs, defining your 185 | own is simple. Here is an example of an adapter that converts its input to strings: 186 | 187 | ~~~c++ 188 | struct convert_to_string { 189 | template 190 | struct Range { 191 | // In many cases, the output type will be derived from the input range. Not in this case, 192 | // though, because we always emit a string. 193 | using output_type = std::string; 194 | 195 | // Some ranges produce an infinite number of elements, but we produce the same number of 196 | // elements as our input. 197 | // 198 | // This definition is optional, and will be treated as "false" if absent. 199 | static constexpr bool is_finite = rx::is_finite_v; 200 | 201 | // Some ranges may call user-provided lambdas to produce a value, in which case they 202 | // cannot be guaranteed to be idempotent (meaning that calling `get()` and `next()` has 203 | // side-effects). In this case, we are idempotent if the input is idempotent. 204 | // 205 | // This definition is optional, and will be treated as "false" if absent. 206 | static constexpr bool is_idempotent = rx::is_idempotent_v; 207 | 208 | Input input; 209 | constexpr explicit Range(Input input) : input(std::move(input)) {} 210 | 211 | // Get the value at the current position. This is where we perform the conversion of 212 | // the input value to string. 213 | // 214 | // This method can be constexpr, but we are producing strings. 215 | [[nodiscard]] constexpr output_type get() const noexcept { 216 | return std::to_string(input.get()); 217 | } 218 | 219 | // Advance to the next position. 220 | constexpr void next() noexcept { 221 | input.next(); 222 | } 223 | 224 | // Check whether we can produce more elements, or we are at the end of the input. 225 | [[nodiscard]] constexpr bool at_end() const noexcept { 226 | return input.at_end(); 227 | } 228 | 229 | // If we know something about the number of elements that we will produce, a hint 230 | // can be supplied to anyone consuming this range. This hint is solely used as an 231 | // optimization, e.g. to avoid excessive heap allocations. 232 | [[nodiscard]] constexpr size_t size_hint() const noexcept { 233 | return input.size_hint(); 234 | } 235 | 236 | // If we can advance by more than one position at a time, this method can be 237 | // implemented as an optimization. 238 | // 239 | // This method is optional. If absent will be implemented as calling `next()` in a loop 240 | // `n` times, or until `at_end()` returns true. Since callers cannot know for sure 241 | // how many remaining elements we can produce, it may be called with `n` larger 242 | // than that number, which should be treated as advancing to the end. 243 | // 244 | // The return value should be how much the range was actually advanced, and therefore always 245 | // less than or equal to `n`. 246 | // 247 | // In this example, we simply pass the call to the input range. 248 | constexpr size_t advance_by(size_t n) const noexcept { 249 | return rx::advance_by(input, n); 250 | } 251 | }; 252 | 253 | // This operator is what takes care of chaining with other adapters. 254 | // 255 | // Implementing this is sufficient to support the `operator|` syntax. 256 | template 257 | [[nodiscard]] constexpr auto operator()(Input&& input) const { 258 | // This line ensures that the adapter can be used to wrap standard containers as well as 259 | // chains of other adapters. 260 | // 261 | // If your range requires idempotency of the input, `as_idempotent_input_range()` is 262 | // provided as an alternative to `as_input_range()`. An example of an adapter that 263 | // requires idempotency is `filter()`, because it calls `get()` on its input both 264 | // when advancing to the next position, and when its own `get()` is called. 265 | using Inner = decltype(rx::as_input_range(std::forward(input))); 266 | return Range(rx::as_input_range(std::forward(input))); 267 | } 268 | }; 269 | 270 | // Using our new adapter in practice: 271 | std::vector convert_ints_to_sorted_strings(std::vector input) { 272 | return input | convert_to_string() | rx::sort() | rx::to_vector(); 273 | } 274 | ~~~ 275 | 276 | 277 | ### Custom sinks 278 | 279 | Some algorithms want to operate on a "materialized" range, i.e. a collection of values from 280 | some input, where all the values are available. However, we do not want to allocate temporary 281 | storage for those when the result of a chain of adapters is going to be materialized into 282 | some collection anyway. To facilitate this, the "sink" concept ensures that operations such as 283 | `sort()` do not need any temporary storage beyond the resulting container. If multiple sinks 284 | are chained, they will all operate on the same resulting container, in order. 285 | 286 | Note that sinks are different from adapters because the operate on a whole range. They do not 287 | have a notion of "position" and "advancing". However, if sinks are chained with other adapters, 288 | temporary storage *will* be allocated to hold the input for the adapters. An example of this is 289 | `sort() | filter()`. 290 | 291 | Here is an example of a sink that normalizes a range representing an 292 | N-dimensional vector, where N is the number of dimensions: 293 | 294 | ~~~c++ 295 | struct normalize { 296 | template 297 | struct Range { 298 | using output_type = rx::get_output_type_of_t; 299 | Input input; 300 | constexpr explicit Range(Input input) : input(std::move(input)) {} 301 | 302 | // Write the result to `out`, which is assumed to be a standard container. 303 | // Note that "sinking" can only be done on an rvalue reference, to ensure that the 304 | // use case of storing a chain including sinks in a local variable that gets reused. 305 | template 306 | constexpr void sink(Out& out) && noexcept { 307 | // First, produce the elements from the input. If the size of the input is 308 | // known, this also takes care of calling `reserve()` on the output, if it is 309 | // available. 310 | rx::sink(std::move(input), out); 311 | 312 | // Then, compute the length of the input vector: 313 | auto square = [](auto x) { return x * x; }; 314 | auto length = std::sqrt(out | transform(square) | sum()); 315 | 316 | // And finally, divide each element in the output by the length: 317 | for (auto& x : out) { 318 | x /= length; 319 | } 320 | } 321 | }; 322 | 323 | template 324 | [[nodiscard]] constexpr auto operator()(Input&& input) const { 325 | // Note: Here we are not using `as_input_range()`, because that would allocate 326 | // temporary storage for each sink in a chain. 327 | using Inner = rx::remove_cvref_t; 328 | return Range(std::forward(input)); 329 | } 330 | }; 331 | 332 | // Using our new sink in practice: 333 | std::vector normalize_vector(std::vector input) { 334 | return input | normalize() | to_vector(); 335 | } 336 | ~~~ 337 | 338 | ### Custom aggregators 339 | 340 | An aggregator is simply any function that takes a range as an input and outputs a single value. This 341 | example computes the average of the input elements. 342 | 343 | ~~~c++ 344 | struct average { 345 | template 346 | constexpr auto operator()(Input&& input) const { 347 | using element_type = rx::get_output_type_of_t; 348 | auto [count, summed] = rx::zip(rx::seq(1, 0), input) 349 | | rx::foldl(std::tuple(size_t(0), element_type{0}), 350 | [](auto&& accum, auto&& element) { 351 | return std::tuple(std::get<0>(accum) + std::get<0>(element), 352 | std::get<1>(accum) + std::get<1>(element)); 353 | }); 354 | return summed / element_type(count); 355 | } 356 | }; 357 | 358 | // Using our new aggregator in practice: 359 | double compute_average(std::vector values) { 360 | return values | average(); 361 | } 362 | ~~~ -------------------------------------------------------------------------------- /include/rx/ranges.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RX_RANGES_HPP_INCLUDED 2 | #define RX_RANGES_HPP_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // This override is provided to avoid name clashes in foreign codebases where `rx` already has a 14 | // different meaning. 15 | #if !defined(RX_NAMESPACE_OVERRIDE) 16 | #define RX_NAMESPACE rx 17 | #else 18 | #define RX_NAMESPACE RX_NAMESPACE_OVERRIDE 19 | #endif 20 | 21 | // Some foreign libraries may have their own optional type, which may be optimized for their own 22 | // value types. For example, some libraries treat NaN values as "none", or provide "transparent" 23 | // optional when the value type has an "illegal" representation. 24 | #if !defined(RX_OPTIONAL_OVERRIDE) 25 | #include 26 | #define RX_OPTIONAL std::optional 27 | #else 28 | #define RX_OPTIONAL RX_OPTIONAL_OVERRIDE 29 | #endif 30 | 31 | // Some foreign libraries may provide a more user-friendly static assertion facility. Most compilers 32 | // don't give any context about the types that failed a `static_assert()` check in templated code. 33 | #if !defined(RX_TYPE_ASSERT_OVERRIDE) 34 | #define RX_TYPE_ASSERT(cond) static_assert(std::conjunction::value) 35 | #else 36 | #define RX_TYPE_ASSERT RX_TYPE_ASSERT_OVERRIDE 37 | #endif 38 | 39 | // Some foreign libraries may provide their own assertion facilities that integrate better with 40 | // their environment. 41 | #if !defined(RX_ASSERT_OVERRIDE) 42 | #include 43 | #define RX_ASSERT(cond) assert(cond) 44 | #else 45 | #define RX_ASSERT RX_ASSERT_OVERRIDE 46 | #endif 47 | 48 | // Unfortunately, this is not compatible with C++20 `[[likely]]` / `[[unlikely]]`, because it has a 49 | // different syntax structure. 50 | #if defined(__GNUC__) || defined(__clang__) 51 | #define RX_LIKELY(cond) bool(__builtin_expect(!!(cond), 1)) 52 | #define RX_UNLIKELY(cond) bool(__builtin_expect(!!(cond), 0)) 53 | #else 54 | #define RX_LIKELY(cond) bool(cond) 55 | #define RX_UNLIKELY(cond) bool(cond) 56 | #endif 57 | 58 | 59 | /*! 60 | @brief rx::ranges library 61 | 62 | Core concepts 63 | ============= 64 | 65 | - Input range: An object that can produce a series of values. Input ranges can be chained 66 | together and combined to form aggregate input ranges. 67 | 68 | - Sink: An object that can receive values from input ranges in order to perform some 69 | action on the whole range of results. A chain of sinks will operate on the same 70 | resulting container without allocating any intermediate storage. Sinks can also 71 | be (implicitly) converted to input ranges, in which case temporary storage *will* 72 | be allocated for the intermediate result. 73 | 74 | */ 75 | namespace RX_NAMESPACE { 76 | 77 | template 78 | using remove_cvref_t = std::remove_cv_t>; 79 | 80 | /*! 81 | @brief InputRange concept 82 | 83 | An input range is expected to conform to the following interface: 84 | 85 | @code 86 | // Required: 87 | using output_type = ...; // The output element type, usually typename Inner::output_type. 88 | 89 | output_type get() const noexcept; // Get the value at the current position. 90 | 91 | void next() noexcept; // Advance to the next position. 92 | 93 | bool at_end() const noexcept; // True if there are no more elements. 94 | 95 | size_t size_hint() const noexcept; // A hint about how many times `next()` can be called. 96 | // It is purely an optimization hint, not the accurate number 97 | // of times. Return `std::numeric_limits::max()` if 98 | // unknown. 99 | 100 | // Optional: 101 | 102 | static constexpr bool is_finite = ...; // True if the range produces a bounded number of 103 | // elements. Defaults to false. 104 | static constexpr bool is_idempotent = ...; // True if `get()` can be called multiple times 105 | // without advancing internal state. Defaults to 106 | // false. 107 | T&& get() && noexcept; // For non-idempotent ranges, an rvalue reference may be returned for 108 | // efficiency. Note that this also requires the const `get()` version to 109 | // be marked as `const&` instead of just `const`. 110 | size_t advance_by(size_t) noexcept; // Advance by n positions, or until the range is at its end. 111 | @endcode 112 | 113 | Calling `get()` or `next()` while `at_end() == true` is a breach of contract, and is allowed to 114 | be undefined behavior. 115 | 116 | @tparam T Implementation of the input range. 117 | @tparam Inner Another InputRange used as the input for this range. 118 | */ 119 | template 120 | struct is_input_range : std::false_type {}; 121 | template 122 | struct is_input_range< 123 | T, 124 | std::void_t< 125 | typename T::output_type, 126 | decltype(std::declval().get()), 127 | decltype(std::declval().at_end()), 128 | decltype(std::declval().next())>> : std::true_type {}; 129 | template 130 | constexpr bool is_input_range_v = is_input_range>::value; 131 | 132 | /*! 133 | @brief Sink concept 134 | 135 | Sinks transfer the result of a range expression to a container. If a sink is used as an 136 | InputRange, the result will be copied to a temporary vector, which will then become the input. 137 | 138 | A sink is expected to conform to the following interface: 139 | 140 | @code 141 | using output_type = ...; // The output element type, usually `typename Inner::output_type`, if 142 | // the sink is wrapping another range or sink. 143 | template 144 | void sink(Out& out) && noexcept; // Call sink(std::move(inner), out), and perform the desired 145 | // operation on the result. 146 | @endcode 147 | 148 | The `sink()` member function should be "destructive", and does not need to be repeatable 149 | multiple times for the same instance. If the sink wraps another range, it should call 150 | `sink(std::move(inner), out)`. 151 | 152 | @tparam T The sink type. 153 | @tparam Out The output container type. 154 | */ 155 | template 156 | struct is_sink : std::false_type {}; 157 | // Test whether T is a sink by checking that it has `T::output_type` and an implementation of 158 | // `sink()` that accepts an `std::vector&` as its argument. 159 | template 160 | struct is_sink< 161 | T, 162 | std::void_t().sink( 163 | std::declval::output_type>&>()))>> 164 | : std::true_type {}; 165 | template 166 | constexpr bool is_sink_v = is_sink>::value; 167 | 168 | template 169 | constexpr bool is_input_or_sink_v = is_input_range_v || is_sink_v; 170 | 171 | 172 | template 173 | struct has_reserve : std::false_type {}; 174 | template 175 | struct has_reserve().reserve(std::declval()))>> 176 | : std::true_type {}; 177 | template 178 | constexpr bool has_reserve_v = has_reserve>::value; 179 | 180 | template 181 | struct has_emplace_back : std::false_type {}; 182 | template 183 | struct has_emplace_back< 184 | T, 185 | std::void_t().emplace_back( 186 | std::declval()))>> : std::true_type {}; 187 | template 188 | constexpr bool has_emplace_back_v = has_emplace_back>::value; 189 | 190 | template 191 | struct has_push_back : std::false_type {}; 192 | template 193 | struct has_push_back< 194 | T, 195 | std::void_t().push_back(std::declval()))>> 196 | : std::true_type {}; 197 | template 198 | constexpr bool has_push_back_v = has_push_back>::value; 199 | 200 | template 201 | struct has_emplace : std::false_type {}; 202 | template 203 | struct has_emplace< 204 | T, 205 | std::void_t().emplace(std::declval()))>> 206 | : std::true_type {}; 207 | template 208 | constexpr bool has_emplace_v = has_emplace>::value; 209 | 210 | // True if `std::tuple_size::value` is defined. 211 | template 212 | struct is_tuple_like : std::false_type {}; 213 | template 214 | struct is_tuple_like::value)>> : std::true_type {}; 215 | template 216 | constexpr bool is_tuple_like_v = is_tuple_like>::value; 217 | 218 | // Trait for input ranges to indicate whether they output a finite number of elements. 219 | template 220 | struct is_finite : std::false_type {}; 221 | template 222 | struct is_finite> : std::bool_constant {}; 223 | template 224 | constexpr bool is_finite_v = is_finite>::value; 225 | 226 | // Trait for input ranges to indicate whether their `get()` function can be called multiple times 227 | // without advancing internal state. 228 | template 229 | struct is_idempotent : std::false_type {}; 230 | template 231 | struct is_idempotent> 232 | : std::bool_constant {}; 233 | template 234 | constexpr bool is_idempotent_v = is_idempotent>::value; 235 | 236 | // Fake RandomAccessIterator concept. Any iterator that supports advancing by an arbitrary integer 237 | // amount is considered "random access". 238 | template 239 | struct is_random_access_iterator : std::false_type {}; 240 | template 241 | struct is_random_access_iterator() += 0)>> 242 | : std::true_type {}; 243 | template 244 | constexpr bool is_random_access_iterator_v = is_random_access_iterator::value; 245 | 246 | /// Convenience notation for chaining ranges. 247 | /// 248 | /// Note that this enables `operator|` for any pair where RHS is callable with LHS as its only 249 | /// argument. Therefore, it may be best to selectively import this operator into your scope with 250 | /// `using`. If it simply cannot be imported without clashing with other definitions of `operator|`, 251 | /// you can use the alternative chaining syntax using `operator()`: `rhs(lhs)`. 252 | template < 253 | class LHS, 254 | class RHS, 255 | class = std::void_t()(std::declval()))>> 256 | constexpr auto operator|(LHS&& lhs, RHS&& rhs) noexcept { 257 | return std::forward(rhs)(std::forward(lhs)); 258 | } 259 | 260 | /*! 261 | @brief Convert input range or sink to standard iterators. 262 | */ 263 | struct input_range_iterator_end {}; 264 | template 265 | struct input_range_iterator { 266 | R range; 267 | template 268 | constexpr explicit input_range_iterator(Arg&& range) noexcept 269 | : range(std::forward(range)) {} 270 | 271 | constexpr bool operator==(input_range_iterator_end) const noexcept { 272 | return range.at_end(); 273 | } 274 | constexpr bool operator!=(input_range_iterator_end) const noexcept { 275 | return !range.at_end(); 276 | } 277 | 278 | constexpr auto& operator++() noexcept { 279 | RX_ASSERT(!range.at_end()); 280 | range.next(); 281 | return *this; 282 | } 283 | 284 | [[nodiscard]] constexpr decltype(auto) operator*() const noexcept { 285 | return range.get(); 286 | } 287 | template < 288 | class T = typename R::output_type, 289 | class = std::enable_if_t>> 290 | [[nodiscard]] constexpr auto operator-> () const noexcept { 291 | return &range.get(); 292 | } 293 | }; 294 | template 295 | input_range_iterator(R &&)->input_range_iterator>; 296 | 297 | template >> 298 | [[nodiscard]] constexpr auto begin(R&& range) noexcept { 299 | return input_range_iterator(as_input_range(std::forward(range))); 300 | } 301 | template >> 302 | [[nodiscard]] constexpr auto end(const R&) noexcept { 303 | // Note: The first argument may be moved-from, but that's OK, we just need its type. 304 | return input_range_iterator_end{}; 305 | } 306 | 307 | template 308 | struct has_advance_by : std::false_type {}; 309 | template 310 | struct has_advance_by().advance_by(size_t(0)))>> 311 | : std::true_type {}; 312 | template 313 | constexpr bool has_advance_by_v = has_advance_by::value; 314 | 315 | /// Advance a range by n steps, or until it reaches its end. 316 | /// 317 | /// This calls R::advance_by() if available. Otherwise, it calls R::next() at most n times. 318 | template 319 | size_t advance_by(R& range, size_t n) { 320 | if constexpr (has_advance_by_v) { 321 | return range.advance_by(n); 322 | } else { 323 | size_t i; 324 | for (i = 0; i < n && !range.at_end(); ++i) { 325 | range.next(); 326 | } 327 | return i; 328 | } 329 | } 330 | 331 | namespace detail { 332 | template 333 | struct invalid_type {}; 334 | } // namespace detail 335 | 336 | template 337 | constexpr void sink_one(In&& in, Out& out) noexcept { 338 | static_assert( 339 | has_emplace_back_v || has_push_back_v || has_emplace_v, 340 | "Output supports neither emplace_back(), push_back(), nor emplace()."); 341 | 342 | // Copy elements from the input to the output. If the input is tuple-like, and the output 343 | // supports emplacement, the tuple will be unpacked and passed as individual arguments to the 344 | // emplace member function on the output. Otherwise, the tuple-like object will be passed as-is. 345 | // 346 | // This means that both linear containers of tuples and associative containers like maps will 347 | // work as outputs. 348 | 349 | if constexpr (has_emplace_back_v) { 350 | if constexpr (is_tuple_like_v) { 351 | // Output has emplace_back, and the input generates tuple-like elements. Pass tuple 352 | // elements as individual arguments to emplace_back. 353 | auto unpack = [&](auto&&... args) constexpr { 354 | out.emplace_back(std::forward(args)...); 355 | }; 356 | std::apply(unpack, std::forward(in)); 357 | } else { 358 | out.emplace_back(std::forward(in)); 359 | } 360 | } else if constexpr (has_push_back_v) { 361 | out.push_back(std::forward(in)); 362 | } else if constexpr (has_emplace_v) { 363 | if constexpr (is_tuple_like_v) { 364 | // Output has emplace, and the input generates tuple-like elements. Pass tuple 365 | // elements as individual arguments to emplace. 366 | auto unpack = [&](auto&&... args) constexpr { 367 | out.emplace(std::forward(args)...); 368 | }; 369 | std::apply(unpack, std::forward(in)); 370 | } else { 371 | out.emplace(std::forward(in)); 372 | } 373 | } 374 | } 375 | 376 | /// Copy elements from an rvalue InputRange to output. 377 | /// 378 | /// If an input range is chained with a sink (like `sort()`), this will the initial population of 379 | /// the output, on which any further "sink" operators (like `uniq()`) will operate. 380 | /// 381 | /// This function only participates in overload resolution if @a in is an rvalue reference. This is 382 | /// to provide a compile-time check for reentrancy -- `sink()` should never advance the state of an 383 | /// input range that may later be reused by the caller. 384 | /// 385 | /// Note: As of C++17, no standard container can actually allocate memory in a `constexpr` context, 386 | /// but this was added in C++20. This is the only reason that this function is marked 387 | /// `constexpr`. See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0784r5.html 388 | /// 389 | /// @tparam In The range to copy from. 390 | /// @tparam Out A container that can hold the result. 391 | template 392 | constexpr void sink( 393 | In&& in, 394 | Out& out, 395 | std::enable_if_t && is_input_range_v>* = nullptr) noexcept { 396 | RX_TYPE_ASSERT(is_finite>); 397 | 398 | if constexpr (has_reserve_v) { 399 | out.reserve(in.size_hint()); 400 | } 401 | 402 | while (RX_LIKELY(!in.at_end())) { 403 | sink_one(in.get(), out); 404 | in.next(); 405 | } 406 | } 407 | 408 | /// Copy elements from an lvalue InputRange to sink. Note that the input range is copied to maintain 409 | /// reentrancy. 410 | template >> 411 | constexpr void sink(const In& in, Out& out) noexcept { 412 | auto copy = in; 413 | sink(std::move(copy), out); 414 | } 415 | 416 | /// Copy resulting elements from an rvalue sink implementation into the output container. 417 | /// 418 | /// This function is what allows operations that require temporary storage to work on the resulting 419 | /// container without allocating their own temporary storage. 420 | /// 421 | /// @tparam In The sink to copy from, typically an output modifier like `sort` or `uniq`. 422 | /// @tparam Out A container that can hold the result. 423 | template < 424 | class In, 425 | class Out, 426 | class = std::enable_if_t>, 427 | class = std::enable_if_t>> 428 | constexpr void sink(In&& in, Out& out) noexcept { 429 | std::move(in).sink(out); 430 | } 431 | 432 | /// Copy resulting elements from an lvalue range, copying the input range before modifying it. 433 | /// 434 | /// Note: This function should not be called from a Sink's implementation of `sink(out)`. Instead, 435 | /// call `sink(std::move(inner), out)` to avoid unnecessary copies. 436 | template 437 | constexpr void sink(const In& in, Out& out, std::enable_if_t>* = nullptr) noexcept { 438 | In copy = in; 439 | sink(std::move(copy), out); 440 | } 441 | 442 | /// Copy elements from a standard container into a sink. 443 | template 444 | constexpr void 445 | sink(const In& in, Out& out, std::enable_if_t>* = nullptr) noexcept { 446 | std::copy(begin(in), end(in), std::back_inserter(out)); 447 | } 448 | 449 | /*! 450 | @brief Create a range from a standard iterator pair, or a standard container. 451 | 452 | @tparam It The type of the iterator that will be incremented. 453 | @tparam EndIt The type of the end iterator, if it is different from `It`. 454 | */ 455 | template 456 | struct iterator_range { 457 | using iterator_type = It; 458 | using end_iterator_type = EndIt; 459 | using output_type = decltype(*std::declval()); 460 | 461 | // TODO: If the end-iterator type is different from the iterator type, this may not produce 462 | // a finite range. 463 | static constexpr bool is_finite = true; 464 | static constexpr bool is_idempotent = true; // we only operate on const iterators 465 | 466 | It current_; 467 | EndIt end_; 468 | 469 | template 470 | constexpr explicit iterator_range(const C& collection) noexcept 471 | : current_(begin(collection)), end_(end(collection)) {} 472 | constexpr iterator_range(It begin, EndIt end) noexcept : current_(begin), end_(end) {} 473 | 474 | constexpr void next() noexcept { 475 | RX_ASSERT(!at_end()); 476 | ++current_; 477 | } 478 | 479 | [[nodiscard]] constexpr output_type get() const noexcept { 480 | RX_ASSERT(!at_end()); 481 | return *current_; 482 | } 483 | 484 | [[nodiscard]] constexpr bool at_end() const noexcept { 485 | return current_ == end_; 486 | } 487 | 488 | constexpr size_t size_hint() const noexcept { 489 | if constexpr (std::is_same_v && is_random_access_iterator_v) { 490 | return end_ - current_; 491 | } else { 492 | return 0; 493 | } 494 | } 495 | constexpr size_t advance_by(size_t n) noexcept { 496 | if constexpr (std::is_same_v && is_random_access_iterator_v) { 497 | if (RX_LIKELY(size_t(end_ - current_) >= n)) { 498 | current_ += n; 499 | return n; 500 | } else { 501 | size_t advanced = end_ - current_; 502 | current_ = end_; 503 | return advanced; 504 | } 505 | } else { 506 | size_t i = 0; 507 | for (i = 0; i < n && current_ != end_; ++i) { 508 | next(); 509 | } 510 | return i; 511 | } 512 | } 513 | }; 514 | template 515 | iterator_range(const C& c)->iterator_range; 516 | template 517 | iterator_range(It&&, EndIt &&)->iterator_range, remove_cvref_t>; 518 | 519 | /*! 520 | @brief An input range over an internally owned `std::vector`. 521 | 522 | This is used internally when chains require temporary storage (for example, when a sink is 523 | used as an input rage). 524 | 525 | @tparam T The element type of the internal vector. Must be a non-reference value type. 526 | */ 527 | template 528 | struct vector_range { 529 | using output_type = T; 530 | static constexpr bool is_finite = true; 531 | static constexpr bool is_idempotent = true; 532 | std::vector vector_; 533 | // Note: Moving a vector does not invalidate iterators. 534 | typename std::vector::const_iterator current_; 535 | 536 | constexpr explicit vector_range(std::vector vec) noexcept 537 | : vector_(std::move(vec)), current_(vector_.begin()) {} 538 | constexpr vector_range(vector_range&&) noexcept = default; 539 | constexpr vector_range(const vector_range& other) noexcept 540 | : vector_(other.vector_), current_(vector_.begin()) {} 541 | constexpr vector_range& operator=(vector_range&&) noexcept = default; 542 | constexpr vector_range& operator=(const vector_range& other) noexcept { 543 | vector_ = other.vector_; 544 | current_ = vector_.begin(); 545 | return *this; 546 | } 547 | constexpr void next() noexcept { 548 | RX_ASSERT(!at_end()); 549 | ++current_; 550 | } 551 | constexpr const output_type& get() const& noexcept { 552 | RX_ASSERT(!at_end()); 553 | return *current_; 554 | } 555 | constexpr output_type get() && noexcept { 556 | RX_ASSERT(!at_end()); 557 | return std::move(*current_); 558 | } 559 | constexpr bool at_end() const noexcept { 560 | return current_ == vector_.end(); 561 | } 562 | constexpr size_t size_hint() const noexcept { 563 | return vector_.size(); 564 | } 565 | constexpr size_t advance_by(size_t n) noexcept { 566 | if (size_t(vector_.end() - current_) >= n) { 567 | current_ += n; 568 | return n; 569 | } else { 570 | size_t advanced = vector_.end() - current_; 571 | current_ = vector_.end(); 572 | return advanced; 573 | } 574 | } 575 | }; 576 | 577 | namespace detail { 578 | // This exists only to maintain some sanity in figuring out overload resolution for 579 | // as_input_range(). 580 | struct input_category { 581 | struct input_range {}; 582 | struct sink {}; 583 | struct other {}; 584 | }; 585 | 586 | template 587 | struct input_category_of { 588 | using type = input_category::other; 589 | }; 590 | template 591 | struct input_category_of>> { 592 | using type = input_category::input_range; 593 | }; 594 | template 595 | struct input_category_of>> { 596 | using type = input_category::sink; 597 | }; 598 | 599 | template 600 | using input_category_of_t = typename input_category_of>::type; 601 | 602 | template 603 | constexpr decltype(auto) as_input_range_impl(input_category::input_range, T&& range) noexcept { 604 | return std::forward(range); 605 | } 606 | 607 | template 608 | constexpr auto as_input_range_impl(input_category::sink, T&& input) noexcept { 609 | static_assert( 610 | !std::is_lvalue_reference_v, 611 | "Please only convert sinks to input ranges with rvalue references (for efficiency)."); 612 | using RX_NAMESPACE::sink; 613 | // Store the output in a temporary vector. 614 | using output_type = remove_cvref_t::output_type>; 615 | std::vector vec; 616 | sink(std::move(input), vec); 617 | return vector_range{std::move(vec)}; 618 | } 619 | 620 | template 621 | constexpr auto as_input_range_impl(input_category::other, const T& container) noexcept { 622 | return iterator_range(container.begin(), container.end()); 623 | } 624 | 625 | template 626 | constexpr auto as_input_range_impl(input_category::other, T (&arr)[N]) noexcept { 627 | return iterator_range(std::begin(arr), std::end(arr)); 628 | } 629 | } // namespace detail 630 | 631 | template 632 | constexpr decltype(auto) as_input_range(R&& range) { 633 | using category = detail::input_category_of_t; 634 | return detail::as_input_range_impl(category{}, std::forward(range)); 635 | } 636 | 637 | /*! 638 | @brief Wrap an non-idempotent range to make it idempotent. 639 | 640 | Some combinators expect an idempotent input to avoid internal storage unless it is actually 641 | needed. This is a convenience wrapper for those combinators, which wraps a non-idempotent 642 | combinator. 643 | 644 | Idempotency is achieved by storing the "current" element in internal, temporary storage. For 645 | this reason, the output type of `R` must be copy-constructible. 646 | 647 | @tparam R The inner (non-idempotent) range. 648 | */ 649 | template 650 | struct idempotent_range { 651 | static_assert(!is_idempotent_v); // Don't wrap an already idempotent range. 652 | 653 | static constexpr bool is_finite = is_finite_v; 654 | static constexpr bool is_idempotent = true; 655 | using element_type = remove_cvref_t; 656 | using storage_type = RX_OPTIONAL; 657 | using output_type = const element_type&; 658 | 659 | R inner_; 660 | storage_type current_; 661 | 662 | template 663 | idempotent_range(Q&& inner) : inner_(std::forward(inner)) { 664 | if (!inner_.at_end()) { 665 | current_.emplace(inner_.get()); 666 | } 667 | } 668 | 669 | constexpr output_type get() const noexcept { 670 | RX_ASSERT(!at_end()); 671 | return *current_; 672 | } 673 | 674 | constexpr void next() noexcept { 675 | RX_ASSERT(!at_end()); 676 | inner_.next(); 677 | if (!inner_.at_end()) { 678 | current_.emplace(inner_.get()); 679 | } 680 | } 681 | 682 | constexpr bool at_end() const noexcept { 683 | return inner_.at_end(); 684 | } 685 | 686 | constexpr size_t size_hint() const noexcept { 687 | return inner_.size_hint(); 688 | } 689 | }; 690 | template 691 | idempotent_range(R &&)->idempotent_range>; 692 | 693 | // Convenience function to make non-idempotent input ranges idempotent. 694 | template 695 | constexpr auto as_idempotent_input_range(R&& range) { 696 | // Convert the input to an input range, and then wrap it in idempotent_range if it isn't already 697 | // idempotent. 698 | auto input_range = as_input_range(std::forward(range)); 699 | using range_type = decltype(input_range); 700 | if constexpr (is_idempotent_v) { 701 | return input_range; 702 | } else { 703 | return idempotent_range(std::move(input_range)); 704 | } 705 | } 706 | 707 | // Get the equivalent range type of some type. This is the return type of `as_input_range()` without 708 | // cvref-qualifiers when called with T as the argument (which may be any lvalue or rvalue type). The 709 | // reason that we can't juse use remove_cvref_t is that some inputs may be converted to a 710 | // different type by `as_input_range()`, such as standard containers. 711 | template 712 | using get_range_type_t = remove_cvref_t()))>; 713 | 714 | // This function is analogous to get_range_type_t. Instead the result of as_input_range() it tells 715 | // you the result of as_idempotent_input_range(). 716 | template 717 | using get_idempotent_range_type_t = 718 | remove_cvref_t()))>; 719 | 720 | // Get the output type of T as if it was converted to a range. This is the output type of the input 721 | // range after conversion through `as_input_range()`. 722 | template 723 | using get_output_type_of_t = typename get_range_type_t::output_type; 724 | 725 | /*! 726 | @brief Generate a series of values from a function. 727 | 728 | Produces an infinite number of outputs, calling F to produce each output. 729 | 730 | Combine with things like take(), until(), etc. to create a finite range. 731 | 732 | Note: The function does not have to be idempotent, but subsequent chained combinators may cache 733 | the return value if they need idempotency. 734 | 735 | @tparam F A function that will be invoked to produce an output. 736 | */ 737 | template 738 | struct generate { 739 | using output_type = decltype(std::declval()()); 740 | static constexpr bool is_finite = false; 741 | static constexpr bool is_idempotent = false; 742 | 743 | mutable F func; 744 | 745 | template 746 | constexpr explicit generate(Fx&& func) : func(std::forward(func)) {} 747 | 748 | constexpr void next() noexcept {} 749 | 750 | [[nodiscard]] constexpr bool at_end() const noexcept { 751 | return false; 752 | } 753 | 754 | [[nodiscard]] constexpr decltype(auto) get() const { 755 | return func(); 756 | } 757 | 758 | constexpr size_t size_hint() const noexcept { 759 | return std::numeric_limits::max(); 760 | } 761 | }; 762 | template 763 | generate(F &&)->generate>; 764 | 765 | /*! 766 | @brief Sequence generator. 767 | 768 | Create an infinite sequence of T, advancing each iteration by a value of type U. 769 | 770 | T is expected to be initializable by an integral constant, and to have well-defined 771 | `operator+=(U)`. 772 | */ 773 | template 774 | struct seq { 775 | T current; 776 | U step; 777 | 778 | using output_type = T; 779 | static constexpr bool is_finite = false; 780 | static constexpr bool is_idempotent = true; 781 | 782 | constexpr explicit seq(T init = T{}, U step = U{} + 1) : current(init), step(step) {} 783 | 784 | constexpr void next() { 785 | current += step; 786 | } 787 | [[nodiscard]] constexpr output_type get() const { 788 | return current; 789 | } 790 | [[nodiscard]] constexpr bool at_end() const noexcept { 791 | return false; 792 | } 793 | constexpr size_t size_hint() const noexcept { 794 | return std::numeric_limits::max(); 795 | } 796 | }; 797 | seq()->seq; 798 | template 799 | seq(T)->seq; 800 | template 801 | seq(T, U)->seq; 802 | 803 | /*! 804 | @brief Generate infinite copies of type T. 805 | */ 806 | template 807 | struct fill { 808 | T value; 809 | constexpr explicit fill(T value) noexcept : value(std::move(value)) {} 810 | 811 | using output_type = T; 812 | static constexpr bool is_finite = false; 813 | static constexpr bool is_idempotent = true; 814 | 815 | constexpr void next() {} 816 | 817 | [[nodiscard]] constexpr output_type get() const { 818 | return value; 819 | } 820 | 821 | [[nodiscard]] constexpr bool at_end() const noexcept { 822 | return false; 823 | } 824 | 825 | constexpr size_t size_hint() const noexcept { 826 | return std::numeric_limits::max(); 827 | } 828 | }; 829 | template 830 | fill(T &&)->fill>; 831 | 832 | /*! 833 | @brief Transform a range of values by a function F. 834 | 835 | Each output is produced by passing the output of the inner range to the function F. 836 | */ 837 | template 838 | struct transform { 839 | F func; 840 | explicit constexpr transform(F func) noexcept : func(std::move(func)) {} 841 | 842 | template 843 | struct Range { 844 | InputRange input_; 845 | transform transform_; 846 | using output_type = decltype(std::declval()((input_.get()))); 847 | static constexpr bool is_finite = is_finite_v; 848 | static constexpr bool is_idempotent = false; // `func` is called each time `get` is called 849 | 850 | constexpr Range(InputRange input, transform transform) noexcept 851 | : input_(std::move(input)), transform_(std::move(transform)) {} 852 | constexpr void next() { 853 | RX_ASSERT(!at_end()); 854 | input_.next(); 855 | } 856 | constexpr output_type get() const { 857 | RX_ASSERT(!at_end()); 858 | return transform_.func(input_.get()); 859 | } 860 | constexpr bool at_end() const { 861 | return input_.at_end(); 862 | } 863 | constexpr size_t size_hint() const noexcept { 864 | return input_.size_hint(); 865 | } 866 | constexpr size_t advance_by(size_t n) noexcept { 867 | using RX_NAMESPACE::advance_by; 868 | return advance_by(input_, n); 869 | } 870 | }; 871 | 872 | // Since F may be either cheap or expensive to copy, provide overloads for both const-refs and 873 | // rvalue-refs. 874 | 875 | template 876 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const& noexcept { 877 | using Inner = get_range_type_t; 878 | return Range{as_input_range(std::forward(input)), *this}; 879 | } 880 | template 881 | [[nodiscard]] constexpr auto operator()(InputRange&& input) && noexcept { 882 | using Inner = get_range_type_t; 883 | return Range{as_input_range(std::forward(input)), std::move(*this)}; 884 | } 885 | }; 886 | template 887 | transform(F &&)->transform>; 888 | 889 | /*! 890 | @brief Filter range by predicate. 891 | 892 | If the predicate returns true, the element will be included in the output. Otherwise, it will 893 | not. 894 | 895 | @tparam P A predicate returning a value convertible to bool 896 | */ 897 | template 898 | struct filter { 899 | P pred; 900 | constexpr explicit filter(P pred) : pred(std::move(pred)) {} 901 | 902 | template 903 | struct Range { 904 | static_assert(is_idempotent_v); 905 | using output_type = remove_cvref_t; 906 | static constexpr bool is_finite = is_finite_v; 907 | static constexpr bool is_idempotent = true; 908 | 909 | R input_; 910 | P pred_; 911 | 912 | template 913 | constexpr Range(R input, Q&& pred) 914 | : input_(std::move(input)), pred_(std::forward(pred)) { 915 | // Skip initial non-matching outputs. 916 | while (RX_LIKELY(!input_.at_end()) && !pred_(input_.get())) { 917 | input_.next(); 918 | } 919 | } 920 | 921 | [[nodiscard]] constexpr decltype(auto) get() const& noexcept { 922 | RX_ASSERT(!at_end()); 923 | return input_.get(); 924 | } 925 | 926 | [[nodiscard]] constexpr decltype(auto) get() && noexcept { 927 | RX_ASSERT(!at_end()); 928 | return input_.get(); 929 | } 930 | 931 | constexpr void next() noexcept { 932 | RX_ASSERT(!at_end()); 933 | // XXX: For some reason, compilers are not able to merge this loop condition with 934 | // conditions on the input range, causing many more branches than necessary. (Tested 935 | // with MSVC, Clang, GCC.) 936 | // 937 | // This means that filter() can be much much slower (4x with Clang/GCC, 10x with MSVC) 938 | // in tight loops. 939 | input_.next(); 940 | while (!at_end() && !pred_(input_.get())) { 941 | input_.next(); 942 | } 943 | } 944 | 945 | [[nodiscard]] constexpr bool at_end() const noexcept { 946 | return input_.at_end(); 947 | } 948 | 949 | [[nodiscard]] constexpr size_t size_hint() const noexcept { 950 | return input_.size_hint(); 951 | } 952 | }; 953 | 954 | // Since F may be either cheap or expensive to copy, provide overloads for both const-refs and 955 | // rvalue-refs. 956 | 957 | template 958 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const& { 959 | using Inner = get_idempotent_range_type_t; 960 | return Range{as_idempotent_input_range(std::forward(input)), pred}; 961 | } 962 | template 963 | [[nodiscard]] constexpr auto operator()(InputRange&& input) && { 964 | using Inner = get_idempotent_range_type_t; 965 | return Range{as_idempotent_input_range(std::forward(input)), 966 | std::move(pred)}; 967 | } 968 | }; 969 | template 970 | filter(F &&)->filter>; 971 | 972 | /*! 973 | @brief Create a range that produces at most N elements from the beginning of its input. 974 | */ 975 | struct take { 976 | const size_t n; 977 | constexpr explicit take(size_t n) noexcept : n(n) {} 978 | 979 | template 980 | struct Range { 981 | using output_type = typename R::output_type; 982 | static constexpr bool is_finite = true; 983 | static constexpr bool is_idempotent = is_idempotent_v; 984 | 985 | R inner; 986 | const size_t n; 987 | size_t i = 0; 988 | 989 | constexpr Range(R inner, size_t n) noexcept : inner(std::move(inner)), n(n) {} 990 | 991 | [[nodiscard]] constexpr output_type get() const noexcept { 992 | RX_ASSERT(!at_end()); 993 | return inner.get(); 994 | } 995 | 996 | constexpr void next() noexcept { 997 | RX_ASSERT(!at_end()); 998 | ++i; 999 | inner.next(); 1000 | } 1001 | 1002 | [[nodiscard]] constexpr bool at_end() const noexcept { 1003 | return i >= n || inner.at_end(); 1004 | } 1005 | 1006 | constexpr size_t size_hint() const noexcept { 1007 | return n; 1008 | } 1009 | 1010 | constexpr size_t advance_by(size_t m) noexcept { 1011 | using RX_NAMESPACE::advance_by; 1012 | // Addition beyond n and integer overflow both clamp to the end. 1013 | size_t check = i + m; 1014 | bool int_did_overflow = check < i; 1015 | bool bounds_did_overflow = check > n; 1016 | 1017 | size_t advanced = 0; 1018 | if (!int_did_overflow && !bounds_did_overflow) { 1019 | advanced = m; 1020 | advance_by(inner, advanced); 1021 | i += m; 1022 | } else if (i != n) { 1023 | advanced = n - i; 1024 | advance_by(inner, advanced); 1025 | i = n; 1026 | } 1027 | RX_ASSERT((!int_did_overflow && !bounds_did_overflow) || at_end()); 1028 | return advanced; 1029 | } 1030 | }; 1031 | 1032 | template 1033 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const { 1034 | using Inner = get_range_type_t; 1035 | return Range{as_input_range(std::forward(input)), n}; 1036 | } 1037 | }; 1038 | 1039 | /*! 1040 | @brief Generate N copies of type T. 1041 | 1042 | This is equivalent to `fill(value) | take(n)`. 1043 | */ 1044 | template 1045 | constexpr auto fill_n(size_t n, T&& v) { 1046 | return fill(std::forward(v)) | take(n); 1047 | } 1048 | 1049 | /// Alias for `take()`. 1050 | using first_n = take; 1051 | 1052 | /*! 1053 | @brief Create a range that skips the first N elements of its input. 1054 | */ 1055 | struct skip_n { 1056 | const size_t n; 1057 | constexpr explicit skip_n(size_t n) noexcept : n(n) {} 1058 | 1059 | template 1060 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const { 1061 | auto range = as_input_range(std::forward(input)); 1062 | advance_by(range, n); 1063 | return range; 1064 | } 1065 | }; 1066 | 1067 | /*! 1068 | @brief Produce elements from the input until the predicate returns true. 1069 | 1070 | Note: The element for which the predicate returned true is not included in the output. 1071 | 1072 | @tparam P A predicate function callable with elements of the input as its argument, returning a 1073 | boolean. 1074 | */ 1075 | template 1076 | struct until { 1077 | P pred; 1078 | constexpr explicit until(P pred) : pred(std::move(pred)) {} 1079 | 1080 | template 1081 | struct Range { 1082 | static_assert(is_idempotent_v); 1083 | using output_type = remove_cvref_t; 1084 | static constexpr bool is_finite = true; 1085 | static constexpr bool is_idempotent = false; // get() is called twice per iteration 1086 | 1087 | R input; 1088 | P pred; 1089 | bool done = false; 1090 | 1091 | constexpr Range(R input, P pred) noexcept 1092 | : input(std::move(input)), pred(std::move(pred)), done(input.at_end()) { 1093 | if (!done) { 1094 | done = pred(input.get()); 1095 | } 1096 | } 1097 | 1098 | [[nodiscard]] constexpr decltype(auto) get() const& noexcept { 1099 | RX_ASSERT(!at_end()); 1100 | return input.get(); 1101 | } 1102 | 1103 | [[nodiscard]] constexpr decltype(auto) get() && noexcept { 1104 | RX_ASSERT(!at_end()); 1105 | return input.get(); 1106 | } 1107 | 1108 | constexpr void next() noexcept { 1109 | RX_ASSERT(!at_end()); 1110 | input.next(); 1111 | done = input.at_end(); 1112 | if (!done) { 1113 | done = pred(input.get()); 1114 | } 1115 | } 1116 | 1117 | [[nodiscard]] constexpr bool at_end() const noexcept { 1118 | return done; 1119 | } 1120 | 1121 | constexpr size_t size_hint() const noexcept { 1122 | return 0; 1123 | } 1124 | }; 1125 | 1126 | template 1127 | constexpr auto operator()(InputRange&& input) const& { 1128 | using Inner = get_idempotent_range_type_t; 1129 | return Range{as_idempotent_input_range(std::forward(input)), pred}; 1130 | } 1131 | 1132 | template 1133 | constexpr auto operator()(InputRange&& input) && { 1134 | using Inner = get_idempotent_range_type_t; 1135 | return Range{as_idempotent_input_range(std::forward(input)), 1136 | std::move(pred)}; 1137 | } 1138 | }; 1139 | template 1140 | until(P &&)->until>; 1141 | 1142 | template 1143 | struct ZipRange { 1144 | static_assert(sizeof...(Inputs) > 0); 1145 | 1146 | std::tuple inputs; 1147 | using output_type = std::tuple...>; 1148 | static constexpr bool is_finite = (is_finite_v || ...); 1149 | static constexpr bool is_idempotent = (is_idempotent_v && ...); 1150 | 1151 | template 1152 | constexpr explicit ZipRange(std::tuple&& tuple) : inputs(tuple) {} 1153 | 1154 | [[nodiscard]] constexpr output_type get() const noexcept { 1155 | RX_ASSERT(!at_end()); 1156 | return _get(std::index_sequence_for{}); 1157 | } 1158 | 1159 | constexpr void next() noexcept { 1160 | RX_ASSERT(!at_end()); 1161 | _next(std::index_sequence_for{}); 1162 | } 1163 | 1164 | [[nodiscard]] constexpr bool at_end() const noexcept { 1165 | return _at_end(std::index_sequence_for{}); 1166 | } 1167 | 1168 | constexpr size_t size_hint() const noexcept { 1169 | return _size_hint(std::index_sequence_for{}); 1170 | } 1171 | 1172 | constexpr size_t advance_by(size_t n) noexcept { 1173 | return _advance_by(std::index_sequence_for{}, n); 1174 | } 1175 | 1176 | private: 1177 | template 1178 | [[nodiscard]] constexpr output_type _get(std::index_sequence) const noexcept { 1179 | return output_type(std::forward_as_tuple(std::get(inputs).get()...)); 1180 | } 1181 | 1182 | template 1183 | constexpr void _next(std::index_sequence) noexcept { 1184 | (std::get(inputs).next(), ...); 1185 | } 1186 | 1187 | template 1188 | [[nodiscard]] constexpr bool _at_end(std::index_sequence) const noexcept { 1189 | return (std::get(inputs).at_end() || ...); 1190 | } 1191 | 1192 | template 1193 | constexpr size_t _size_hint(std::index_sequence) const noexcept { 1194 | return std::min({std::get(inputs).size_hint()...}); 1195 | } 1196 | 1197 | template 1198 | constexpr size_t _advance_by(std::index_sequence, size_t n) noexcept { 1199 | using RX_NAMESPACE::advance_by; 1200 | return std::min({advance_by(std::get(inputs), n)...}); 1201 | } 1202 | }; 1203 | template 1204 | ZipRange(std::tuple &&)->ZipRange...>; 1205 | 1206 | /*! 1207 | @brief Zip two or more ranges. 1208 | 1209 | Until none of the ranges are at end, produce a tuple of an element from each range. 1210 | 1211 | The ranges are not required to produce the same number of elements, but elements will only be 1212 | produced until one of the ranges reaches its end. 1213 | */ 1214 | template 1215 | [[nodiscard]] constexpr auto zip(Inputs&&... inputs) noexcept { 1216 | // For some reason, argument deduction doesn't work here. 1217 | return ZipRange(std::forward_as_tuple(as_input_range(std::forward(inputs))...)); 1218 | } 1219 | 1220 | /*! 1221 | @brief Enumerate elements of the input sequentially. 1222 | 1223 | Equivalent to `zip(seq(), inputs...)`. 1224 | 1225 | The inputs are not required to produce the same number of elements, but elements will only be 1226 | produced until one of the ranges reaches its end. 1227 | */ 1228 | template 1229 | [[nodiscard]] constexpr auto enumerate(Inputs&&... inputs) noexcept { 1230 | return ZipRange( 1231 | std::forward_as_tuple(seq(), as_input_range(std::forward(inputs))...)); 1232 | } 1233 | 1234 | /*! 1235 | @brief Produce sequential groups of exactly N elements. 1236 | 1237 | The output type is a range of exactly N element. 1238 | 1239 | If the input produces a number of inputs that is not divisible by N, elements at the end will be 1240 | skipped. 1241 | */ 1242 | struct in_groups_of_exactly { 1243 | const size_t n; 1244 | 1245 | constexpr explicit in_groups_of_exactly(size_t n) noexcept : n(n) { 1246 | RX_ASSERT(n > 0); 1247 | } 1248 | 1249 | template 1250 | struct Range { 1251 | using element_type = remove_cvref_t; 1252 | using output_type = remove_cvref_t() | take(1))>; 1253 | static constexpr bool is_finite = is_finite_v; 1254 | static constexpr bool is_idempotent = true; // ... but we output potentially non-idempotent 1255 | // ranges. 1256 | 1257 | R inner; 1258 | const size_t n; 1259 | RX_OPTIONAL storage; 1260 | 1261 | constexpr explicit Range(R inner, size_t n) : inner(std::move(inner)), n(n) { 1262 | next(); 1263 | } 1264 | 1265 | 1266 | [[nodiscard]] constexpr output_type get() const& noexcept { 1267 | RX_ASSERT(!at_end()); 1268 | return *storage; 1269 | } 1270 | 1271 | [[nodiscard]] constexpr output_type get() && noexcept { 1272 | RX_ASSERT(!at_end()); 1273 | return std::move(*storage); 1274 | } 1275 | 1276 | [[nodiscard]] constexpr bool at_end() const noexcept { 1277 | return bool(!storage); 1278 | } 1279 | 1280 | constexpr void next() noexcept { 1281 | storage.reset(); 1282 | if (RX_LIKELY(!inner.at_end())) { 1283 | auto copy = as_input_range(inner); 1284 | using RX_NAMESPACE::advance_by; 1285 | if (RX_LIKELY(n > 1)) { 1286 | advance_by(inner, n - 1); 1287 | if (inner.at_end()) { 1288 | // end was reached before we could produce a whole group. 1289 | storage.reset(); 1290 | return; 1291 | } 1292 | } 1293 | // n cannot be zero 1294 | inner.next(); 1295 | storage.emplace(std::move(copy) | take(n)); 1296 | } 1297 | } 1298 | 1299 | constexpr size_t size_hint() const noexcept { 1300 | return inner.size_hint() / n; 1301 | } 1302 | 1303 | constexpr size_t advance_by(size_t m) noexcept { 1304 | using RX_NAMESPACE::advance_by; 1305 | size_t advanced = advance_by(inner, n * m); 1306 | next(); 1307 | return advanced / n; 1308 | } 1309 | }; 1310 | 1311 | template 1312 | [[nodiscard]] auto operator()(R&& input) const { 1313 | using Inner = get_range_type_t; 1314 | return Range{as_input_range(std::forward(input)), n}; 1315 | } 1316 | }; 1317 | 1318 | /*! 1319 | @brief Produce sequential groups of N or fewer elements. 1320 | 1321 | The output type is a range producing at most N elements. If the input produces a number of 1322 | inputs that is not divisible by N, the last group produces will have a size < N. 1323 | */ 1324 | struct in_groups_of { 1325 | size_t n; 1326 | 1327 | constexpr explicit in_groups_of(size_t n) noexcept : n(n) { 1328 | RX_ASSERT(n > 0); 1329 | } 1330 | 1331 | template 1332 | struct Range { 1333 | using element_type = remove_cvref_t; 1334 | using output_type = decltype(std::declval() | take(1)); 1335 | static constexpr bool is_finite = is_finite_v; 1336 | static constexpr bool is_idempotent = true; // ... but we output potentially non-idempotent 1337 | // ranges. 1338 | 1339 | R inner; 1340 | RX_OPTIONAL storage; 1341 | size_t n; 1342 | 1343 | Range(R inner, size_t n) : inner(std::move(inner)), n(n) { 1344 | next(); 1345 | } 1346 | 1347 | [[nodiscard]] output_type get() const& noexcept { 1348 | RX_ASSERT(!at_end()); 1349 | return *storage; 1350 | } 1351 | 1352 | [[nodiscard]] output_type get() && noexcept { 1353 | RX_ASSERT(!at_end()); 1354 | return std::move(*storage); 1355 | } 1356 | 1357 | [[nodiscard]] bool at_end() const noexcept { 1358 | return bool(!storage); 1359 | } 1360 | 1361 | constexpr void next() noexcept { 1362 | storage.reset(); 1363 | if (RX_LIKELY(!inner.at_end())) { 1364 | auto copy = as_input_range(inner); 1365 | using RX_NAMESPACE::advance_by; 1366 | advance_by(inner, n); 1367 | storage.emplace(std::move(copy) | take(n)); 1368 | } 1369 | } 1370 | 1371 | [[nodiscard]] size_t size_hint() const noexcept { 1372 | // Round up. 1373 | return (inner.size_hint() + n - 1) / n; 1374 | } 1375 | 1376 | constexpr size_t advance_by(size_t m) noexcept { 1377 | using RX_NAMESPACE::advance_by; 1378 | size_t advanced = advance_by(inner, n * m); 1379 | next(); 1380 | // Round up 1381 | return (advanced + n - 1) / n; 1382 | } 1383 | }; 1384 | 1385 | template 1386 | [[nodiscard]] constexpr auto operator()(R&& input) const { 1387 | using Inner = get_range_type_t; 1388 | return Range{as_input_range(std::forward(input)), n}; 1389 | } 1390 | }; 1391 | 1392 | /*! 1393 | @brief Produce consecutive groups of elements for which P returns the same value, according to 1394 | the Compare function. 1395 | 1396 | The output type is a range of the same types as the input. The size of the range is 1397 | indeterminate, but always greater than 0. 1398 | 1399 | @tparam P The discriminant function. When the return value of this function changes, according 1400 | to Compare, a new group is emitted. 1401 | @tparam Compare The compare function used to compare return values of P. 1402 | @tparam group_size_hint A hint about the average group size. It is 8 by default (somewhat 1403 | arbitrarily). If smaller or larger groups are expected, this parameter 1404 | can be tuned to avoid either preallocating too little or too much, 1405 | respectively, when outputting to a sink that supports `reserve()`. 1406 | */ 1407 | template , size_t group_size_hint = 8> 1408 | struct group_adjacent_by : private Compare { 1409 | P pred; 1410 | constexpr explicit group_adjacent_by(P pred, Compare cmp) 1411 | : Compare(std::move(cmp)), pred(std::move(pred)) {} 1412 | 1413 | // Only enabled if C is default-constructible. 1414 | template 1415 | constexpr explicit group_adjacent_by(P pred) : pred(std::move(pred)) {} 1416 | 1417 | template 1418 | struct Range : private Compare { 1419 | using element_type = remove_cvref_t; 1420 | using output_type = remove_cvref_t() | take(1))>; 1421 | static constexpr bool is_finite = is_finite_v; 1422 | static constexpr bool is_idempotent = true; // we have internal storage 1423 | 1424 | R inner; 1425 | P pred; 1426 | // rationale: most ranges in this library are not assignable 1427 | RX_OPTIONAL storage; 1428 | 1429 | Range(R inner, P pred, Compare cmp) 1430 | : Compare(std::move(cmp)), inner(std::move(inner)), pred(std::move(pred)) { 1431 | next(); 1432 | } 1433 | 1434 | [[nodiscard]] constexpr output_type get() const noexcept { 1435 | RX_ASSERT(!at_end()); 1436 | return *storage; 1437 | } 1438 | 1439 | [[nodiscard]] constexpr bool at_end() const noexcept { 1440 | return !bool(storage); 1441 | } 1442 | 1443 | void next() noexcept { 1444 | // No at_end() test: method is called in constructor. 1445 | if (RX_LIKELY(!inner.at_end())) { 1446 | fill_group(); 1447 | } else { 1448 | storage.reset(); 1449 | } 1450 | } 1451 | 1452 | [[nodiscard]] size_t size_hint() const noexcept { 1453 | // It's impossible to say how many groups we will produce, but we can take a guess. 1454 | // Somewhat arbitrarily, we just assume that we will produce groups of about 8. 1455 | if constexpr (R::is_finite) { 1456 | const size_t n = inner.size_hint(); 1457 | return n / group_size_hint + 1; 1458 | } else { 1459 | return std::numeric_limits::max(); 1460 | } 1461 | } 1462 | 1463 | constexpr void fill_group() { 1464 | auto copy = as_input_range(inner); 1465 | const auto& current = inner.get(); // lifetime extension for value types 1466 | auto p = pred(current); 1467 | size_t n = 0; 1468 | const Compare& cmp = *this; 1469 | do { 1470 | ++n; 1471 | inner.next(); 1472 | if (inner.at_end()) { 1473 | break; 1474 | } 1475 | } while (cmp(p, pred(inner.get()))); 1476 | storage.emplace(std::move(copy) | take(n)); 1477 | } 1478 | }; 1479 | 1480 | template 1481 | [[nodiscard]] constexpr auto operator()(R&& input) const { 1482 | using Inner = get_range_type_t; 1483 | const Compare& cmp = *this; 1484 | return Range{as_input_range(std::forward(input)), pred, cmp}; 1485 | } 1486 | }; 1487 | 1488 | template 1489 | group_adjacent_by(P &&)->group_adjacent_by>; 1490 | template 1491 | group_adjacent_by(P&&, Compare &&)->group_adjacent_by, remove_cvref_t>; 1492 | 1493 | /*! 1494 | @brief Sink element(s) into an optional. 1495 | 1496 | If the input produces 1 or more output, the last element produced will be placed in the result. 1497 | 1498 | If the input produces 0 outputs, the result will be nullopt. 1499 | */ 1500 | struct to_opt { 1501 | template 1502 | [[nodiscard]] constexpr auto operator()(R&& input) noexcept { 1503 | RX_OPTIONAL> result; 1504 | sink(std::forward(input), result); 1505 | return result; 1506 | } 1507 | }; 1508 | 1509 | /*! 1510 | @brief Get the first element of a range, if it exists. 1511 | 1512 | The result is an optional containing the first element, or none if the range did not produce any 1513 | outputs. 1514 | */ 1515 | struct first { 1516 | template 1517 | [[nodiscard]] constexpr auto operator()(R&& input) const { 1518 | return to_opt()(take(1)(std::forward(input))); 1519 | } 1520 | }; 1521 | 1522 | struct to_vector { 1523 | template 1524 | [[nodiscard]] constexpr auto operator()(R&& range) const { 1525 | std::vector>> vec; 1526 | sink(std::forward(range), vec); 1527 | return vec; 1528 | } 1529 | }; 1530 | 1531 | /*! 1532 | @brief Convert range into std::vector. 1533 | */ 1534 | struct to_list { 1535 | template 1536 | [[nodiscard]] constexpr auto operator()(R&& range) const { 1537 | std::list>> list; 1538 | sink(std::forward(range), list); 1539 | return list; 1540 | } 1541 | }; 1542 | 1543 | /*! 1544 | @brief Convert range into std::map. 1545 | 1546 | The input range is expected to produce values conforming to the "tuple" concept, i.e., 1547 | std::get<0>(value) and std::get<1>(value) are defined, as well as std::tuple_element<0> and 1548 | std::tuple_element<1>. 1549 | 1550 | The first element of the output will be the key, and the second element will be the value. 1551 | */ 1552 | struct to_map { 1553 | template 1554 | [[nodiscard]] constexpr auto operator()(R&& range) const { 1555 | using output_type = get_output_type_of_t; 1556 | using key_type = remove_cvref_t>; 1557 | using value_type = remove_cvref_t>; 1558 | std::map result; 1559 | // Note: Sink only modifies the input if it actually is an rvalue reference. 1560 | sink(std::forward(range), result); 1561 | return result; 1562 | } 1563 | }; 1564 | 1565 | /*! 1566 | @brief Convert range into std::set. 1567 | */ 1568 | struct to_set { 1569 | template 1570 | [[nodiscard]] constexpr auto operator()(R&& range) const { 1571 | using output_type = remove_cvref_t>; 1572 | std::set result; 1573 | // Note: Sink only modifies the input if it actually is an rvalue reference. 1574 | sink(std::forward(range), result); 1575 | return result; 1576 | } 1577 | }; 1578 | 1579 | /*! 1580 | @brief Count number of outputs of the input range. 1581 | */ 1582 | struct count { 1583 | template 1584 | [[nodiscard]] constexpr size_t operator()(R&& range) const { 1585 | using range_type = remove_cvref_t(range)))>; 1586 | RX_TYPE_ASSERT(is_finite); 1587 | // Copying for reentrancy. 1588 | auto copy = as_input_range(std::forward(range)); 1589 | size_t n = 0; 1590 | while (RX_LIKELY(!copy.at_end())) { 1591 | n += 1; 1592 | copy.next(); 1593 | } 1594 | return n; 1595 | } 1596 | }; 1597 | 1598 | /*! 1599 | @brief Binary left-fold of a range. 1600 | 1601 | Produces a single value created from consecutive calls to the provided function. 1602 | 1603 | @tparam T The type of the initial value and internal accumulator. 1604 | @tparam F The 1605 | */ 1606 | template 1607 | struct foldl { 1608 | T init; 1609 | F func; 1610 | 1611 | constexpr foldl(T init, F func) 1612 | : init(std::move(init)), func(std::move(func)) {} 1613 | 1614 | template 1615 | constexpr T operator()(InputRange&& input) { 1616 | // Copying for reentrancy. 1617 | auto copy = as_input_range(std::forward(input)); 1618 | RX_TYPE_ASSERT(is_finite); 1619 | T accum = init; 1620 | if (RX_UNLIKELY(copy.at_end())) 1621 | return accum; 1622 | do { 1623 | accum = func(accum, copy.get()); 1624 | copy.next(); 1625 | } while (RX_LIKELY(!copy.at_end())); 1626 | return accum; 1627 | } 1628 | }; 1629 | template 1630 | foldl(T&&, F &&)->foldl, remove_cvref_t>; 1631 | 1632 | /*! 1633 | @brief Sum the input range. 1634 | 1635 | The output type of the input range is required to implement `operator+`. 1636 | */ 1637 | struct sum { 1638 | constexpr sum() = default; 1639 | template 1640 | [[nodiscard]] constexpr auto operator()(R&& input) const noexcept { 1641 | using type = remove_cvref_t>; 1642 | // Note: foldl is reentrant. 1643 | auto folder = foldl( 1644 | type{}, [](type accum, auto&& x) constexpr { return accum + x; }); 1645 | return std::move(folder)(std::forward(input)); 1646 | } 1647 | }; 1648 | 1649 | /*! 1650 | @brief Produce the max value of the input range as an optional. 1651 | 1652 | If the input is empty, "none" is returned. 1653 | 1654 | The output type of the input range is compared using an instance of Compare, which is 1655 | `std::less` by default. 1656 | 1657 | @tparam Compare The comparison function. 1658 | */ 1659 | template > 1660 | struct max : private Compare { 1661 | constexpr max(Compare cmp) noexcept : Compare(std::move(cmp)) {} 1662 | constexpr max() = default; 1663 | 1664 | template 1665 | [[nodiscard]] constexpr auto operator()(R&& range) const noexcept { 1666 | using type = remove_cvref_t>; 1667 | decltype(auto) input = as_input_range(std::forward(range)); 1668 | using range_type = decltype(input); 1669 | if (RX_LIKELY(!input.at_end())) { 1670 | type first = input.get(); 1671 | input.next(); 1672 | auto folder = foldl( 1673 | std::move(first), [this](auto&& accum, auto&& x) constexpr { 1674 | // Note: Can't use std::max(), because it takes the comparison function 1675 | // by-value. 1676 | const Compare& cmp = *this; 1677 | const auto& accum_ = accum; 1678 | const auto& x_ = x; 1679 | return cmp(x_, accum_) ? std::forward(accum) 1680 | : std::forward(x); 1681 | }); 1682 | return RX_OPTIONAL{std::move(folder)(std::forward(input))}; 1683 | } else { 1684 | // GCC 9.1 mistakenly thinks the return value is uninitialized unless we declare it as a 1685 | // local first. 1686 | const RX_OPTIONAL none; 1687 | return none; 1688 | } 1689 | } 1690 | }; 1691 | max()->max<>; 1692 | 1693 | template > 1694 | struct min : private Compare { 1695 | constexpr min(Compare cmp) noexcept : Compare(std::move(cmp)) {} 1696 | constexpr min() = default; 1697 | 1698 | template 1699 | [[nodiscard]] constexpr auto operator()(R&& range) const noexcept { 1700 | using type = remove_cvref_t>; 1701 | decltype(auto) input = as_input_range(std::forward(range)); 1702 | using range_type = decltype(input); 1703 | if (RX_LIKELY(!input.at_end())) { 1704 | auto folder = foldl( 1705 | type{}, [this](auto&& accum, auto&& x) constexpr { 1706 | // Note: Can't use std::min(), because it takes the comparison function 1707 | // by-value. 1708 | const Compare& cmp = *this; 1709 | const auto& accum_ = accum; 1710 | const auto& x_ = x; 1711 | return cmp(accum_, x_) ? std::forward(accum) 1712 | : std::forward(x); 1713 | }); 1714 | return RX_OPTIONAL{std::move(folder)(std::forward(input))}; 1715 | } else { 1716 | // GCC 9.1 mistakenly thinks the return value is uninitialized unless we declare it as a 1717 | // local first. 1718 | const RX_OPTIONAL none; 1719 | return none; 1720 | } 1721 | } 1722 | }; 1723 | min()->min<>; 1724 | 1725 | /*! 1726 | @brief Checks if the predicate returns true for any element in the input range. 1727 | 1728 | @tparam P An object callable with the result of the input range, returning a boolean. 1729 | */ 1730 | template 1731 | struct any_of { 1732 | P pred; 1733 | 1734 | constexpr explicit any_of(P pred) : pred(std::move(pred)) {} 1735 | 1736 | template 1737 | [[nodiscard]] constexpr bool operator()(R&& range) const { 1738 | // Copying for reentrancy. 1739 | auto copy = as_input_range(std::forward(range)); 1740 | RX_TYPE_ASSERT(is_finite); 1741 | while (RX_LIKELY(!copy.at_end())) { 1742 | if (pred(copy.get())) { 1743 | return true; 1744 | } 1745 | copy.next(); 1746 | } 1747 | return false; 1748 | } 1749 | }; 1750 | template 1751 | any_of(P &&)->any_of

; 1752 | 1753 | /*! 1754 | @brief Checks if the predicate returns true for all elements in the input range. 1755 | 1756 | @tparam P An object callable with the result of the input range, returning a boolean. 1757 | */ 1758 | template 1759 | struct all_of { 1760 | P pred; 1761 | 1762 | constexpr explicit all_of(P pred) : pred(std::move(pred)) {} 1763 | 1764 | template 1765 | [[nodiscard]] constexpr bool operator()(R&& range) { 1766 | // Copying for reentrancy. 1767 | auto copy = as_input_range(std::forward(range)); 1768 | RX_TYPE_ASSERT(is_finite); 1769 | while (RX_LIKELY(!copy.at_end())) { 1770 | if (!pred(copy.get())) { 1771 | return false; 1772 | } 1773 | copy.next(); 1774 | } 1775 | return true; 1776 | } 1777 | }; 1778 | template 1779 | all_of(P &&)->all_of

; 1780 | 1781 | /*! 1782 | @brief Checks that the predicate returns false for all elements in the input range. 1783 | 1784 | @tparam P An object callable with the result of the input range, returning a boolean. 1785 | */ 1786 | template 1787 | struct none_of { 1788 | P pred; 1789 | constexpr explicit none_of(P pred) : pred(std::move(pred)) {} 1790 | 1791 | template 1792 | [[nodiscard]] constexpr bool operator()(R&& range) { 1793 | // Copying for reentrancy. 1794 | auto copy = as_input_range(std::forward(range)); 1795 | RX_TYPE_ASSERT(is_finite); 1796 | while (RX_LIKELY(!copy.at_end())) { 1797 | if (pred(copy.get())) { 1798 | return false; 1799 | } 1800 | copy.next(); 1801 | } 1802 | return true; 1803 | } 1804 | }; 1805 | template 1806 | none_of(P &&)->none_of

; 1807 | 1808 | /*! 1809 | @brief Perform action for each element of the input range. 1810 | 1811 | This is usually redundant, since normal range-based for loops are supported, but in some cases 1812 | it can yield more readable code, especially for long chains of combinators. 1813 | */ 1814 | template 1815 | struct for_each { 1816 | F func; 1817 | constexpr explicit for_each(F func) : func(std::move(func)) {} 1818 | 1819 | template 1820 | constexpr void operator()(R&& range) { 1821 | // Copying for reentrancy. 1822 | auto copy = as_input_range(std::forward(range)); 1823 | while (RX_LIKELY(!copy.at_end())) { 1824 | func(copy.get()); 1825 | copy.next(); 1826 | } 1827 | } 1828 | }; 1829 | template 1830 | for_each(F &&)->for_each>; 1831 | 1832 | /*! 1833 | @brief Append elements from the range into an existing container. 1834 | */ 1835 | template 1836 | struct append { 1837 | C& out; 1838 | constexpr explicit append(C& out) : out(out) {} 1839 | 1840 | template 1841 | constexpr C& operator()(R&& range) { 1842 | // Note: sink() only advances the input range if it is actually an rvalue reference. 1843 | sink(std::forward(range), out); 1844 | return out; 1845 | } 1846 | }; 1847 | template 1848 | struct append { 1849 | C out; 1850 | constexpr explicit append(C&& out) : out(std::move(out)) {} 1851 | 1852 | template 1853 | [[nodiscard]] constexpr C operator()(R&& range) && { 1854 | // Note: sink() only advances the input range if it is actually an rvalue reference. 1855 | sink(std::forward(range), out); 1856 | return std::move(out); 1857 | } 1858 | }; 1859 | template 1860 | append(C&)->append; 1861 | template 1862 | append(C &&)->append; 1863 | 1864 | /// Sorting sink. 1865 | /// 1866 | /// Writes the result of the inner range to the output, and sorts the output container. 1867 | template > 1868 | struct sort : private Compare { 1869 | constexpr explicit sort(Compare cmp) noexcept : Compare(std::move(cmp)) {} 1870 | constexpr sort() = default; 1871 | 1872 | template 1873 | struct Range : private Compare { 1874 | using output_type = get_output_type_of_t; 1875 | InputRange input; 1876 | constexpr Range(InputRange input, Compare cmp) 1877 | : Compare(std::move(cmp)), input(std::move(input)) {} 1878 | 1879 | template 1880 | constexpr void sink(Out& out) && noexcept { 1881 | using RX_NAMESPACE::sink; // enable ADL 1882 | sink(std::move(input), out); 1883 | // Note: This indirection is only required because GNU libstdc++ implements sort() in a 1884 | // way that requires the predicate to be copy-constructible. 1885 | using compare_type = remove_cvref_t; 1886 | struct indirection { 1887 | const Compare* cmp; 1888 | constexpr explicit indirection(const Compare* cmp) : cmp(cmp) {} 1889 | constexpr bool operator()(const compare_type& lhs, const compare_type& rhs) const 1890 | noexcept { 1891 | return (*cmp)(lhs, rhs); 1892 | } 1893 | }; 1894 | std::sort(begin(out), end(out), indirection(this)); 1895 | } 1896 | }; 1897 | 1898 | template 1899 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const& noexcept { 1900 | // Note: We don't want to use as_input_range(), because it would copy the input when 1901 | // chaining sinks. 1902 | using Inner = remove_cvref_t; 1903 | const Compare& cmp = *this; 1904 | return Range{std::forward(input), cmp}; 1905 | } 1906 | 1907 | template 1908 | [[nodiscard]] constexpr auto operator()(InputRange&& input) && noexcept { 1909 | using Inner = remove_cvref_t; 1910 | return Range{std::forward(input), static_cast(*this)}; 1911 | } 1912 | }; 1913 | template 1914 | sort(Compare &&)->sort>; 1915 | sort()->sort<>; 1916 | 1917 | /// Unique-elements sink. 1918 | /// 1919 | /// Writes the result of the inner range to the output, calls std::unique() on the output container, 1920 | /// and erases the remaining elements from the output. 1921 | /// 1922 | /// Note: std::unique() only eliminates contiguous sequences of equal elements. If all duplicates 1923 | /// should be removed, sort the range first. 1924 | template > 1925 | struct uniq : private Compare { 1926 | constexpr explicit uniq(Compare cmp) noexcept : Compare(std::move(cmp)) {} 1927 | constexpr uniq() = default; 1928 | 1929 | template 1930 | struct Range : private Compare { 1931 | using output_type = get_output_type_of_t; 1932 | InputRange input; 1933 | constexpr Range(InputRange input, Compare cmp) noexcept 1934 | : Compare(std::move(cmp)), input(std::move(input)) {} 1935 | 1936 | template 1937 | constexpr void sink(Out& out) && noexcept { 1938 | using RX_NAMESPACE::sink; // enable ADL 1939 | sink(std::move(input), out); 1940 | const Compare& cmp = *this; 1941 | auto remove_from = std::unique(begin(out), end(out), cmp); 1942 | out.erase(remove_from, end(out)); 1943 | } 1944 | }; 1945 | 1946 | template 1947 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const& noexcept { 1948 | // Note: We don't want to use as_input_range(), because it would copy the input when 1949 | // chaining sinks. 1950 | using Inner = remove_cvref_t; 1951 | const Compare& cmp = *this; 1952 | return Range{std::forward(input), cmp}; 1953 | } 1954 | 1955 | template 1956 | [[nodiscard]] constexpr auto operator()(InputRange&& input) && noexcept { 1957 | // Note: We don't want to use as_input_range(), because it would copy the input when 1958 | // chaining sinks. 1959 | using Inner = remove_cvref_t; 1960 | Compare& cmp = *this; 1961 | return Range{std::forward(input), std::move(cmp)}; 1962 | } 1963 | }; 1964 | template 1965 | uniq(Compare &&)->uniq>; 1966 | uniq()->uniq<>; 1967 | 1968 | /// Reverse range sink. 1969 | /// 1970 | /// Writes the result of the inner range to the output, then reverses the order of the elements 1971 | /// using `std::reverse()`. 1972 | /// 1973 | /// Note: Contrary to other range implementations, this requires storage. If the output is sinked 1974 | /// into a container, the storage for that container will be used, but otherwise temporary storage 1975 | /// will be allocated. 1976 | /// 1977 | /// If multiple sinks are chained, they will reuse the same storage (i.e., `sort() | reverse()` will 1978 | /// operate on the same output container, modifying it in turn). 1979 | struct reverse { 1980 | constexpr reverse() noexcept {} 1981 | 1982 | template 1983 | struct Range { 1984 | using output_type = get_output_type_of_t; 1985 | InputRange input; 1986 | constexpr explicit Range(InputRange input) : input(std::move(input)) {} 1987 | 1988 | template 1989 | constexpr void sink(Out& out) && noexcept { 1990 | using RX_NAMESPACE::sink; // enable ADL 1991 | sink(std::move(input), out); 1992 | std::reverse(begin(out), end(out)); 1993 | } 1994 | }; 1995 | 1996 | template 1997 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const noexcept { 1998 | // Note: We don't want to use as_input_range(), because it would copy the input when 1999 | // chaining sinks. 2000 | using Inner = remove_cvref_t; 2001 | return Range{std::forward(input)}; 2002 | } 2003 | }; 2004 | 2005 | /// A range that is always empty. 2006 | template 2007 | struct empty_range : public iterator_range { 2008 | constexpr empty_range() noexcept : iterator_range(nullptr, nullptr) {} 2009 | }; 2010 | empty_range()->empty_range; 2011 | 2012 | template 2013 | struct ChainRange { 2014 | static_assert(sizeof...(Rs) >= 2); 2015 | 2016 | static constexpr bool is_finite = (is_finite_v && ...); 2017 | static constexpr bool is_idempotent = (is_idempotent_v && ...); 2018 | using output_type = std::common_type_t; 2019 | 2020 | std::tuple inputs; 2021 | size_t current_input = 0; 2022 | 2023 | constexpr explicit ChainRange(std::tuple inputs) : inputs(std::move(inputs)) { 2024 | _skip_to_data(std::make_index_sequence()); 2025 | } 2026 | 2027 | [[nodiscard]] constexpr output_type get() const { 2028 | return _get(std::make_index_sequence()); 2029 | } 2030 | 2031 | [[nodiscard]] constexpr bool at_end() const noexcept { 2032 | return current_input >= sizeof...(Rs); 2033 | } 2034 | 2035 | constexpr void next() noexcept { 2036 | return _next(std::make_index_sequence()); 2037 | } 2038 | 2039 | [[nodiscard]] constexpr size_t size_hint() const noexcept { 2040 | return _size_hint(std::make_index_sequence()); 2041 | } 2042 | 2043 | constexpr size_t advance_by(size_t by) noexcept { 2044 | return _advance_by(std::make_index_sequence(), by); 2045 | } 2046 | 2047 | private: 2048 | [[noreturn]] 2049 | constexpr output_type _get(std::index_sequence<>) const { 2050 | RX_ASSERT(false); // chain was at end! 2051 | std::abort(); 2052 | } 2053 | 2054 | 2055 | template 2056 | constexpr output_type _get(std::index_sequence) const { 2057 | if (i == current_input) { 2058 | return std::get(inputs).get(); 2059 | } else { 2060 | return _get(std::index_sequence{}); 2061 | } 2062 | } 2063 | 2064 | constexpr void _next(std::index_sequence<>) noexcept { 2065 | RX_ASSERT(false); // chain was at end! 2066 | } 2067 | 2068 | template 2069 | constexpr void _next(std::index_sequence) noexcept { 2070 | if (i == current_input) { 2071 | auto& input = std::get(inputs); 2072 | input.next(); 2073 | if (input.at_end()) { 2074 | ++current_input; 2075 | _skip_to_data(std::index_sequence{}); 2076 | } 2077 | } else { 2078 | _next(std::index_sequence{}); 2079 | } 2080 | } 2081 | 2082 | template 2083 | constexpr size_t _size_hint(std::index_sequence) const noexcept { 2084 | return (std::get(inputs).size_hint() + ...); 2085 | } 2086 | 2087 | constexpr size_t _advance_by(std::index_sequence<>, size_t) noexcept { 2088 | return 0; 2089 | } 2090 | 2091 | template 2092 | constexpr size_t _advance_by(std::index_sequence, size_t by) noexcept { 2093 | using RX_NAMESPACE::advance_by; 2094 | if (i == current_input) { 2095 | auto& input = std::get(inputs); 2096 | size_t advanced = advance_by(input, by); 2097 | size_t remainder = by - advanced; 2098 | if (input.at_end()) { 2099 | ++current_input; 2100 | return advanced + _advance_by(std::index_sequence{}, remainder); 2101 | } else { 2102 | return advanced; 2103 | } 2104 | } else { 2105 | return _advance_by(std::index_sequence{}, by); 2106 | } 2107 | } 2108 | 2109 | constexpr void _skip_to_data(std::index_sequence<>) noexcept { 2110 | // Reached end, do nothing. 2111 | } 2112 | 2113 | template 2114 | constexpr void _skip_to_data(std::index_sequence) noexcept { 2115 | const auto& input = std::get(inputs); 2116 | if (input.at_end()) { 2117 | ++current_input; 2118 | _skip_to_data(std::index_sequence{}); 2119 | } 2120 | } 2121 | }; 2122 | template 2123 | ChainRange(Rs&&...) -> ChainRange...>; 2124 | 2125 | /// Return values from multiple ranges. 2126 | /// 2127 | /// Once the first range is exhausted, values from the second range are returned, and so on. 2128 | template 2129 | [[nodiscard]] constexpr auto chain(First&& first, InputRanges&&... inputs) noexcept { 2130 | if constexpr (sizeof...(InputRanges) == 0) { 2131 | return as_input_range(std::forward(first)); 2132 | } else { 2133 | return ChainRange, get_range_type_t...>{ 2134 | std::forward_as_tuple(as_input_range(std::forward(first)), as_input_range(std::forward(inputs))...)}; 2135 | } 2136 | } 2137 | [[nodiscard]] constexpr auto chain() noexcept { 2138 | return empty_range(); 2139 | } 2140 | 2141 | /// Create an infinite range repeating the input elements in a loop. 2142 | /// 2143 | /// If the input is empty, the output is empty, too. Note that passing an inifite range is 2144 | /// supported, but will never cycle. 2145 | struct cycle { 2146 | template 2147 | struct Range { 2148 | using output_type = typename R::output_type; 2149 | static constexpr bool is_finite = false; 2150 | static constexpr bool is_idempotent = R::is_idempotent; 2151 | 2152 | const R prototype; 2153 | RX_OPTIONAL input; // rationale: most ranges in this library are not assignable 2154 | 2155 | static_assert(is_finite_v); 2156 | 2157 | constexpr Range(R prototype_) : prototype(std::move(prototype_)) { 2158 | if (RX_LIKELY(!prototype.at_end())) { 2159 | input.emplace(prototype); 2160 | } 2161 | } 2162 | 2163 | [[nodiscard]] constexpr output_type get() const noexcept { 2164 | RX_ASSERT(input && !input->at_end()); 2165 | return input->get(); 2166 | } 2167 | 2168 | [[nodiscard]] constexpr bool at_end() const noexcept { 2169 | return !bool(input); 2170 | } 2171 | 2172 | constexpr void next() noexcept { 2173 | RX_ASSERT(!at_end()); 2174 | input->next(); 2175 | if (RX_UNLIKELY(input->at_end())) { 2176 | input.emplace(prototype); 2177 | } 2178 | } 2179 | 2180 | [[nodiscard]] constexpr size_t size_hint() const noexcept { 2181 | return bool(input) ? std::numeric_limits::max() : 0; 2182 | } 2183 | 2184 | constexpr size_t advance_by(size_t n) noexcept { 2185 | if (!input) 2186 | return 0; 2187 | using RX_NAMESPACE::advance_by; 2188 | size_t remainder = n; 2189 | while (remainder > 0) { 2190 | size_t advanced = advance_by(*input, remainder); 2191 | RX_ASSERT(advanced <= remainder); 2192 | remainder -= advanced; 2193 | if (RX_UNLIKELY(input->at_end())) { 2194 | input.emplace(prototype); 2195 | } 2196 | } 2197 | return n; 2198 | } 2199 | 2200 | // Note: advance_by() not specialized because we need to know when to wrap. 2201 | }; 2202 | 2203 | template 2204 | [[nodiscard]] constexpr auto operator()(R&& input) const { 2205 | using Inner = get_range_type_t; 2206 | return Range{as_input_range(std::forward(input))}; 2207 | } 2208 | }; 2209 | 2210 | /// Yield an infinite list of constant values once the input range is exhausted. 2211 | template 2212 | struct padded { 2213 | V value; 2214 | explicit constexpr padded(V value) noexcept : value(std::move(value)) {} 2215 | 2216 | template 2217 | struct Range { 2218 | R input; 2219 | const V value; 2220 | 2221 | using output_type = std::common_type_t>, V>; 2222 | static constexpr bool is_finite = false; 2223 | static constexpr bool is_idempotent = is_idempotent_v; 2224 | 2225 | constexpr Range(R input, V value) noexcept 2226 | : input(std::move(input)), value(std::move(value)) {} 2227 | 2228 | constexpr void next() { 2229 | RX_ASSERT(!at_end()); 2230 | if (!input.at_end()) { 2231 | input.next(); 2232 | } 2233 | } 2234 | 2235 | [[nodiscard]] constexpr output_type get() const { 2236 | RX_ASSERT(!at_end()); 2237 | if (!input.at_end()) { 2238 | return input.get(); 2239 | } else { 2240 | return value; 2241 | } 2242 | } 2243 | 2244 | [[nodiscard]] constexpr bool at_end() const { 2245 | return false; 2246 | } 2247 | 2248 | [[nodiscard]] constexpr size_t size_hint() const noexcept { 2249 | return std::numeric_limits::max(); 2250 | } 2251 | 2252 | constexpr size_t advance_by(size_t n) noexcept { 2253 | using RX_NAMESPACE::advance_by; 2254 | size_t advanced = advance_by(input, n); 2255 | return advanced % n; 2256 | } 2257 | }; 2258 | 2259 | template 2260 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const& noexcept { 2261 | using Inner = get_range_type_t; 2262 | return Range{as_input_range(std::forward(input)), value}; 2263 | } 2264 | 2265 | template 2266 | [[nodiscard]] constexpr auto operator()(InputRange&& input) && noexcept { 2267 | using Inner = get_range_type_t; 2268 | return Range{as_input_range(std::forward(input)), std::move(value)}; 2269 | } 2270 | }; 2271 | template 2272 | padded(V&)->padded>; 2273 | template 2274 | padded(V &&)->padded>; 2275 | 2276 | template 2277 | struct ZipLongestRange { 2278 | static_assert(sizeof...(Inputs) > 0); 2279 | 2280 | std::tuple inputs; 2281 | using output_type = std::tuple>...>; 2282 | static constexpr bool is_finite = (is_finite_v && ...); 2283 | static constexpr bool is_idempotent = (is_idempotent_v && ...); 2284 | 2285 | constexpr explicit ZipLongestRange(std::tuple tuple) : inputs(std::move(tuple)) {} 2286 | 2287 | [[nodiscard]] constexpr output_type get() const noexcept { 2288 | RX_ASSERT(!at_end()); 2289 | return _get(std::index_sequence_for{}); 2290 | } 2291 | 2292 | constexpr void next() noexcept { 2293 | RX_ASSERT(!at_end()); 2294 | _next(std::index_sequence_for{}); 2295 | } 2296 | 2297 | [[nodiscard]] constexpr bool at_end() const noexcept { 2298 | return _at_end(std::index_sequence_for{}); 2299 | } 2300 | 2301 | constexpr size_t size_hint() const noexcept { 2302 | return _size_hint(std::index_sequence_for{}); 2303 | } 2304 | 2305 | constexpr size_t advance_by(size_t n) noexcept { 2306 | return _advance_by(std::index_sequence_for{}, n); 2307 | } 2308 | 2309 | private: 2310 | template 2311 | [[nodiscard]] constexpr output_type _get(std::index_sequence) const noexcept { 2312 | return output_type(std::forward_as_tuple((std::get(inputs) | first())...)); 2313 | } 2314 | 2315 | template 2316 | constexpr void _next(std::index_sequence) noexcept { 2317 | ((std::get(inputs).at_end() ? 0 : (std::get(inputs).next(), 0)), ...); 2318 | } 2319 | 2320 | template 2321 | [[nodiscard]] constexpr bool _at_end(std::index_sequence) const noexcept { 2322 | return (std::get(inputs).at_end() && ...); 2323 | } 2324 | 2325 | template 2326 | constexpr size_t _size_hint(std::index_sequence) const noexcept { 2327 | return std::max({std::get(inputs).size_hint()...}); 2328 | } 2329 | 2330 | template 2331 | constexpr size_t _advance_by(std::index_sequence, size_t n) noexcept { 2332 | using RX_NAMESPACE::advance_by; 2333 | return std::max({advance_by(std::get(inputs), n)...}); 2334 | } 2335 | }; 2336 | template 2337 | ZipLongestRange(std::tuple)->ZipLongestRange...>; 2338 | 2339 | /*! 2340 | @brief Zip two or more ranges, return longest range. 2341 | Until all of the ranges are at end, produce a tuple of an element from each range. 2342 | The ranges are not required to produce the same number of elements, and elements will be 2343 | produced until all of the ranges reach their end. Ranges that ended earlier produce nullopt. 2344 | */ 2345 | template 2346 | [[nodiscard]] constexpr auto zip_longest(Inputs&&... inputs) noexcept { 2347 | return ZipLongestRange(std::forward_as_tuple(as_input_range(std::forward(inputs))...)); 2348 | } 2349 | 2350 | /// Copy values of a range into a container during iteration. 2351 | template 2352 | struct tee { 2353 | Dest& dest; 2354 | explicit constexpr tee(Dest& dest) noexcept : dest(dest) {} 2355 | 2356 | template 2357 | struct Range { 2358 | R input; 2359 | Dest& dest; 2360 | 2361 | using output_type = get_output_type_of_t; 2362 | static constexpr bool is_finite = is_finite_v; 2363 | static constexpr bool is_idempotent = false; 2364 | 2365 | constexpr void next() { 2366 | sink_one(input.get(), dest); 2367 | input.next(); 2368 | } 2369 | 2370 | constexpr output_type get() const { 2371 | return input.get(); 2372 | } 2373 | 2374 | constexpr bool at_end() const { 2375 | return input.at_end(); 2376 | } 2377 | 2378 | constexpr size_t size_hint() const noexcept { 2379 | return input.size_hint(); 2380 | } 2381 | 2382 | // Note: advance_by() not specialized because tee() needs to see every 2383 | // element. 2384 | }; 2385 | 2386 | template 2387 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const noexcept { 2388 | using Inner = get_idempotent_range_type_t; 2389 | return Range{as_idempotent_input_range(std::forward(input)), dest}; 2390 | } 2391 | }; 2392 | template 2393 | tee(Dest&)->tee>; 2394 | 2395 | /// Return a range flattening one level of nesting in a range of ranges. 2396 | /// 2397 | /// Supply a template parameter bigger than 1 to flatten multiple levels at once. 2398 | template 2399 | struct flatten; 2400 | flatten()->flatten<1>; 2401 | 2402 | template <> 2403 | struct flatten<0> { 2404 | template 2405 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const noexcept { 2406 | return std::forward(input); 2407 | } 2408 | }; 2409 | 2410 | template <> 2411 | struct flatten<1> { 2412 | template 2413 | struct Range { 2414 | using S = get_range_type_t>; 2415 | using output_type = get_output_type_of_t; 2416 | 2417 | static constexpr bool is_finite = is_finite_v && is_finite_v; 2418 | static constexpr bool is_idempotent = is_idempotent_v; 2419 | 2420 | R outer_range; 2421 | RX_OPTIONAL inner_range; 2422 | 2423 | constexpr explicit Range(R input) : outer_range(std::move(input)) { 2424 | while (RX_LIKELY(!outer_range.at_end())) { 2425 | inner_range.emplace(as_input_range(outer_range.get())); 2426 | if (RX_LIKELY(!inner_range->at_end())) { 2427 | return; 2428 | } 2429 | } 2430 | inner_range.reset(); 2431 | } 2432 | 2433 | [[nodiscard]] constexpr bool at_end() const noexcept { 2434 | return !bool(inner_range); 2435 | } 2436 | 2437 | [[nodiscard]] constexpr output_type get() const noexcept { 2438 | RX_ASSERT(!at_end()); 2439 | return inner_range->get(); 2440 | } 2441 | 2442 | constexpr void next() noexcept { 2443 | RX_ASSERT(!at_end()); 2444 | 2445 | inner_range->next(); 2446 | if (RX_LIKELY(!inner_range->at_end())) { 2447 | return; 2448 | } 2449 | 2450 | for (;;) { 2451 | outer_range.next(); 2452 | if (RX_UNLIKELY(outer_range.at_end())) { 2453 | break; 2454 | } 2455 | 2456 | inner_range.emplace(as_input_range(outer_range.get())); 2457 | if (RX_LIKELY(!inner_range->at_end())) { 2458 | return; 2459 | } 2460 | } 2461 | inner_range.reset(); 2462 | } 2463 | 2464 | [[nodiscard]] constexpr size_t size_hint() const noexcept { 2465 | if (at_end()) { 2466 | return 0; 2467 | } 2468 | 2469 | auto r = outer_range.size_hint(); 2470 | if (r == 0 || r == std::numeric_limits::max()) { 2471 | return r; 2472 | } 2473 | 2474 | auto s = inner_range->size_hint(); 2475 | if (s == std::numeric_limits::max()) { 2476 | return s; 2477 | } else if (s == 0) { 2478 | // Only because this inner range is empty, does not mean all inner ranges are. 2479 | // But either way, we have to assume the other ones have at least one element. 2480 | return r; 2481 | } 2482 | 2483 | auto rs = r * s; 2484 | if (rs < r || rs < s) { 2485 | // If the multiplication overflows, return the maximum. 2486 | return std::numeric_limits::max(); 2487 | } else { 2488 | return rs; 2489 | } 2490 | } 2491 | }; 2492 | 2493 | template 2494 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const noexcept { 2495 | using Inner = get_range_type_t; 2496 | return Range{as_input_range(std::forward(input))}; 2497 | } 2498 | }; 2499 | 2500 | template 2501 | struct flatten { 2502 | template 2503 | [[nodiscard]] constexpr auto operator()(InputRange&& input) const noexcept { 2504 | return std::forward(input) | flatten<1>() | flatten(); 2505 | } 2506 | }; 2507 | 2508 | /// A sink that simply discards all elements of a range. 2509 | /// 2510 | /// Use as `append(null_sink())` if you need the range to run, but you don't need the result. 2511 | struct null_sink { 2512 | struct value_type {}; 2513 | 2514 | template 2515 | constexpr void emplace_back(V&&...) const noexcept {} 2516 | }; 2517 | 2518 | } // namespace RX_NAMESPACE 2519 | 2520 | #endif // RX_RANGES_HPP_INCLUDED 2521 | -------------------------------------------------------------------------------- /test/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace rx; 8 | 9 | // Note: The std algorithms almost always fall back to std::for_each, because the standard 10 | // algorithms like `std::transform()` etc. always require an output container, which would be 11 | // unfairly expensive due to heap allocations. 12 | 13 | static void bench_transform_rx(benchmark::State& state) { 14 | std::vector input; 15 | input.resize(1'000'000); 16 | std::iota(begin(input), end(input), 0); 17 | for (auto _ : state) { 18 | auto f = [](auto begin, auto end) constexpr { 19 | return iterator_range(begin, end) | transform([](int x) { return x * 2; }) | sum(); 20 | }; 21 | benchmark::DoNotOptimize(f(begin(input), end(input))); 22 | } 23 | } 24 | BENCHMARK(bench_transform_rx); 25 | 26 | static void bench_transform_std(benchmark::State& state) { 27 | std::vector input; 28 | input.resize(1'000'000); 29 | std::iota(begin(input), end(input), 0); 30 | for (auto _ : state) { 31 | auto f = [](auto begin, auto end) constexpr { 32 | int sum = 0; 33 | std::for_each(begin, end, [&](int x) { sum += x * 2; }); 34 | return sum; 35 | }; 36 | benchmark::DoNotOptimize(f(begin(input), end(input))); 37 | } 38 | } 39 | BENCHMARK(bench_transform_std); 40 | 41 | static void bench_transform_plain(benchmark::State& state) { 42 | std::vector input; 43 | input.resize(1'000'000); 44 | std::iota(begin(input), end(input), 0); 45 | for (auto _ : state) { 46 | auto f = [](auto begin, auto end) constexpr { 47 | int sum = 0; 48 | for (auto it = begin; it != end; ++it) { 49 | sum += *it * 2; 50 | } 51 | return sum; 52 | }; 53 | benchmark::DoNotOptimize(f(begin(input), end(input))); 54 | } 55 | } 56 | BENCHMARK(bench_transform_plain); 57 | 58 | static void bench_filter_sum_rx(benchmark::State& state) { 59 | std::vector input; 60 | input.resize(1'000'000); 61 | std::iota(begin(input), end(input), 0); 62 | for (auto _ : state) { 63 | // For some reason, compilers are not able to merge the condition in filter() with the loop 64 | // condition in sum(). For that reason, this benchmark is significantly slower than the 65 | // plain alternatives. 66 | auto f = [](auto begin, auto end) constexpr { 67 | return iterator_range(begin, end) | filter([](int x) constexpr { return bool(x % 2); }) 68 | | sum(); 69 | }; 70 | //// This version is just as fast as the others. 71 | // auto f = [](auto begin, auto end) constexpr { 72 | // int sum = 0; 73 | // iterator_range(begin, end) | for_each([&](int x) constexpr { 74 | // if (x % 2) 75 | // sum += x; 76 | // }); 77 | // return sum; 78 | // }; 79 | benchmark::DoNotOptimize(f(begin(input), end(input))); 80 | } 81 | } 82 | BENCHMARK(bench_filter_sum_rx); 83 | 84 | static void bench_filter_sum_std(benchmark::State& state) { 85 | std::vector input; 86 | input.resize(1'000'000); 87 | std::iota(begin(input), end(input), 0); 88 | for (auto _ : state) { 89 | auto f = [](auto begin, auto end) { 90 | int sum = 0; 91 | std::for_each( 92 | begin, end, [&](int x) constexpr { 93 | if (x % 2) 94 | sum += x; 95 | }); 96 | return sum; 97 | }; 98 | benchmark::DoNotOptimize(f(begin(input), end(input))); 99 | } 100 | } 101 | BENCHMARK(bench_filter_sum_std); 102 | 103 | static void bench_filter_sum_plain(benchmark::State& state) { 104 | std::vector input; 105 | input.resize(1'000'000); 106 | std::iota(begin(input), end(input), 0); 107 | for (auto _ : state) { 108 | auto f = [](auto begin, auto end) constexpr { 109 | int sum = 0; 110 | for (auto it = begin; it != end; ++it) { 111 | if (*it % 2) { 112 | sum += *it; 113 | } 114 | } 115 | return sum; 116 | }; 117 | benchmark::DoNotOptimize(f(begin(input), end(input))); 118 | } 119 | } 120 | BENCHMARK(bench_filter_sum_plain); 121 | 122 | static void bench_in_groups_of_exactly_rx(benchmark::State& state) { 123 | std::vector input = seq() | take(1'000'000) | to_vector(); 124 | for (auto _ : state) { 125 | // Sum the last element in each group 126 | auto f = [](auto begin, auto end) { 127 | return iterator_range(begin, end) | in_groups_of_exactly(16) | transform([](auto&& group) { 128 | advance_by(group, 15); 129 | return group.get(); 130 | }) | sum(); 131 | }; 132 | benchmark::DoNotOptimize(f(begin(input), end(input))); 133 | } 134 | } 135 | BENCHMARK(bench_in_groups_of_exactly_rx); 136 | 137 | static void bench_in_groups_of_rx(benchmark::State& state) { 138 | std::vector input = seq() | take(1'000'000) | to_vector(); 139 | for (auto _ : state) { 140 | // sum highest element of each group 141 | auto f = [](auto begin, auto end) constexpr { 142 | return iterator_range(begin, end) | in_groups_of(16) | transform([](auto&& group) { 143 | auto copy = group; 144 | advance_by(copy, 15); 145 | if (RX_LIKELY(!copy.at_end())) { 146 | return copy.get(); 147 | } else { 148 | auto tmp = group.get(); 149 | while (!group.at_end()) { 150 | group.next(); 151 | tmp = group.get(); 152 | } 153 | return tmp; 154 | } 155 | return group.get(); 156 | }) | sum(); 157 | }; 158 | benchmark::DoNotOptimize(f(begin(input), end(input))); 159 | } 160 | } 161 | BENCHMARK(bench_in_groups_of_rx); 162 | 163 | static void bench_in_groups_of_std(benchmark::State& state) { 164 | std::vector input = seq() | take(1'000'000) | to_vector(); 165 | for (auto _ : state) { 166 | auto f = [](auto begin, auto end) constexpr { 167 | int sum = 0; 168 | for (auto it = begin; it != end;) { 169 | if (end - it >= 16) { 170 | sum += *(it + 15); 171 | it += 16; 172 | } else { 173 | sum += *(end - 1); 174 | break; 175 | } 176 | } 177 | return sum; 178 | }; 179 | benchmark::DoNotOptimize(f(begin(input), end(input))); 180 | } 181 | } 182 | BENCHMARK(bench_in_groups_of_std); 183 | 184 | static void bench_cycle_rx(benchmark::State& state) { 185 | auto input = seq() | take(10); 186 | for (auto _ : state) { 187 | auto f = [=]() { 188 | return input | cycle() | take(1'000'000) | sum(); 189 | }; 190 | benchmark::DoNotOptimize(f()); 191 | } 192 | } 193 | BENCHMARK(bench_cycle_rx); 194 | 195 | static void bench_cycle_plain(benchmark::State& state) { 196 | for (auto _ : state) { 197 | auto f = []() { 198 | int sum = 0; 199 | for (size_t i = 0; i < 1'000'000; ++i) { 200 | sum += int(i % 10); 201 | } 202 | return sum; 203 | }; 204 | benchmark::DoNotOptimize(f()); 205 | } 206 | } 207 | BENCHMARK(bench_cycle_plain); 208 | 209 | static void bench_padded_rx(benchmark::State& state) { 210 | std::vector result; 211 | result.reserve(1'000'000); 212 | for (auto _ : state) { 213 | auto f = [&]() { 214 | result.clear(); 215 | fill('a') | take(500'000) | padded('b') | take(1'000'000) | append(result); 216 | return 0; 217 | }; 218 | benchmark::DoNotOptimize(f()); 219 | } 220 | } 221 | BENCHMARK(bench_padded_rx); 222 | 223 | static void bench_padded_plain(benchmark::State& state) { 224 | std::vector result; 225 | result.reserve(1'000'000); 226 | for (auto _ : state) { 227 | auto f = [&]() { 228 | result.clear(); 229 | for (size_t i = 0; i < 1'000'000; ++i) { 230 | char c = i < 500'000 ? 'a' : 'b'; 231 | result.push_back(c); 232 | } 233 | return 0; 234 | }; 235 | benchmark::DoNotOptimize(f()); 236 | } 237 | } 238 | BENCHMARK(bench_padded_plain); 239 | 240 | static void bench_chain_rx(benchmark::State& state) { 241 | // Note: In the "plain" case, Clang is able to autovectorize, making that ~10x faster. 242 | std::vector input1 = seq() | take(200'000) | to_vector(); 243 | std::vector input2 = seq(200'000) | take(200'000) | to_vector(); 244 | std::vector input3 = seq(400'000) | take(200'000) | to_vector(); 245 | std::vector input4 = seq(600'000) | take(200'000) | to_vector(); 246 | for (auto _ : state) { 247 | auto f = [&]() { 248 | return chain(input1, input2, input3, input4) | sum(); 249 | }; 250 | benchmark::DoNotOptimize(f()); 251 | } 252 | } 253 | BENCHMARK(bench_chain_rx); 254 | 255 | static void bench_chain_plain(benchmark::State& state) { 256 | std::vector input1 = seq() | take(200'000) | to_vector(); 257 | std::vector input2 = seq(200'000) | take(200'000) | to_vector(); 258 | std::vector input3 = seq(400'000) | take(200'000) | to_vector(); 259 | std::vector input4 = seq(600'000) | take(200'000) | to_vector(); 260 | for (auto _ : state) { 261 | auto f = [&]() { 262 | int sum = 0; 263 | for (auto x : input1) { 264 | sum += x; 265 | } 266 | for (auto x : input2) { 267 | sum += x; 268 | } 269 | for (auto x : input3) { 270 | sum += x; 271 | } 272 | for (auto x : input4) { 273 | sum += x; 274 | } 275 | return sum; 276 | }; 277 | benchmark::DoNotOptimize(f()); 278 | } 279 | } 280 | BENCHMARK(bench_chain_plain); 281 | 282 | static void bench_any_of_rx(benchmark::State& state) { 283 | std::vector input = seq() | take(1'000'000) | to_vector(); 284 | for (auto _ : state) { 285 | auto f = [&]() { 286 | return input | any_of([](int x) { return x == 500'000; }); 287 | }; 288 | benchmark::DoNotOptimize(f()); 289 | } 290 | } 291 | BENCHMARK(bench_any_of_rx); 292 | 293 | static void bench_any_of_std(benchmark::State& state) { 294 | std::vector input = seq() | take(1'000'000) | to_vector(); 295 | for (auto _ : state) { 296 | auto f = [&]() { 297 | return std::any_of(begin(input), end(input), [](int x) { 298 | return x == 500'000; 299 | }); 300 | }; 301 | benchmark::DoNotOptimize(f()); 302 | } 303 | } 304 | BENCHMARK(bench_any_of_std); 305 | 306 | static void bench_all_of_rx(benchmark::State& state) { 307 | std::vector input = seq() | take(1'000'000) | to_vector(); 308 | for (auto _ : state) { 309 | auto f = [&]() { 310 | return input | all_of([](int x) { return x <= 500'000; }); 311 | }; 312 | benchmark::DoNotOptimize(f()); 313 | } 314 | } 315 | BENCHMARK(bench_all_of_rx); 316 | 317 | static void bench_all_of_std(benchmark::State& state) { 318 | std::vector input = seq() | take(1'000'000) | to_vector(); 319 | for (auto _ : state) { 320 | auto f = [&]() { 321 | return std::all_of(begin(input), end(input), [](int x) { 322 | return x <= 500'000; 323 | }); 324 | }; 325 | benchmark::DoNotOptimize(f()); 326 | } 327 | } 328 | BENCHMARK(bench_all_of_std); 329 | 330 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /test/calendar.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | using namespace RX_NAMESPACE; 8 | 9 | // This example is derived from the 'rangeless' library's example. 10 | // 11 | // See: https://github.com/ast-al/rangeless/blob/gh-pages/test/calendar.cpp 12 | 13 | namespace greg = boost::gregorian; 14 | using date_t = greg::date; 15 | 16 | static void make_calendar(uint16_t year, uint8_t num_months_horizontally, std::ostream& os) { 17 | static constexpr std::array s_month_names = { 18 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 19 | seq(date_t(year, greg::Jan, 1), greg::date_duration(1)) 20 | | until([year](date_t x) { return x.year() != year; }) 21 | | group_adjacent_by( 22 | [](const date_t& d) { return std::make_pair(d.month(), d.week_number()); }) 23 | | transform([&](const auto &wk_dates) -> std::pair { 24 | const auto left_pad_amt = size_t(3 * ((wk_dates.get().day_of_week() + 7 - 1) % 7)); 25 | return {wk_dates.get().month(), 26 | wk_dates 27 | | foldl( 28 | std::string(left_pad_amt, ' '), 29 | [](std::string ret_wk, const date_t& d) { 30 | return std::move(ret_wk) + (d.day() < 10 ? " " : " ") 31 | + std::to_string(d.day()); 32 | })}; 33 | }) // 34 | | group_adjacent_by([](const auto& x) { return x.first; }) // group by month 35 | | in_groups_of(num_months_horizontally) // 36 | | for_each([&](const auto& group) { 37 | for (int row = -2; row < 6; ++row) { 38 | for (const auto& mo : group | transform(to_vector())) { 39 | os << std::setiosflags(std::ios::left) << std::setw(25) 40 | << (row == -2 ? std::string{" "} + s_month_names.at(mo.front().first - 1u) 41 | : row == -1 ? " Mo Tu We Th Fr Sa Su" 42 | : size_t(row) < mo.size() 43 | ? mo[row].second // formatted week 44 | : " "); 45 | } 46 | os << std::endl; 47 | } 48 | }); 49 | } 50 | 51 | int main() { 52 | make_calendar(2019, 3, std::cout); 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /test/test_ranges.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | #include 9 | 10 | 11 | using namespace rx; 12 | using namespace std::literals::string_literals; 13 | 14 | template 15 | std::string to_string(T val) { 16 | return std::to_string(val); 17 | } 18 | 19 | // TEST_CASE("ranges operator| is general") { 20 | // // This is not necessarily something we actually want, but it is very convenient when 21 | // defining 22 | // // custom combinators. 23 | // std::string s = 123 | [](int x) { return to_string(x); }; 24 | // CHECK(s == "123"); 25 | // } 26 | 27 | TEST_CASE("range take advance_by overflow") { 28 | auto bounds = seq() | take(10); 29 | advance_by(bounds, 11); 30 | CHECK(bounds.i == bounds.n); 31 | 32 | auto arithmetic = seq() | take(10); 33 | advance_by(arithmetic, std::numeric_limits::max()); 34 | CHECK(arithmetic.i == arithmetic.n); 35 | } 36 | 37 | TEST_CASE("range transform") { 38 | auto input = std::vector{{1, 2, 3, 4}}; 39 | auto strings = input | transform(&to_string) | to_vector(); 40 | auto expected = std::vector{{"1"s, "2"s, "3"s, "4"s}}; 41 | CHECK(strings == expected); 42 | } 43 | 44 | TEST_CASE("range transform reentrant") { 45 | auto input = std::vector{{1, 2, 3, 4}}; 46 | auto strings = input | transform(&to_string); 47 | auto a = strings | to_vector(); 48 | auto b = strings | to_vector(); 49 | CHECK(a == b); 50 | } 51 | 52 | TEST_CASE("range filter") { 53 | auto input = std::list{{1, 2, 3, 4}}; 54 | auto odd = input | filter([](int x) { return x % 2 == 1; }) | to_list(); 55 | for (auto x : odd) { 56 | CHECK(x % 2 == 1); 57 | } 58 | } 59 | 60 | TEST_CASE("range filter reentrant") { 61 | auto input = std::list{{1, 2, 3, 4}}; 62 | auto odd = input | filter([](int x) { return x % 2 == 1; }); 63 | auto a = odd | to_vector(); 64 | auto b = odd | to_vector(); 65 | CHECK(a == b); 66 | } 67 | 68 | TEST_CASE("range filter idempotent") { 69 | auto input = std::vector{{1, 2, 3, 4}}; 70 | int idempotent_guard{0}; 71 | auto odd = input | transform([&idempotent_guard] (int i) { ++idempotent_guard; return i; }) | filter([](int x) { return x % 2 == 1; }); 72 | auto a = odd | to_vector(); 73 | CHECK(a == std::vector{{1,3}}); 74 | CHECK(idempotent_guard == 4); 75 | } 76 | 77 | TEST_CASE("range first") { 78 | auto input = std::vector{{"Hello"s, "World"s, "Morty"s}}; 79 | auto contains_y = [](std::string_view sv) { return sv.find('y') != std::string::npos; }; 80 | RX_OPTIONAL morty = input | filter(contains_y) | first(); 81 | CHECK(morty); 82 | CHECK(*morty == "Morty"); 83 | } 84 | 85 | TEST_CASE("range first reentrant") { 86 | auto input = std::vector{{"Hello"s, "World"s, "Morty"s}}; 87 | auto contains_y = [](std::string_view sv) { return sv.find('y') != std::string::npos; }; 88 | auto range = input | filter(contains_y); 89 | auto a = range | first(); 90 | auto b = range | first(); 91 | CHECK(a == b); 92 | } 93 | 94 | TEST_CASE("range first_n") { 95 | auto input = std::vector{{1, 2, 3, 4, 5}}; 96 | auto first_3 = input | first_n(3) | to_vector(); 97 | CHECK(first_3.size() == 3); 98 | CHECK(first_3 == std::vector{{1, 2, 3}}); 99 | } 100 | 101 | TEST_CASE("range first_n reentrant") { 102 | auto input = std::vector{{1, 2, 3, 4, 5}}; 103 | auto first_3 = input | first_n(3); 104 | auto a = first_3 | to_vector(); 105 | auto b = first_3 | to_vector(); 106 | CHECK(a == b); 107 | } 108 | 109 | TEST_CASE("range skip_n") { 110 | auto input = seq() | skip_n(1000) | first_n(10) | to_vector(); 111 | auto expected = seq(1000) | first_n(10) | to_vector(); 112 | CHECK(input == expected); 113 | } 114 | 115 | TEST_CASE("ranges zip") { 116 | auto input1 = seq() | first_n(5); 117 | auto input2 = input1 | transform(&to_string); 118 | auto input3 = seq(10); // inifinite range! 119 | auto zipped = zip(input1, input2, input3) | to_vector(); 120 | CHECK(zipped.size() == 5); 121 | auto expected = std::vector{ 122 | std::make_tuple(0, "0"s, 10), 123 | std::make_tuple(1, "1"s, 11), 124 | std::make_tuple(2, "2"s, 12), 125 | std::make_tuple(3, "3"s, 13), 126 | std::make_tuple(4, "4"s, 14), 127 | }; 128 | CHECK(zipped == expected); 129 | } 130 | 131 | TEST_CASE("ranges zip two same") { 132 | auto add = [](auto lr) { 133 | auto [l, r] = lr; 134 | return l + r; 135 | }; 136 | auto value = zip(seq(0), seq(1)) | first_n(5) | transform(add) | max(); 137 | CHECK(value == 9); 138 | 139 | auto advancing = zip(seq(0), seq(1)) | transform(add); 140 | advance_by(advancing, 4); 141 | CHECK(advancing.get() == 9); 142 | } 143 | 144 | TEST_CASE("ranges zip advance_by") { 145 | auto input = zip(seq(), seq(1)); 146 | advance_by(input, 10); 147 | CHECK(input.get() == std::tuple{10, 11}); 148 | 149 | auto finite = zip(seq(), seq() | take(5)); 150 | size_t advanced = advance_by(finite, 6); 151 | CHECK(advanced == 5); 152 | } 153 | 154 | TEST_CASE("ranges zip reentrant") { 155 | auto input1 = seq() | first_n(5); 156 | auto input2 = input1 | transform(&to_string); 157 | auto input3 = seq(10); // inifinite range! 158 | auto zipped = zip(input1, input2, input3); 159 | auto a = zipped | to_vector(); 160 | auto b = zipped | to_vector(); 161 | CHECK(a == b); 162 | } 163 | 164 | TEST_CASE("ranges to_map") { 165 | auto input1 = seq(); 166 | auto input2 = input1 | transform(&to_string) | first_n(5); 167 | auto result = zip(input1, input2) | to_map(); 168 | CHECK(result.size() == 5); 169 | auto expected = std::map{{ 170 | std::make_pair(0, "0"s), 171 | std::make_pair(1, "1"s), 172 | std::make_pair(2, "2"s), 173 | std::make_pair(3, "3"s), 174 | std::make_pair(4, "4"s), 175 | }}; 176 | CHECK(result == expected); 177 | } 178 | 179 | TEST_CASE("ranges to_set") { 180 | auto input = std::vector{{0, 0, 1, 1}}; 181 | auto result = as_input_range(input) | to_set(); 182 | CHECK(result.size()); 183 | auto expected = std::set{{0, 1}}; 184 | CHECK(result == expected); 185 | } 186 | 187 | TEST_CASE("ranges append to arbitrary container") { 188 | std::unordered_map result; 189 | auto keys = seq(); 190 | auto values = keys | transform(&to_string); 191 | zip(keys, values) | first_n(5) | append(result); 192 | auto expected = std::unordered_map{{std::make_pair(0.0, "0"s), 193 | std::make_pair(1.0, "1"s), 194 | std::make_pair(2.0, "2"s), 195 | std::make_pair(3.0, "3"s), 196 | std::make_pair(4.0, "4"s)}}; 197 | CHECK(result == expected); 198 | } 199 | 200 | TEST_CASE("range append to rvalue container") { 201 | auto lower = seq('a') | take(26) | append(""s); 202 | CHECK(lower == "abcdefghijklmnopqrstuvwxyz"s); 203 | 204 | auto digits = seq() | take(10) | append(std::list(0,0)); 205 | CHECK(digits == std::list{{0,1,2,3,4,5,6,7,8,9}}); 206 | } 207 | 208 | TEST_CASE("ranges generate") { 209 | int x = 0; 210 | auto input = generate([&] { return x++; }); 211 | auto result = input | first_n(5) | to_vector(); 212 | auto expected = seq() | first_n(5) | to_vector(); 213 | CHECK(result == expected); 214 | } 215 | 216 | TEST_CASE("ranges generate reentrant") { 217 | struct callable { 218 | int x = 0; 219 | int operator()() noexcept { 220 | return x++; 221 | } 222 | }; 223 | // Check that the generator function is copied when sinking into a range. 224 | auto input = generate(callable{}) | first_n(5); 225 | auto a = input | to_vector(); 226 | auto b = input | to_vector(); 227 | CHECK(a == b); 228 | } 229 | 230 | TEST_CASE("ranges until") { 231 | auto input = seq() | until([](int x) { return x == 5; }); 232 | auto result = input | to_vector(); 233 | auto expected = seq() | first_n(5) | to_vector(); 234 | CHECK(result == expected); 235 | } 236 | 237 | TEST_CASE("ranges any_of") { 238 | auto input = seq() | first_n(5); 239 | auto a = input | any_of([](int x) { return x > 3; }); 240 | CHECK(a); 241 | auto b = input | any_of([](int x) { return x == 5; }); 242 | CHECK(!b); 243 | } 244 | 245 | TEST_CASE("ranges all_of") { 246 | auto input = seq() | first_n(5); 247 | auto a = input | all_of([](int x) { return x < 5; }); 248 | CHECK(a); 249 | auto b = input | all_of([](int x) { return x < 4; }); 250 | CHECK(!b); 251 | } 252 | 253 | TEST_CASE("ranges none_of") { 254 | auto input = seq() | first_n(5); 255 | auto a = input | none_of([](int x) { return x > 4; }); 256 | CHECK(a); 257 | auto b = input | none_of([](int x) { return x == 4; }); 258 | CHECK(!b); 259 | } 260 | 261 | TEST_CASE("ranges avoid copy") { 262 | auto input = std::vector{{1, 2, 3, 4}}; 263 | auto odd = input | filter([](int x) { return x % 2 == 1; }); 264 | // modify the input to check that filtered range is not actually operating on a copy of the 265 | // vector. Note: filter() skips non-matching elements initially, which is a bit awkward. 266 | input[2] = 0; 267 | CHECK((odd | count()) == 1); 268 | } 269 | 270 | TEST_CASE("ranges count reentrant") { 271 | auto input = seq() | first_n(10); 272 | auto a = input | count(); 273 | CHECK(a == 10); 274 | auto b = input | count(); 275 | CHECK(b == 10); 276 | } 277 | 278 | TEST_CASE("ranges fill") { 279 | std::string a; 280 | fill_n(5, 'a') | append(a); 281 | CHECK(a == "aaaaa"); 282 | 283 | std::string b; 284 | fill('b') | first_n(5) | append(b); 285 | CHECK(b == "bbbbb"); 286 | 287 | int v = 7; 288 | CHECK((fill(v) | take(5) | sum()) == 7*5); 289 | CHECK(v == 7); 290 | CHECK((fill_n(5, v) | sum()) == 7*5); 291 | CHECK(v == 7); 292 | } 293 | 294 | TEST_CASE("ranges sum") { 295 | auto s = fill_n(5, 1) | sum(); 296 | CHECK(s == 5); 297 | 298 | auto d = fill_n(5, 1.0) | sum(); 299 | CHECK(d == 5.0); 300 | } 301 | 302 | TEST_CASE("ranges max") { 303 | auto s = seq() | first_n(5) | max(); 304 | CHECK(*s == 4); 305 | } 306 | 307 | TEST_CASE("ranges min") { 308 | auto s = seq() | first_n(5) | min(); 309 | CHECK(*s == 0); 310 | } 311 | 312 | TEST_CASE("ranges infinity propagates") { 313 | auto s = 314 | seq() | skip_n(1) | filter([](auto) { return true; }) | transform([](auto) { return 0; }); 315 | CHECK(!decltype(s)::is_finite); 316 | } 317 | 318 | TEST_CASE("ranges enumerate with indices") { 319 | auto input = std::vector{{"a"s, "b"s, "c"s}}; 320 | for (auto pair : zip(input, seq())) { 321 | if (std::get<0>(pair) == "a") { 322 | CHECK(std::get<1>(pair) == 0); 323 | } else if (std::get<0>(pair) == "b") { 324 | CHECK(std::get<1>(pair) == 1); 325 | } else if (std::get<0>(pair) == "c") { 326 | CHECK(std::get<1>(pair) == 2); 327 | } else { 328 | CHECK(false); 329 | } 330 | } 331 | 332 | auto a = zip(seq(), input) | to_vector(); 333 | auto b = enumerate(input) | to_vector(); 334 | CHECK(a == b); 335 | } 336 | 337 | TEST_CASE("ranges sort") { 338 | // Check that we can use std algorithms directly. 339 | auto sorted = std::vector{{3, 2, 1}} | sort() | to_vector(); 340 | CHECK(std::is_sorted(begin(sorted), end(sorted))); 341 | 342 | auto odd = [](auto x) { return x % 2 == 1; }; 343 | 344 | // Chaining 345 | auto filtered_sorted = std::vector{{3, 2, 1}} | filter(odd) | sort() | to_vector(); 346 | CHECK(std::is_sorted(begin(filtered_sorted), end(filtered_sorted))); 347 | } 348 | 349 | TEST_CASE("ranges reverse") { 350 | auto input = std::vector{{2, 3, 6, 1, 7, 8, 3, 4}}; 351 | auto result = input | sort() | reverse() | to_vector(); 352 | auto expected = std::vector{{8, 7, 6, 4, 3, 3, 2, 1}}; 353 | CHECK(result == expected); 354 | } 355 | 356 | TEST_CASE("ranges in_groups_of_exactly, dynamic size") { 357 | auto input = seq() | take(1001) | to_vector(); 358 | 359 | size_t num_groups = input | in_groups_of_exactly(4) | count(); 360 | CHECK(num_groups == 250); 361 | 362 | // In optimized builds, compilers should be able to auto-vectorize this. 363 | std::array sums = {0.f, 0.f, 0.f, 0.f}; 364 | for (auto group : input | in_groups_of_exactly(4)) { 365 | std::get<0>(sums) += group.get(); 366 | group.next(); 367 | std::get<1>(sums) += group.get(); 368 | group.next(); 369 | std::get<2>(sums) += group.get(); 370 | group.next(); 371 | std::get<3>(sums) += group.get(); 372 | group.next(); 373 | } 374 | 375 | std::array expected_sums = {0.f, 0.f, 0.f, 0.f}; 376 | for (auto [i, x] : enumerate(input)) { 377 | if (i != 1000) 378 | expected_sums[i % 4] += x; 379 | } 380 | 381 | CHECK(sums == expected_sums); 382 | } 383 | 384 | TEST_CASE("ranges in_groups_of_exactly advance_by") { 385 | auto input = seq() | in_groups_of_exactly(4); 386 | size_t advanced = advance_by(input, 3); // already at the first group 387 | auto group = input.get(); 388 | CHECK(group.get() == 16); 389 | CHECK(advanced == 3); 390 | 391 | auto finite = seq() | take(11) | in_groups_of_exactly(4); 392 | advanced = advance_by(finite, 2); 393 | CHECK(finite.at_end()); 394 | CHECK(advanced == 1); 395 | } 396 | 397 | TEST_CASE("ranges in_groups_of") { 398 | const auto input = seq() | take(1001) | to_vector(); 399 | 400 | size_t num_groups_even = seq() | take(12) | in_groups_of(4) | count(); 401 | CHECK(num_groups_even == 3); 402 | 403 | size_t num_groups = input | in_groups_of(4) | count(); 404 | CHECK(num_groups == 251); 405 | 406 | std::array sums = {0.f, 0.f, 0.f, 0.f}; 407 | float last = 0.f; 408 | auto groups = input | in_groups_of(4); 409 | 410 | for (auto&& group : groups) { 411 | CHECK(!group.at_end()); 412 | size_t len = group | count(); 413 | if (len == 4) { 414 | std::get<0>(sums) += group.get(); 415 | group.next(); 416 | std::get<1>(sums) += group.get(); 417 | group.next(); 418 | std::get<2>(sums) += group.get(); 419 | group.next(); 420 | std::get<3>(sums) += group.get(); 421 | group.next(); 422 | } else { 423 | do { 424 | last = group.get(); 425 | group.next(); 426 | } while (!group.at_end()); 427 | } 428 | } 429 | 430 | std::array expected_sums = {0.f, 0.f, 0.f, 0.f}; 431 | float expected_last = 0.f; 432 | for (auto [i, x] : enumerate(input)) { 433 | if (i < 1000) { 434 | expected_sums[i % 4] += x; 435 | } else { 436 | expected_last = x; 437 | } 438 | } 439 | 440 | CHECK(sums == expected_sums); 441 | CHECK(last == expected_last); 442 | } 443 | 444 | TEST_CASE("ranges in_groups_of advance_by") { 445 | auto input = seq() | in_groups_of(4); 446 | advance_by(input, 3); // already at the first group 447 | auto group = input.get(); 448 | CHECK(group.get() == 16); 449 | 450 | auto finite = seq() | take(11) | in_groups_of(4); 451 | size_t advanced = advance_by(finite, 2); 452 | CHECK(advanced == 2); 453 | advanced = advance_by(finite, 1); 454 | CHECK(advanced == 0); 455 | CHECK(finite.at_end()); 456 | } 457 | 458 | TEST_CASE("ranges group_adjacent_by") { 459 | const auto input = seq() | take(10); 460 | 461 | auto pred = [](int x) { return x / 3; }; 462 | 463 | auto groups = input | group_adjacent_by(pred); 464 | auto tmp = groups | to_vector(); 465 | size_t num_groups = groups | count(); 466 | CHECK(num_groups == 4); 467 | 468 | int previous = std::numeric_limits::max(); 469 | for (const auto& group : groups) { 470 | for (auto x : group) { 471 | CHECK(pred(x) == pred(group.get())); 472 | CHECK(pred(x) != previous); 473 | } 474 | previous = pred(group.get()); 475 | } 476 | 477 | auto group_vectors = groups | transform(to_vector()) | to_vector(); 478 | CHECK(group_vectors.size() == 4); 479 | CHECK(group_vectors[0] == std::vector{{0, 1, 2}}); 480 | CHECK(group_vectors[1] == std::vector{{3, 4, 5}}); 481 | CHECK(group_vectors[2] == std::vector{{6, 7, 8}}); 482 | CHECK(group_vectors[3] == std::vector(1, 9)); // note: initializer lists are broken 483 | } 484 | 485 | TEST_CASE("ranges non-default-constructible") { 486 | struct Foo { 487 | const int x; 488 | constexpr explicit Foo(int x) : x(x) {} 489 | bool operator<(const Foo& other) const noexcept { 490 | return x < other.x; 491 | } 492 | }; 493 | static_assert(!std::is_default_constructible_v); 494 | 495 | auto generate_foos = seq() | transform([](int x) { return Foo{x}; }); 496 | 497 | auto vec = generate_foos | filter([](const Foo& foo) { return bool(foo.x % 2); }) 498 | | transform([](const Foo& foo) { return Foo{foo.x + 1}; }) | take(10) | to_vector(); 499 | static_cast(vec); 500 | 501 | std::vector vec2; 502 | generate_foos | take(10) | append(vec2); 503 | } 504 | 505 | TEST_CASE("ranges first after sort") { 506 | auto result = std::vector{{4, 3, 2, 1}} | sort() | first(); 507 | CHECK(result == 1); 508 | } 509 | 510 | TEST_CASE("ranges non-default-constructible, non-copyable predicate") { 511 | // A compare predicate that is not default-constructible or copyable. 512 | struct Compare : std::less { 513 | constexpr explicit Compare(int) {} 514 | constexpr Compare(Compare&&) {} 515 | constexpr Compare& operator=(Compare&&) { return *this; } 516 | constexpr Compare(const Compare&) = delete; 517 | constexpr Compare& operator=(const Compare&) = delete; 518 | }; 519 | static_assert(!std::is_default_constructible_v); 520 | 521 | auto in = seq() | take(10); 522 | auto vec = in | sort(Compare{0}) | to_vector(); 523 | CHECK(vec == (in | to_vector())); 524 | 525 | CHECK((in | sort(Compare{0}) | max(Compare{0})) == 9); 526 | CHECK((in | sort(Compare{0}) | min(Compare{0})) == 0); 527 | 528 | // Compile-time check that iterators don't introduce default-constructibility as a requirement. 529 | // Note: Explicit call to as_input_range() is required here because range-based for loops do not 530 | // perfectly-forward to `begin()/end()` (and it would usually be wrong if they did). 531 | for (auto x : as_input_range(in | sort(Compare{0}))) { 532 | static_cast(x); 533 | } 534 | } 535 | 536 | TEST_CASE("ranges empty_range") { 537 | CHECK((empty_range() | count()) == 0); 538 | CHECK((empty_range() | to_vector()) == std::vector()); 539 | } 540 | 541 | TEST_CASE("ranges chain") { 542 | // 0 arguments 543 | static_assert(std::is_same_v); 544 | 545 | // 1 argument 546 | static_assert(std::is_same_v); 547 | 548 | // 2 arguments 549 | auto homogenous_actual = chain("hello"s, "world"s) | append(""s); 550 | auto homogenous_expected = "helloworld"s; 551 | CHECK(homogenous_actual == homogenous_expected); 552 | 553 | // 3 arguments 554 | auto heterogeneous_actual = chain(seq() | take(4), "test"s, seq()) | take(10) | to_vector(); 555 | auto heterogeneous_expected = std::vector{{0,1,2,3,'t','e','s','t',0,1}}; 556 | CHECK(heterogeneous_actual == heterogeneous_expected); 557 | 558 | // Ensure ranges inbetween can be empty. 559 | homogenous_actual = chain(""s, "hello"s, "world"s) | append(""s); 560 | CHECK(homogenous_actual == homogenous_expected); 561 | 562 | homogenous_actual = chain("hello"s, ""s, "world"s) | append(""s); 563 | CHECK(homogenous_actual == homogenous_expected); 564 | 565 | homogenous_actual = chain("hello"s, "world"s, ""s) | append(""s); 566 | CHECK(homogenous_actual == homogenous_expected); 567 | } 568 | 569 | struct NoDefaultConstruction { 570 | NoDefaultConstruction() = delete; 571 | NoDefaultConstruction(int i_) : i{ i_ } {} 572 | 573 | int i; 574 | 575 | friend bool operator==(NoDefaultConstruction const& lhs, NoDefaultConstruction const& rhs) { return lhs.i == rhs.i; } 576 | }; 577 | 578 | TEST_CASE("ranges chain no-default-constructible type") { 579 | std::vector vec1{ 580 | NoDefaultConstruction(1), 581 | NoDefaultConstruction(2) 582 | }; 583 | 584 | std::vector vec2{ 585 | NoDefaultConstruction(3), 586 | NoDefaultConstruction(4) 587 | }; 588 | 589 | std::vector vec_expected{ 590 | NoDefaultConstruction(1), 591 | NoDefaultConstruction(2), 592 | NoDefaultConstruction(3), 593 | NoDefaultConstruction(4) 594 | }; 595 | 596 | auto vec_actual = chain(vec1, vec2) | to_vector(); 597 | CHECK(vec_actual == vec_expected); 598 | } 599 | 600 | TEST_CASE("ranges chain advance_by") { 601 | auto input = chain(seq() | take(3), seq(10) | take(3), seq(20) | take(3)); 602 | auto result1 = input | to_vector(); 603 | CHECK(result1 == std::vector{{0, 1, 2, 10, 11, 12, 20, 21, 22}}); 604 | advance_by(input, 4); 605 | auto result2 = input | to_vector(); 606 | CHECK(result2 == std::vector{{11, 12, 20, 21, 22}}); 607 | advance_by(input, 3); 608 | auto result3 = input | to_vector(); 609 | CHECK(result3 == std::vector{{21, 22}}); 610 | advance_by(input, 3); // advance beyond end 611 | CHECK(input.at_end()); 612 | } 613 | 614 | TEST_CASE("ranges cycle") { 615 | auto nothing = seq() | take(0) | cycle() | take(10) | to_vector(); 616 | CHECK(nothing == std::vector(0, 0)); 617 | 618 | auto zeroes = seq() | take(1) | cycle() | take(10) | to_vector(); 619 | CHECK(zeroes == std::vector(10, 0)); 620 | 621 | auto zero_one_two = seq() | take(3) | cycle() | take(10) | to_vector(); 622 | CHECK(zero_one_two == std::vector{{0, 1, 2, 0, 1, 2, 0, 1, 2, 0}}); 623 | } 624 | 625 | TEST_CASE("ranges cycle advance_by") { 626 | auto input = seq() | take(5) | cycle(); 627 | advance_by(input, 5); // advancing to the end should wrap around 628 | CHECK(input.get() == 0); 629 | advance_by(input, 6); // overflow 630 | CHECK(input.get() == 1); 631 | } 632 | 633 | TEST_CASE("ranges padded") { 634 | auto actual = seq() | take(3) | padded(-1) | take(5) | to_vector(); 635 | auto expected = std::vector{{0,1,2,-1,-1}}; 636 | CHECK(actual == expected); 637 | } 638 | 639 | TEST_CASE("ranges padded advance_by") { 640 | auto actual = seq() | take(3) | padded(-1); 641 | CHECK(actual.get() == 0); 642 | advance_by(actual, 2); 643 | CHECK(actual.get() == 2); 644 | advance_by(actual, 1); 645 | CHECK(actual.get() == -1); 646 | } 647 | 648 | TEST_CASE("ranges zip_longest") { 649 | auto input1 = seq() | first_n(5); 650 | auto input2 = input1 | transform(&to_string); 651 | auto input3 = seq(10) | first_n(7); 652 | auto zipped = zip_longest(input1, input2, input3) | to_vector(); 653 | CHECK(zipped.size() == 7); 654 | auto expected = std::vector{ 655 | std::make_tuple(RX_OPTIONAL(0), RX_OPTIONAL("0"s), RX_OPTIONAL(10)), 656 | std::make_tuple(RX_OPTIONAL(1), RX_OPTIONAL("1"s), RX_OPTIONAL(11)), 657 | std::make_tuple(RX_OPTIONAL(2), RX_OPTIONAL("2"s), RX_OPTIONAL(12)), 658 | std::make_tuple(RX_OPTIONAL(3), RX_OPTIONAL("3"s), RX_OPTIONAL(13)), 659 | std::make_tuple(RX_OPTIONAL(4), RX_OPTIONAL("4"s), RX_OPTIONAL(14)), 660 | std::make_tuple(RX_OPTIONAL(), RX_OPTIONAL(), RX_OPTIONAL(15)), 661 | std::make_tuple(RX_OPTIONAL(), RX_OPTIONAL(), RX_OPTIONAL(16)), 662 | }; 663 | CHECK(zipped == expected); 664 | } 665 | 666 | TEST_CASE("ranges zip_longest advance_by") { 667 | auto input1 = seq() | first_n(5); 668 | auto input2 = input1 | transform(&to_string); 669 | auto input3 = seq(10) | first_n(7); 670 | auto zipped = zip_longest(input1, input2, input3); 671 | advance_by(zipped, 4); 672 | auto expected1 = std::make_tuple(RX_OPTIONAL(4), RX_OPTIONAL("4"s), RX_OPTIONAL(14)); 673 | CHECK(zipped.get() == expected1); 674 | advance_by(zipped, 2); 675 | auto expected2 = std::make_tuple(RX_OPTIONAL(), RX_OPTIONAL(), RX_OPTIONAL(16)); 676 | CHECK(zipped.get() == expected2); 677 | size_t advanced = advance_by(zipped, 2); 678 | CHECK(zipped.at_end()); 679 | CHECK(advanced == 1); 680 | } 681 | 682 | TEST_CASE("ranges tee") { 683 | auto container1 = std::vector(0, 0); 684 | auto container2 = std::vector(0, 0); 685 | seq() | tee(container1) | take(10) | append(container2); 686 | CHECK(container1 == container2); 687 | 688 | container1.clear(); 689 | auto value = seq() | tee(container1) | take(10) | sum(); 690 | CHECK(container1 == container2); 691 | CHECK(value == 9 * 10 / 2); 692 | } 693 | 694 | TEST_CASE("ranges ad-hoc lambdas") { 695 | auto f = [](auto&& range) { 696 | return range | filter([](auto x) { return x % 2 == 1; }) | take(5); 697 | }; 698 | 699 | auto result = seq() | f | to_vector(); 700 | CHECK(result == std::vector{{1, 3, 5, 7, 9}}); 701 | } 702 | 703 | TEST_CASE("ranges flatten") { 704 | auto l0 = seq(11) | take(3); 705 | auto l1 = fill_n(3, l0); 706 | auto l2 = fill_n(3, l1); 707 | auto l3 = fill_n(3, l2); 708 | 709 | auto flatten0 = l3 | flatten<0>(); 710 | auto flatten1 = l3 | flatten(); 711 | auto flatten2 = l3 | flatten<2>(); 712 | auto flatten3 = l3 | flatten<3>(); 713 | 714 | CHECK((flatten0 | count()) == 3); 715 | CHECK((flatten1 | count()) == 3*3); 716 | CHECK((flatten2 | count()) == 3*3*3); 717 | CHECK((flatten3 | count()) == 3*3*3*3); 718 | 719 | CHECK((flatten3 | sum()) == 3*3*3 * (11+12+13)); 720 | } 721 | 722 | TEST_CASE("ranges null_sink") { 723 | int a = 0; 724 | int b = 0; 725 | generate([&]{ return ++a; }) | take(5) | transform([&](auto v) { CHECK(v == ++b); return v; }) | append(null_sink()); 726 | CHECK(a == 5); 727 | CHECK(b == 5); 728 | } 729 | 730 | namespace { 731 | 732 | // NOTE: When modifying this, make sure to modify README.md as well! 733 | struct convert_to_string { 734 | template 735 | struct Range { 736 | using output_type = std::string; 737 | static constexpr bool is_finite = rx::is_finite_v; 738 | static constexpr bool is_idempotent = rx::is_idempotent_v; 739 | 740 | Input input; 741 | constexpr explicit Range(Input input) : input(std::move(input)) {} 742 | 743 | [[nodiscard]] output_type get() const noexcept { 744 | return std::to_string(input.get()); 745 | } 746 | 747 | constexpr void next() noexcept { 748 | input.next(); 749 | } 750 | 751 | [[nodiscard]] constexpr bool at_end() const noexcept { 752 | return input.at_end(); 753 | } 754 | 755 | [[nodiscard]] constexpr size_t size_hint() const noexcept { 756 | return input.size_hint(); 757 | } 758 | 759 | constexpr size_t advance_by(size_t n) const noexcept { 760 | using rx::advance_by; // Enable ADL. 761 | return advance_by(input, n); 762 | } 763 | }; 764 | 765 | template 766 | [[nodiscard]] constexpr auto operator()(Input&& input) const { 767 | using Inner = decltype(rx::as_input_range(std::forward(input))); 768 | return Range(rx::as_input_range(std::forward(input))); 769 | } 770 | }; 771 | std::vector convert_ints_to_sorted_strings(std::vector input) { 772 | return input | convert_to_string() | rx::sort() | rx::to_vector(); 773 | } 774 | struct normalize { 775 | template 776 | struct Range { 777 | using output_type = rx::get_output_type_of_t; 778 | Input input; 779 | constexpr explicit Range(Input input) : input(std::move(input)) {} 780 | template 781 | constexpr void sink(Out& out) && noexcept { 782 | rx::sink(std::move(input), out); 783 | auto square = [](auto x) { return x * x; }; 784 | auto length = std::sqrt(out | transform(square) | sum()); 785 | for (auto& x : out) { 786 | x /= length; 787 | } 788 | } 789 | }; 790 | 791 | template 792 | [[nodiscard]] constexpr auto operator()(Input&& input) const { 793 | // Note: Here we are not using `as_input_range()`, because that would allocate 794 | // temporary storage for each sink in a chain. 795 | using Inner = rx::remove_cvref_t; 796 | return Range(std::forward(input)); 797 | } 798 | }; 799 | 800 | // Using our new sink in practice: 801 | std::vector normalize_vector(std::vector input) { 802 | return input | normalize() | to_vector(); 803 | } 804 | 805 | struct average { 806 | template 807 | constexpr auto operator()(Input&& input) const { 808 | using element_type = rx::get_output_type_of_t; 809 | auto [count, summed] = rx::zip(rx::seq(1, 0), input) 810 | | rx::foldl(std::tuple(size_t(0), element_type{0}), 811 | [](auto&& accum, auto&& element) { 812 | return std::tuple(std::get<0>(accum) + std::get<0>(element), 813 | std::get<1>(accum) + std::get<1>(element)); 814 | }); 815 | return summed / element_type(count); 816 | } 817 | }; 818 | 819 | // Using our new aggregator in practice: 820 | double compute_average(std::vector values) { 821 | return values | average(); 822 | } 823 | } // anonymous namespace 824 | 825 | TEST_CASE("ranges doc examples test") { 826 | auto strings = convert_ints_to_sorted_strings(std::vector{{3, 1, 2, 3, 4}}); 827 | CHECK(strings == std::vector{{"1"s, "2"s, "3"s, "3"s, "4"s}}); 828 | 829 | // length is 9 — this happens to produce exact floating point results. 830 | auto normalized = normalize_vector(std::vector{{4.0, 8.0, 1.0}}); 831 | CHECK(normalized == std::vector{{4.0 / 9, 8.0 / 9, 1.0 / 9}}); 832 | 833 | auto avg = compute_average(std::vector{{1.0, 2.0, 3.0, 4.0, 5.0}}); 834 | CHECK(avg == 3.0); 835 | } 836 | 837 | /* 838 | TEST_CASE("ranges append to non-container [no compile]") { 839 | double not_a_container = 0; 840 | seq() | first_n(10) | append(not_a_container); 841 | } 842 | 843 | TEST_CASE("ranges infinite to vector [no compile]") { 844 | auto s = seq() | to_vector(); 845 | } 846 | TEST_CASE("ranges sort infinite [no compile]") { 847 | auto s = seq() | sort() | to_vector(); 848 | } 849 | */ 850 | --------------------------------------------------------------------------------