├── codecov.yml ├── .gitignore ├── test ├── source │ ├── main.cpp │ └── easy_iterator.cpp └── CMakeLists.txt ├── .clang-format ├── .github └── workflows │ ├── style.yml │ ├── macos.yml │ ├── windows.yml │ ├── ubuntu.yml │ ├── install.yml │ └── benchmark.yml ├── examples ├── iteration.cpp ├── CMakeLists.txt ├── fibonacci.cpp └── array.cpp ├── cmake └── CPM.cmake ├── LICENSE ├── benchmark ├── CMakeLists.txt └── benchmark.cpp ├── .cmake-format ├── CMakeLists.txt ├── README.md └── include └── easy_iterator.h /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | .venv 4 | /build* -------------------------------------------------------------------------------- /test/source/main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | AccessModifierOffset: '-2' 4 | AlignTrailingComments: 'true' 5 | AllowAllParametersOfDeclarationOnNextLine: 'false' 6 | AlwaysBreakTemplateDeclarations: 'No' 7 | BreakBeforeBraces: Attach 8 | ColumnLimit: '100' 9 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 10 | IncludeBlocks: Regroup 11 | IndentPPDirectives: AfterHash 12 | IndentWidth: '2' 13 | NamespaceIndentation: All 14 | BreakBeforeBinaryOperators: All 15 | BreakBeforeTernaryOperators: 'true' 16 | ... 17 | -------------------------------------------------------------------------------- /.github/workflows/style.yml: -------------------------------------------------------------------------------- 1 | name: Style 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | branches: 10 | - master 11 | - main 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install format dependencies 22 | run: pip3 install clang-format==14.0.6 cmake_format==0.6.11 pyyaml 23 | 24 | - name: configure 25 | run: cmake -Stest -Bbuild 26 | 27 | - name: check style 28 | run: cmake --build build --target check-format 29 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | branches: 10 | - master 11 | - main 12 | 13 | env: 14 | CTEST_OUTPUT_ON_FAILURE: 1 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: macos-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: configure 25 | run: cmake -Htest -Bbuild 26 | 27 | - name: build 28 | run: cmake --build build --config Debug -j4 29 | 30 | - name: test 31 | run: | 32 | cd build 33 | ctest --build-config Debug 34 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | branches: 10 | - master 11 | - main 12 | 13 | env: 14 | CTEST_OUTPUT_ON_FAILURE: 1 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: windows-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: configure 25 | run: cmake -Htest -Bbuild 26 | 27 | - name: build 28 | run: cmake --build build --config Debug -j4 29 | 30 | - name: test 31 | run: | 32 | cd build 33 | ctest --build-config Debug 34 | -------------------------------------------------------------------------------- /examples/iteration.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | int main() { 9 | using namespace easy_iterator; 10 | 11 | std::vector integers(10); 12 | 13 | for (auto i : range(integers.size())) { 14 | integers[i] = i * i; 15 | } 16 | 17 | std::vector strings(integers.size()); 18 | for (auto [i, v, s] : zip(range(integers.size()), integers, strings)) { 19 | s = std::to_string(i) + "^2 = " + std::to_string(v); 20 | } 21 | 22 | for (auto [i, s] : enumerate(strings)) { 23 | std::cout << "strings[" << i << "] = \"" << s << "\"" << std::endl; 24 | } 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 2 | 3 | # ---- Project ---- 4 | 5 | project(Examples CXX) 6 | 7 | # ---- Dependencies ---- 8 | 9 | include(../cmake/CPM.cmake) 10 | 11 | CPMAddPackage(NAME EasyIterator SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) 12 | 13 | # ---- Create binaries ---- 14 | 15 | file(GLOB examples ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 16 | 17 | foreach(example_source_file ${examples}) 18 | get_filename_component(filename ${example_source_file} NAME) 19 | string(REPLACE ".cpp" "" example_name ${filename}) 20 | add_executable(${example_name} ${example_source_file}) 21 | target_link_libraries(${example_name} EasyIterator) 22 | set_target_properties(${example_name} PROPERTIES CXX_STANDARD 17) 23 | endforeach() 24 | -------------------------------------------------------------------------------- /examples/fibonacci.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | using integer = unsigned long long; 8 | 9 | struct Fibonacci { 10 | integer current = 0; 11 | integer next = 1; 12 | 13 | bool advance() { 14 | if (std::numeric_limits::max() - current < next) { 15 | // abort before integer overflow 16 | return false; 17 | } 18 | auto tmp = next; 19 | next += current; 20 | current = tmp; 21 | return true; 22 | } 23 | 24 | integer value() const { return current; } 25 | }; 26 | 27 | using namespace easy_iterator; 28 | 29 | int main() { 30 | for (auto [i, v] : enumerate(MakeIterable())) { 31 | std::cout << "Fib_" << i << "\t= " << v << std::endl; 32 | } 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | branches: 10 | - master 11 | - main 12 | 13 | env: 14 | CTEST_OUTPUT_ON_FAILURE: 1 15 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: configure 26 | run: cmake -Htest -Bbuild -DENABLE_TEST_COVERAGE=1 27 | 28 | - name: build 29 | run: cmake --build build --config Debug -j4 30 | 31 | - name: test 32 | run: | 33 | cd build 34 | ctest --build-config Debug 35 | 36 | - name: collect code coverage 37 | run: bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" 38 | -------------------------------------------------------------------------------- /.github/workflows/install.yml: -------------------------------------------------------------------------------- 1 | name: Install 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | branches: 10 | - master 11 | - main 12 | 13 | env: 14 | CTEST_OUTPUT_ON_FAILURE: 1 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: build and install library 25 | run: | 26 | cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release 27 | sudo cmake --build build --target install 28 | rm -rf build 29 | 30 | - name: configure 31 | run: cmake -Htest -Bbuild -DTEST_INSTALLED_VERSION=1 32 | 33 | - name: build 34 | run: cmake --build build --config Debug -j4 35 | 36 | - name: test 37 | run: | 38 | cd build 39 | ctest --build-config Debug 40 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | branches: 10 | - master 11 | - main 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | # https://stackoverflow.com/questions/39332406/install-libc-on-ubuntu 22 | - name: install dependencies 23 | run: | 24 | sudo apt-get update 25 | sudo apt-get install libc++-dev libc++abi-dev 26 | 27 | - name: configure 28 | # flags set to avoid strange regular expression backend error: https://github.com/google/benchmark/issues/773 29 | run: cmake -Hbenchmark -Bbuild -DRUN_HAVE_STD_REGEX=0 -DRUN_HAVE_POSIX_REGEX=0 30 | 31 | - name: build 32 | run: cmake --build build -j4 33 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.40.2) 6 | set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lars Melchior 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/array.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | using integer = unsigned long long; 8 | 9 | template class MyArray { 10 | private: 11 | T *data; 12 | size_t size; 13 | 14 | public: 15 | using iterator = easy_iterator::ReferenceIterator; 16 | using const_iterator = easy_iterator::ReferenceIterator; 17 | 18 | explicit MyArray(size_t _size) : size(_size), data(new T[_size]) {} 19 | MyArray(const MyArray &) = delete; 20 | ~MyArray() { delete[] data; } 21 | 22 | int &operator[](size_t idx) { return data[idx]; } 23 | const T &operator[](size_t idx) const { return data[idx]; } 24 | 25 | iterator begin() { return iterator(data); } 26 | iterator end() { return iterator(data + size); } 27 | const_iterator begin() const { return const_iterator(data); } 28 | const_iterator end() const { return const_iterator(data + size); } 29 | }; 30 | 31 | using namespace easy_iterator; 32 | 33 | int main() { 34 | MyArray arr(10); 35 | 36 | for (auto [i, v] : enumerate(arr)) { 37 | v = i * i; 38 | } 39 | 40 | for (auto [i, v] : enumerate(std::as_const(arr))) { 41 | std::cout << "arr[" << i << "] = " << v << std::endl; 42 | } 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | option(EASY_ITERATOR_COMPARE_WITH_ITERTOOLS "benchmark itertools" OFF) 4 | 5 | # ---- create project ---- 6 | 7 | project(EasyIteratorBenchmark LANGUAGES CXX) 8 | 9 | # ---- Dependencies ---- 10 | 11 | include(../cmake/CPM.cmake) 12 | 13 | CPMAddPackage(NAME EasyIterator SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) 14 | 15 | CPMAddPackage( 16 | NAME benchmark 17 | GITHUB_REPOSITORY google/benchmark 18 | VERSION 1.5.0 19 | OPTIONS "BENCHMARK_ENABLE_TESTING Off" "BENCHMARK_USE_LIBCXX ON" 20 | ) 21 | 22 | if(benchmark_ADDED) 23 | # patch benchmark target 24 | set_target_properties(benchmark PROPERTIES CXX_STANDARD 17) 25 | endif() 26 | 27 | CPMAddPackage( 28 | NAME itertools 29 | GIT_REPOSITORY https://github.com/ryanhaining/cppitertools.git 30 | VERSION 1.0 31 | DOWNLOAD_ONLY Yes 32 | ) 33 | 34 | if(itertools_ADDED) 35 | add_library(itertools INTERFACE) 36 | target_include_directories(itertools INTERFACE ${itertools_SOURCE_DIR}) 37 | endif() 38 | 39 | # ---- Create standalone executable ---- 40 | 41 | add_executable(EasyIteratorBenchmark "benchmark.cpp") 42 | set_target_properties(EasyIteratorBenchmark PROPERTIES CXX_STANDARD 17) 43 | target_link_libraries(EasyIteratorBenchmark benchmark itertools EasyIterator) 44 | target_compile_definitions(EasyIteratorBenchmark PRIVATE "COMPARE_WITH_ITERTOOLS=1") 45 | -------------------------------------------------------------------------------- /.cmake-format: -------------------------------------------------------------------------------- 1 | format: 2 | tab_size: 2 3 | line_width: 100 4 | dangle_parens: true 5 | 6 | parse: 7 | additional_commands: 8 | cpmaddpackage: 9 | pargs: 10 | nargs: '*' 11 | flags: [] 12 | spelling: CPMAddPackage 13 | kwargs: &cpmaddpackagekwargs 14 | NAME: 1 15 | FORCE: 1 16 | VERSION: 1 17 | GIT_TAG: 1 18 | DOWNLOAD_ONLY: 1 19 | GITHUB_REPOSITORY: 1 20 | GITLAB_REPOSITORY: 1 21 | GIT_REPOSITORY: 1 22 | SVN_REPOSITORY: 1 23 | SVN_REVISION: 1 24 | SOURCE_DIR: 1 25 | DOWNLOAD_COMMAND: 1 26 | FIND_PACKAGE_ARGUMENTS: 1 27 | NO_CACHE: 1 28 | GIT_SHALLOW: 1 29 | URL: 1 30 | URL_HASH: 1 31 | URL_MD5: 1 32 | DOWNLOAD_NAME: 1 33 | DOWNLOAD_NO_EXTRACT: 1 34 | HTTP_USERNAME: 1 35 | HTTP_PASSWORD: 1 36 | OPTIONS: + 37 | cpmfindpackage: 38 | pargs: 39 | nargs: '*' 40 | flags: [] 41 | spelling: CPMFindPackage 42 | kwargs: *cpmaddpackagekwargs 43 | packageproject: 44 | pargs: 45 | nargs: '*' 46 | flags: [] 47 | spelling: packageProject 48 | kwargs: 49 | NAME: 1 50 | VERSION: 1 51 | NAMESPACE: 1 52 | INCLUDE_DIR: 1 53 | INCLUDE_DESTINATION: 1 54 | BINARY_DIR: 1 55 | COMPATIBILITY: 1 56 | VERSION_HEADER: 1 57 | DEPENDENCIES: + 58 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 2 | 3 | # ---- Project ---- 4 | 5 | project( 6 | EasyIterator 7 | VERSION 1.5 8 | LANGUAGES CXX 9 | ) 10 | 11 | # ---- Include guards ---- 12 | 13 | if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) 14 | message( 15 | FATAL_ERROR 16 | "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there." 17 | ) 18 | endif() 19 | 20 | # ---- Add dependencies via CPM ---- 21 | # see https://github.com/TheLartians/CPM.cmake for more info 22 | 23 | include(cmake/CPM.cmake) 24 | 25 | # PackageProject.cmake will be used to make our target installable 26 | CPMAddPackage( 27 | NAME PackageProject.cmake 28 | GITHUB_REPOSITORY TheLartians/PackageProject.cmake 29 | VERSION 1.0 30 | ) 31 | 32 | # ---- Header target ---- 33 | 34 | file(GLOB_RECURSE headers CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") 35 | add_library(EasyIterator-headers EXCLUDE_FROM_ALL ${headers}) 36 | set_target_properties(EasyIterator-headers PROPERTIES LINKER_LANGUAGE CXX) 37 | 38 | # ---- Create library ---- 39 | 40 | add_library(EasyIterator INTERFACE) 41 | 42 | target_compile_options(EasyIterator INTERFACE "$<$:/permissive->") 43 | set_target_properties(EasyIterator PROPERTIES INTERFACE_COMPILE_FEATURES cxx_std_17) 44 | 45 | target_include_directories( 46 | EasyIterator INTERFACE $ 47 | $ 48 | ) 49 | 50 | # ---- Create an installable target ---- 51 | 52 | packageProject( 53 | NAME ${PROJECT_NAME} 54 | VERSION ${PROJECT_VERSION} 55 | BINARY_DIR ${PROJECT_BINARY_DIR} 56 | INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include 57 | INCLUDE_DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION} 58 | ) 59 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 2 | 3 | project(EasyIteratorTests LANGUAGES CXX) 4 | 5 | # ---- Options ---- 6 | 7 | option(ENABLE_TEST_COVERAGE "Enable test coverage" OFF) 8 | option(TEST_INSTALLED_VERSION "Test the version found by find_package" OFF) 9 | 10 | # ---- Dependencies ---- 11 | 12 | include(../cmake/CPM.cmake) 13 | 14 | CPMAddPackage("gh:doctest/doctest@2.4.11") 15 | 16 | if(TEST_INSTALLED_VERSION) 17 | find_package(EasyIterator REQUIRED) 18 | else() 19 | CPMAddPackage(NAME EasyIterator SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) 20 | endif() 21 | 22 | CPMAddPackage("gh:TheLartians/Format.cmake@1.7.3") 23 | 24 | # ---- Create binary ---- 25 | 26 | file(GLOB sources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp) 27 | add_executable(EasyIteratorTests ${sources}) 28 | target_link_libraries(EasyIteratorTests doctest EasyIterator) 29 | 30 | set_target_properties(EasyIteratorTests PROPERTIES CXX_STANDARD 17) 31 | 32 | # enable compiler warnings 33 | if(NOT TEST_INSTALLED_VERSION) 34 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 35 | target_compile_options(EasyIterator INTERFACE -Wall -pedantic -Wextra -Werror) 36 | elseif(MSVC) 37 | target_compile_options(EasyIterator INTERFACE /W4 /WX) 38 | endif() 39 | endif() 40 | 41 | # ---- Add EasyIteratorTests ---- 42 | 43 | enable_testing() 44 | 45 | # Note: doctest and similar testing frameworks can automatically configure CMake tests For other 46 | # testing frameworks add the tests target instead: add_test(EasyIteratorTests EasyIteratorTests) 47 | 48 | include(${doctest_SOURCE_DIR}/scripts/cmake/doctest.cmake) 49 | doctest_discover_tests(EasyIteratorTests) 50 | 51 | # ---- code coverage ---- 52 | 53 | if(ENABLE_TEST_COVERAGE) 54 | target_compile_options(EasyIterator INTERFACE -O0 -g -fprofile-arcs -ftest-coverage --coverage) 55 | target_link_options(EasyIterator INTERFACE "--coverage") 56 | endif() 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/TheLartians/EasyIterator/workflows/MacOS/badge.svg)](https://github.com/TheLartians/EasyIterator/actions) 2 | [![Actions Status](https://github.com/TheLartians/EasyIterator/workflows/Windows/badge.svg)](https://github.com/TheLartians/EasyIterator/actions) 3 | [![Actions Status](https://github.com/TheLartians/EasyIterator/workflows/Ubuntu/badge.svg)](https://github.com/TheLartians/EasyIterator/actions) 4 | [![Actions Status](https://github.com/TheLartians/EasyIterator/workflows/Style/badge.svg)](https://github.com/TheLartians/EasyIterator/actions) 5 | [![Actions Status](https://github.com/TheLartians/EasyIterator/workflows/Install/badge.svg)](https://github.com/TheLartians/EasyIterator/actions) 6 | [![codecov](https://codecov.io/gh/TheLartians/EasyIterator/branch/master/graph/badge.svg)](https://codecov.io/gh/TheLartians/EasyIterator) 7 | 8 | # EasyIterator 9 | 10 | C++ iterators and range-based loops are incredibly useful, however defining iterators still requires a large amount of boilerplate code. 11 | The goal of this library is to find alternative and useful ways to use and create C++17 iterators without impacting performance or compiler optimizations. 12 | 13 | ## Example 14 | 15 | ### Iteration 16 | 17 | EasyIterator adds well-known generators and iterator combinators from other languages to C++, such as `range`, `zip` and `enumerate`. 18 | 19 | ```cpp 20 | using namespace easy_iterator; 21 | 22 | std::vector integers(10); 23 | std::vector strings(integers.size()); 24 | 25 | for (auto i: range(integers.size())) { 26 | integers[i] = i*i; 27 | } 28 | 29 | for (auto [i, v, s]: zip(range(integers.size()), integers, strings)) { 30 | s = std::to_string(i) + "^2 = " + std::to_string(v); 31 | } 32 | 33 | for (auto [i, s]: enumerate(strings)) { 34 | std::cout << "strings[" << i << "] = \"" << s << "\"" << std::endl; 35 | } 36 | ``` 37 | 38 | ### Iterator definition 39 | 40 | Most iterator boilerplate code is defined in an `easy_iterator::IteratorPrototype` base class type. 41 | A possible implementation of the `range` iterable is below. 42 | 43 | ```cpp 44 | using namespace easy_iterator; 45 | 46 | template struct RangeIterator: public IteratorPrototype { 47 | T increment; 48 | 49 | RangeIterator(const T &start): 50 | IteratorPrototype(start), 51 | increment(1) { 52 | } 53 | 54 | RangeIterator &operator++(){ RangeIterator::value += increment; return *this; } 55 | }; 56 | 57 | template auto range(T end) { 58 | return wrap(RangeIterator(begin), RangeIterator(end)); 59 | } 60 | ``` 61 | 62 | ### Iterable algorithms 63 | 64 | Algorithms can be easily wrapped into iterators by defining a class that defines `advance()` and `value()` member functions. The code below shows how to define an iterator over Fibonacci numbers. 65 | 66 | ```cpp 67 | struct Fibonacci { 68 | unsigned current = 0; 69 | unsigned next = 1; 70 | 71 | void advance() { 72 | auto tmp = next; 73 | next += current; 74 | current = tmp; 75 | } 76 | 77 | unsigned value() { 78 | return current; 79 | } 80 | }; 81 | 82 | using namespace easy_iterator; 83 | 84 | for (auto [i,v]: enumerate(MakeIterable())){ 85 | std::cout << "Fib_" << i << "\t= " << v << std::endl; 86 | if (i == 10) break; 87 | } 88 | ``` 89 | 90 | Algorithms that have an end state can also be defined by returning a the state in the `advance()` method. If the initial state can also be undefined, the iterator should define a `bool init()` method and inherit from `easy_iterator::InitializedIterable`. The code below shows an alternative `range` implementation. 91 | 92 | ```cpp 93 | template struct RangeIterator: public easy_iterator::InitializedIterable { 94 | T current, max, step; 95 | RangeIterator(T end): current(0), max(end), step(1) { } 96 | bool advance(){ current += step; return current != max; } 97 | bool init(){ return current != max; } 98 | T value(){ return current; } 99 | }; 100 | 101 | template auto range(T end) { 102 | return easy_iterator::MakeIterable>(end); 103 | } 104 | ``` 105 | 106 | ## Installation and usage 107 | 108 | EasyIterator is a single-header library, so you can simply download and copy the header into your project, or use the Cmake script to install it globally. 109 | Using the [CPM](https://github.com/cpm-cmake/CPM.cmake) dependency manager, you can also include EasyIterator simply by adding the following to your projects' `CMakeLists.txt`. 110 | 111 | ```cmake 112 | CPMAddPackage("gh:thelartians/easyiterator@1.5") 113 | 114 | target_link_libraries(myProject EasyIterator) 115 | set_target_properties(myProject PROPERTIES CXX_STANDARD 17) 116 | ``` 117 | 118 | ## Test suite 119 | 120 | You can run the tests suite included in this repo with the following commands. 121 | 122 | ```bash 123 | cmake -Htest -Bbuild/test 124 | cmake --build build/test 125 | cmake --build build/test --target test 126 | ``` 127 | 128 | ## Performance 129 | 130 | EasyIterator is designed to come with little or no performance impact compared to handwritten code. For example, using `for(auto i: range(N))` loops create identical assembly compared to regular `for(auto i=0;i 2 | #include 3 | 4 | #ifdef COMPARE_WITH_ITERTOOLS 5 | # include 6 | # include 7 | # include 8 | #endif 9 | 10 | #include 11 | #include 12 | 13 | using Integer = unsigned long long; 14 | 15 | template void AssertEqual(const A &a, const B &b) { 16 | if (a != b) { 17 | throw std::runtime_error("assertion failed: " + std::to_string(a) + " != " + std::to_string(b)); 18 | } 19 | } 20 | 21 | Integer __attribute__((noinline)) easyRangeLoop(Integer max) { 22 | Integer result = 0; 23 | for (auto i : easy_iterator::range(max + 1)) { 24 | result += i; 25 | } 26 | return result; 27 | } 28 | 29 | void EasyRangeLoop(benchmark::State &state) { 30 | Integer max = 10000; 31 | for (auto _ : state) { 32 | benchmark::DoNotOptimize(max); 33 | AssertEqual(easyRangeLoop(max), max * (max + 1) / 2); 34 | } 35 | } 36 | 37 | BENCHMARK(EasyRangeLoop); 38 | 39 | Integer __attribute__((noinline)) easyCustomRangeLoop(Integer max) { 40 | struct CustomRangeIterator : public easy_iterator::InitializedIterable { 41 | Integer current, max, step; 42 | 43 | CustomRangeIterator(Integer start, Integer end, Integer increment) 44 | : current(start), max(end - ((end - start) % increment)), step(increment) {} 45 | 46 | CustomRangeIterator(Integer start, Integer end) : CustomRangeIterator(start, end, 1) {} 47 | explicit CustomRangeIterator(Integer max) : CustomRangeIterator(0, max, 1) {} 48 | 49 | bool init() { return current != max; } 50 | bool advance() { 51 | current += step; 52 | return current != max; 53 | } 54 | Integer value() { return current; } 55 | }; 56 | 57 | Integer result = 0; 58 | for (auto i : easy_iterator::MakeIterable(max + 1)) { 59 | result += i; 60 | } 61 | 62 | return result; 63 | } 64 | 65 | void EasyCustomRangeLoop(benchmark::State &state) { 66 | Integer max = 10000; 67 | for (auto _ : state) { 68 | benchmark::DoNotOptimize(max); 69 | AssertEqual(easyCustomRangeLoop(max), max * (max + 1) / 2); 70 | } 71 | } 72 | 73 | BENCHMARK(EasyCustomRangeLoop); 74 | 75 | #ifdef COMPARE_WITH_ITERTOOLS 76 | 77 | Integer __attribute__((noinline)) iterRangeLoop(Integer max) { 78 | Integer result = 0; 79 | for (auto i : iter::range(max + 1)) { 80 | result += i; 81 | } 82 | return result; 83 | } 84 | 85 | void IterRangeLoop(benchmark::State &state) { 86 | Integer max = 10000; 87 | for (auto _ : state) { 88 | benchmark::DoNotOptimize(max); 89 | AssertEqual(easyRangeLoop(max), max * (max + 1) / 2); 90 | } 91 | } 92 | 93 | BENCHMARK(IterRangeLoop); 94 | 95 | #endif 96 | 97 | Integer __attribute__((noinline)) forLoop(Integer max) { 98 | Integer result = 0; 99 | for (auto i = 0; i < max + 1; ++i) { 100 | result += i; 101 | } 102 | return result; 103 | } 104 | 105 | void ForLoop(benchmark::State &state) { 106 | Integer max = 10000; 107 | for (auto _ : state) { 108 | benchmark::DoNotOptimize(max); 109 | AssertEqual(forLoop(max), max * (max + 1) / 2); 110 | } 111 | } 112 | 113 | BENCHMARK(ForLoop); 114 | 115 | Integer __attribute__((noinline)) easyArrayIteration(const std::vector &values) { 116 | using namespace easy_iterator; 117 | Integer result = 0; 118 | for (auto &i : valuesBetween(values.data(), values.data() + values.size())) { 119 | result += i; 120 | } 121 | return result; 122 | } 123 | 124 | void EasyArrayIteration(benchmark::State &state) { 125 | Integer max = 10000; 126 | std::vector values(max + 1); 127 | easy_iterator::copy(easy_iterator::range(max + 1), values); 128 | for (auto _ : state) { 129 | benchmark::DoNotOptimize(values); 130 | AssertEqual(easyArrayIteration(values), max * (max + 1) / 2); 131 | } 132 | } 133 | 134 | BENCHMARK(EasyArrayIteration); 135 | 136 | Integer __attribute__((noinline)) stdArrayIteration(const std::vector &values) { 137 | Integer result = 0; 138 | for (auto &i : values) { 139 | result += i; 140 | } 141 | return result; 142 | } 143 | 144 | void StdArrayIteration(benchmark::State &state) { 145 | Integer max = 10000; 146 | std::vector values(max + 1); 147 | easy_iterator::copy(easy_iterator::range(max + 1), values); 148 | for (auto _ : state) { 149 | benchmark::DoNotOptimize(values); 150 | AssertEqual(stdArrayIteration(values), max * (max + 1) / 2); 151 | } 152 | } 153 | 154 | BENCHMARK(StdArrayIteration); 155 | 156 | void __attribute__((noinline)) 157 | easyZipIteration(const std::vector &integers, const std::vector &doubles) { 158 | for (auto [i, d] : easy_iterator::zip(integers, doubles)) { 159 | AssertEqual(i, d); 160 | } 161 | } 162 | 163 | void EasyZipIteration(benchmark::State &state) { 164 | Integer size = 10000; 165 | std::vector integers(size); 166 | std::vector doubles(size); 167 | easy_iterator::copy(easy_iterator::range(size), integers); 168 | easy_iterator::copy(easy_iterator::range(size), doubles); 169 | for (auto _ : state) { 170 | benchmark::DoNotOptimize(integers); 171 | benchmark::DoNotOptimize(doubles); 172 | easyZipIteration(integers, doubles); 173 | } 174 | } 175 | 176 | BENCHMARK(EasyZipIteration); 177 | 178 | #ifdef COMPARE_WITH_ITERTOOLS 179 | 180 | void __attribute__((noinline)) 181 | iterZipIteration(const std::vector &integers, const std::vector &doubles) { 182 | for (auto [i, d] : iter::zip(integers, doubles)) { 183 | AssertEqual(i, d); 184 | } 185 | } 186 | 187 | void IterZipIteration(benchmark::State &state) { 188 | Integer size = 10000; 189 | std::vector integers(size); 190 | std::vector doubles(size); 191 | easy_iterator::copy(easy_iterator::range(size), integers); 192 | easy_iterator::copy(easy_iterator::range(size), doubles); 193 | for (auto _ : state) { 194 | benchmark::DoNotOptimize(integers); 195 | benchmark::DoNotOptimize(doubles); 196 | iterZipIteration(integers, doubles); 197 | } 198 | } 199 | 200 | BENCHMARK(IterZipIteration); 201 | 202 | #endif 203 | 204 | void __attribute__((noinline)) 205 | stdZipIteration(const std::vector &integers, const std::vector &doubles) { 206 | auto i = integers.begin(), ie = integers.end(); 207 | auto d = doubles.begin(); 208 | while (i != ie) { 209 | AssertEqual(*i, *d); 210 | ++i; 211 | ++d; 212 | } 213 | } 214 | 215 | void StdZipIteration(benchmark::State &state) { 216 | Integer size = 10000; 217 | std::vector integers(size); 218 | std::vector doubles(size); 219 | easy_iterator::copy(easy_iterator::range(size), integers); 220 | easy_iterator::copy(easy_iterator::range(size), doubles); 221 | for (auto _ : state) { 222 | benchmark::DoNotOptimize(integers); 223 | benchmark::DoNotOptimize(doubles); 224 | stdZipIteration(integers, doubles); 225 | } 226 | } 227 | 228 | BENCHMARK(StdZipIteration); 229 | 230 | void __attribute__((noinline)) easyEnumerateIteration(const std::vector &values) { 231 | for (auto [i, v] : easy_iterator::enumerate(values)) { 232 | AssertEqual(i, v); 233 | } 234 | } 235 | 236 | void EasyEnumerateIteration(benchmark::State &state) { 237 | Integer max = 10000; 238 | std::vector values(max); 239 | easy_iterator::copy(easy_iterator::range(max), values); 240 | for (auto _ : state) { 241 | benchmark::DoNotOptimize(values); 242 | easyEnumerateIteration(values); 243 | } 244 | } 245 | 246 | BENCHMARK(EasyEnumerateIteration); 247 | 248 | #ifdef COMPARE_WITH_ITERTOOLS 249 | 250 | void __attribute__((noinline)) iterEnumerateIteration(const std::vector &values) { 251 | for (auto [i, v] : iter::enumerate(values)) { 252 | AssertEqual(i, v); 253 | } 254 | } 255 | 256 | void IterEnumerateIteration(benchmark::State &state) { 257 | Integer max = 10000; 258 | std::vector values(max); 259 | easy_iterator::copy(easy_iterator::range(max), values); 260 | for (auto _ : state) { 261 | benchmark::DoNotOptimize(values); 262 | iterEnumerateIteration(values); 263 | } 264 | } 265 | 266 | BENCHMARK(IterEnumerateIteration); 267 | 268 | #endif 269 | 270 | void __attribute__((noinline)) manualEnumerateIteration(const std::vector &values) { 271 | auto i = 0; 272 | for (auto &v : values) { 273 | AssertEqual(i, v); 274 | ++i; 275 | } 276 | } 277 | 278 | void ManualEnumerateIteration(benchmark::State &state) { 279 | Integer max = 10000; 280 | std::vector values(max); 281 | easy_iterator::copy(easy_iterator::range(max), values); 282 | for (auto _ : state) { 283 | benchmark::DoNotOptimize(values); 284 | manualEnumerateIteration(values); 285 | } 286 | } 287 | 288 | BENCHMARK(ManualEnumerateIteration); 289 | 290 | BENCHMARK_MAIN(); 291 | -------------------------------------------------------------------------------- /test/source/easy_iterator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace easy_iterator; 11 | 12 | TEST_CASE("IteratorPrototype") { 13 | struct CountDownIterator : public IteratorPrototype { 14 | using IteratorPrototype::IteratorPrototype; 15 | CountDownIterator &operator++() { 16 | --value; 17 | return *this; 18 | } 19 | }; 20 | 21 | SUBCASE("iteration") { 22 | CountDownIterator iterator(42); 23 | REQUIRE(*iterator == 42); 24 | auto expected = *iterator; 25 | while (*iterator > 10) { 26 | REQUIRE(*iterator == expected); 27 | ++iterator; 28 | --expected; 29 | } 30 | REQUIRE(expected == 10); 31 | } 32 | 33 | SUBCASE("wrapper") { 34 | int expected = 10; 35 | for (auto i : wrap(CountDownIterator(expected), CountDownIterator(3))) { 36 | REQUIRE(i == expected); 37 | --expected; 38 | } 39 | REQUIRE(expected == 3); 40 | } 41 | 42 | SUBCASE("compare") { 43 | REQUIRE(CountDownIterator(1) == CountDownIterator(1)); 44 | REQUIRE(CountDownIterator(1) != CountDownIterator(2)); 45 | } 46 | } 47 | 48 | TEST_CASE("Iterator") { 49 | SUBCASE("values") { 50 | auto it = makeIterator( 51 | 0, +[](int &v) { 52 | v++; 53 | return true; 54 | }); 55 | REQUIRE(*it == 0); 56 | ++it; 57 | REQUIRE(*it == 1); 58 | for (int i = *it; i < 10; ++i) { 59 | REQUIRE(*it == i); 60 | ++it; 61 | } 62 | decltype(it) end(100); 63 | while (it != end) { 64 | ++it; 65 | } 66 | REQUIRE(*it == 100); 67 | } 68 | 69 | SUBCASE("array incrementer") { 70 | std::vector arr(10); 71 | SUBCASE("manual iteration") { 72 | ReferenceIterator it(arr.data()); 73 | REQUIRE(&*it == &arr[0]); 74 | ++it; 75 | REQUIRE(&*it == &arr[1]); 76 | } 77 | SUBCASE("iterate to end") { 78 | ReferenceIterator it(arr.data()); 79 | auto end = makeIterator(arr.data() + arr.size()); 80 | REQUIRE(it != end); 81 | size_t idx = 0; 82 | while (it != end) { 83 | REQUIRE(&*it == &arr[idx]); 84 | ++it; 85 | ++idx; 86 | } 87 | REQUIRE(it == end); 88 | REQUIRE(idx == 10); 89 | } 90 | SUBCASE("valuesBetween") { 91 | size_t idx = 0; 92 | for (auto &v : valuesBetween(arr.data(), arr.data() + arr.size())) { 93 | static_assert(!std::is_const::type>::value); 94 | REQUIRE(&v == &arr[idx]); 95 | ++idx; 96 | } 97 | REQUIRE(idx == 10); 98 | } 99 | } 100 | } 101 | 102 | TEST_CASE("Range") { 103 | SUBCASE("begin-end-advance") { 104 | int expected = 3; 105 | for (auto i : range(3, 28, 3)) { 106 | REQUIRE(i == expected); 107 | expected = expected + 3; 108 | } 109 | REQUIRE(expected == 27); 110 | } 111 | 112 | SUBCASE("negative advance") { 113 | int expected = 28; 114 | for (auto i : range(28, 1, -2)) { 115 | REQUIRE(i == expected); 116 | expected = expected - 2; 117 | } 118 | REQUIRE(expected == 2); 119 | } 120 | 121 | SUBCASE("begin-end") { 122 | int expected = 2; 123 | for (auto i : range(2, 12)) { 124 | REQUIRE(i == expected); 125 | expected = expected + 1; 126 | } 127 | REQUIRE(expected == 12); 128 | } 129 | 130 | SUBCASE("end") { 131 | int expected = 0; 132 | for (auto i : range(10)) { 133 | REQUIRE(i == expected); 134 | expected = expected + 1; 135 | } 136 | REQUIRE(expected == 10); 137 | } 138 | 139 | SUBCASE("modifiers") { 140 | auto a = range(5, 20, 3); 141 | int expected = 5; 142 | SUBCASE("copy") { 143 | auto b = a; 144 | for (auto i : b) { 145 | REQUIRE(i == expected); 146 | expected = expected + 3; 147 | } 148 | } 149 | SUBCASE("const") { 150 | for (auto i : std::as_const(a)) { 151 | REQUIRE(i == expected); 152 | expected = expected + 3; 153 | } 154 | } 155 | REQUIRE(expected == 20); 156 | } 157 | } 158 | 159 | TEST_CASE("Zip") { 160 | SUBCASE("with ranges") { 161 | unsigned expected = 0; 162 | for (auto [i, j, k] : zip(range(10), range(0, 20, 2), range(0, 30, 3))) { 163 | REQUIRE(i == expected); 164 | REQUIRE(2 * i == j); 165 | REQUIRE(3 * i == k); 166 | expected++; 167 | } 168 | REQUIRE(expected == 10); 169 | } 170 | 171 | SUBCASE("with arrays") { 172 | std::vector integers(10); 173 | unsigned expected = 0; 174 | for (auto [i, v] : zip(range(10), integers)) { 175 | REQUIRE(i == expected); 176 | REQUIRE(&integers[i] == &v); 177 | v = i; 178 | REQUIRE(integers[i] == i); 179 | expected++; 180 | } 181 | REQUIRE(expected == 10); 182 | } 183 | } 184 | 185 | TEST_CASE("Enumerate") { 186 | std::vector vec(10); 187 | int count = 0; 188 | for (auto [i, v] : enumerate(vec)) { 189 | REQUIRE(i == count); 190 | REQUIRE(&v == &vec[i]); 191 | ++count; 192 | } 193 | REQUIRE(count == 10); 194 | } 195 | 196 | TEST_CASE("Reverse") { 197 | std::vector vec(rangeValue(0), rangeValue(10)); 198 | int count = 0; 199 | REQUIRE(vec.size() == 10); 200 | for (auto [i, v] : enumerate(reverse(vec))) { 201 | REQUIRE(v == 9 - i); 202 | REQUIRE(i == count); 203 | ++count; 204 | } 205 | } 206 | 207 | TEST_CASE("fill") { 208 | std::vector vec(10); 209 | fill(vec, 42); 210 | for (auto v : vec) { 211 | REQUIRE(v == 42); 212 | } 213 | } 214 | 215 | TEST_CASE("copy") { 216 | std::vector vec(10); 217 | SUBCASE("value") { 218 | copy(range(10), vec); 219 | for (auto [i, v] : enumerate(vec)) { 220 | REQUIRE(v == i); 221 | } 222 | } 223 | SUBCASE("transformed value") { 224 | copy(range(10), vec, [](auto v) { return 2 * v; }); 225 | for (auto [i, v] : enumerate(vec)) { 226 | REQUIRE(v == 2 * i); 227 | } 228 | } 229 | } 230 | 231 | TEST_CASE("array class") { 232 | class MyArray { 233 | private: 234 | size_t size; 235 | int *data; 236 | 237 | public: 238 | using iterator = ReferenceIterator; 239 | using const_iterator = ReferenceIterator; 240 | 241 | explicit MyArray(size_t _size) : size(_size), data(new int[size]) {} 242 | MyArray(const MyArray &) = delete; 243 | ~MyArray() { delete[] data; } 244 | 245 | int &operator[](size_t idx) { return data[idx]; } 246 | const int &operator[](size_t idx) const { return data[idx]; } 247 | 248 | iterator begin() { return iterator(data); } 249 | iterator end() { return iterator(data + size); } 250 | const_iterator begin() const { return const_iterator(data); } 251 | const_iterator end() const { return const_iterator(data + size); } 252 | }; 253 | 254 | MyArray array(10); 255 | 256 | SUBCASE("iterate") { 257 | size_t idx = 0; 258 | for (auto &v : array) { 259 | REQUIRE(&v == &array[idx]); 260 | ++idx; 261 | static_assert(!std::is_const::type>::value); 262 | } 263 | REQUIRE(idx == 10); 264 | } 265 | 266 | SUBCASE("const iterate") { 267 | size_t idx = 0; 268 | for (auto &v : std::as_const(array)) { 269 | REQUIRE(&v == &array[idx]); 270 | ++idx; 271 | static_assert(std::is_const::type>::value); 272 | } 273 | REQUIRE(idx == 10); 274 | } 275 | } 276 | 277 | TEST_CASE("MakeIterable") { 278 | struct Countdown { 279 | unsigned current; 280 | 281 | explicit Countdown(unsigned start) : current(start) {} 282 | 283 | bool advance() { 284 | if (current == 0) { 285 | return false; 286 | } 287 | current--; 288 | return true; 289 | } 290 | 291 | unsigned value() { return current; } 292 | }; 293 | 294 | SUBCASE("iterate") { 295 | auto it = MakeIterable(1).begin(); 296 | REQUIRE(it); 297 | REQUIRE(it != IterationEnd()); 298 | REQUIRE(*it == 1); 299 | ++it; 300 | REQUIRE(it); 301 | REQUIRE(it != IterationEnd()); 302 | REQUIRE(*it == 0); 303 | ++it; 304 | REQUIRE(!it); 305 | REQUIRE_THROWS_AS(*it, UndefinedIteratorException); 306 | REQUIRE_THROWS_WITH(*it, "attempt to dereference an undefined iterator"); 307 | REQUIRE(it == IterationEnd()); 308 | } 309 | 310 | SUBCASE("iterate") { 311 | unsigned count = 0; 312 | for (auto v : MakeIterable(10)) { 313 | REQUIRE(v == 10 - count); 314 | count++; 315 | } 316 | REQUIRE(count == 11); 317 | } 318 | 319 | SUBCASE("initialized") { 320 | struct Invalid : InitializedIterable { 321 | bool init() { return false; } 322 | int value() { 323 | REQUIRE(false); 324 | return 0; 325 | } 326 | bool advance() { 327 | REQUIRE(false); 328 | return true; 329 | } 330 | }; 331 | 332 | auto it = MakeIterable().begin(); 333 | REQUIRE(!it); 334 | REQUIRE_THROWS_AS(*it, UndefinedIteratorException); 335 | } 336 | } 337 | 338 | TEST_CASE("eraseIfFound") { 339 | std::map map; 340 | map["a"] = 1; 341 | map["b"] = 2; 342 | REQUIRE(eraseIfFound(map.find("a"), map)); 343 | REQUIRE(!eraseIfFound(map.find("c"), map)); 344 | REQUIRE(map.find("a") == map.end()); 345 | REQUIRE(map.size() == 1); 346 | } 347 | 348 | TEST_CASE("found") { 349 | std::map map; 350 | map["a"] = 1; 351 | map["b"] = 2; 352 | REQUIRE(found(map.find("a"), map)); 353 | REQUIRE(&found(map.find("a"), map)->second == &map["a"]); 354 | REQUIRE(!found(map.find("c"), map)); 355 | 356 | REQUIRE(find(map, "a")); 357 | REQUIRE(&find(map, "a")->second == &map["a"]); 358 | REQUIRE(!find(map, "c")); 359 | } 360 | -------------------------------------------------------------------------------- /include/easy_iterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace easy_iterator { 11 | 12 | /** 13 | * The end state for self-contained iterators. 14 | */ 15 | struct IterationEnd { 16 | const IterationEnd &operator*() const { return *this; } 17 | }; 18 | 19 | /** 20 | * Helper functions for comparing iterators. 21 | */ 22 | namespace compare { 23 | 24 | struct ByValue { 25 | template bool operator()(const T &a, const T &b) const { return a == b; } 26 | }; 27 | 28 | struct ByAddress { 29 | template bool operator()(const T &a, const T &b) const { return &a == &b; } 30 | }; 31 | 32 | struct ByLastTupleElementMatch { 33 | template 34 | bool operator()(const std::tuple &a, const std::tuple &b) const { 35 | static_assert(sizeof...(ArgsA) == sizeof...(ArgsB), "comparing invalid tuples"); 36 | return std::get(a) == std::get(b); 37 | } 38 | }; 39 | 40 | struct Never { 41 | template bool operator()(const A &, const B &) const { return false; } 42 | }; 43 | } // namespace compare 44 | 45 | /** 46 | * Helper functions for incrementing iterators. 47 | */ 48 | namespace increment { 49 | template struct ByValue { 50 | template void operator()(T &v) const { v = v + A; } 51 | }; 52 | 53 | struct ByTupleIncrement { 54 | template void dummy(Args &&...) {} 55 | template void updateValues(T &v, std::index_sequence) { 56 | dummy(++std::get(v)...); 57 | } 58 | template void operator()(std::tuple &v) { 59 | updateValues(v, std::make_index_sequence()); 60 | } 61 | }; 62 | 63 | template struct ByMemberCall { 64 | using R = decltype((std::declval().*Method)()); 65 | R operator()(T &v) const { return (v.*Method)(); } 66 | }; 67 | 68 | } // namespace increment 69 | 70 | /** 71 | * Helper functions for dereferencing iterators. 72 | */ 73 | namespace dereference { 74 | struct ByValue { 75 | template T operator()(T &v) const { return v; } 76 | }; 77 | 78 | struct ByConstValueReference { 79 | template const T &operator()(T &v) const { return v; } 80 | }; 81 | 82 | struct ByValueReference { 83 | template T &operator()(T &v) const { return v; } 84 | }; 85 | 86 | struct ByValueDereference { 87 | template auto &operator()(T &v) const { return *v; } 88 | }; 89 | 90 | struct ByTupleDereference { 91 | template auto constexpr getElement(T &v) const { 92 | if constexpr (std::is_reference(v))>::value) { 93 | return std::reference_wrapper(*std::get(v)); 94 | } else { 95 | return *std::get(v); 96 | } 97 | } 98 | template 99 | auto getReferenceTuple(T &v, std::index_sequence) const { 100 | return std::make_tuple(getElement(v)...); 101 | } 102 | template auto operator()(std::tuple &v) const { 103 | return getReferenceTuple(v, std::make_index_sequence()); 104 | } 105 | }; 106 | 107 | template struct ByMemberCall { 108 | using R = decltype((std::declval().*Method)()); 109 | R operator()(T &v) const { return (v.*Method)(); } 110 | }; 111 | 112 | } // namespace dereference 113 | 114 | /** 115 | * Exception when dereferencing an undefined iterator value. 116 | */ 117 | struct UndefinedIteratorException : public std::exception { 118 | const char *what() const noexcept override { 119 | return "attempt to dereference an undefined iterator"; 120 | } 121 | }; 122 | 123 | /** 124 | * Base class for simple iterators. Takes several template parameters. 125 | * Implementations must define `operator++()` to update the value of `value`. 126 | * @param `T` - The data type held by the iterator. 127 | * @param `D` - A function that dereferences the data. Determines the value type of the 128 | * iterator. 129 | * @param `C` - A function that compares two values of type `T`. Used to determine if two 130 | * iterators are equal. 131 | */ 132 | template 133 | class IteratorPrototype { 134 | public: 135 | // iterator traits 136 | using iterator_category = std::input_iterator_tag; 137 | using reference = decltype(std::declval()(std::declval())); 138 | using value_type = typename std::decay::type; 139 | using pointer = void; 140 | using difference_type = void; 141 | 142 | protected: 143 | D dereferencer; 144 | C compare; 145 | 146 | public: 147 | T value; 148 | using DereferencedType = decltype(dereferencer(value)); 149 | 150 | IteratorPrototype() = delete; 151 | template 152 | explicit IteratorPrototype(F &&first, AD &&_dereferencer = D(), AC &&_compare = C()) 153 | : dereferencer(std::forward(_dereferencer)), 154 | compare(std::forward(_compare)), 155 | value(std::forward(first)) {} 156 | 157 | DereferencedType operator*() { return dereferencer(value); } 158 | auto *operator->() const { return &**this; } 159 | 160 | template 161 | friend bool operator==(const IteratorPrototype &lhs, 162 | const IteratorPrototype &rhs); 163 | 164 | // required for C++17 or earlier 165 | template 166 | friend bool operator!=(const IteratorPrototype &lhs, 167 | const IteratorPrototype &rhs); 168 | }; 169 | 170 | template 171 | bool operator==(const IteratorPrototype &lhs, const IteratorPrototype &rhs) { 172 | return lhs.compare(lhs.value, rhs.value); 173 | } 174 | 175 | // required for C++17 or earlier 176 | template 177 | bool operator!=(const IteratorPrototype &lhs, const IteratorPrototype &rhs) { 178 | return !(lhs == rhs); 179 | } 180 | 181 | template IteratorPrototype(const T &) -> IteratorPrototype; 182 | 183 | template IteratorPrototype(const T &, const D &) -> IteratorPrototype; 184 | 185 | template IteratorPrototype(const T &, const D &, const C &) 186 | -> IteratorPrototype; 187 | 188 | namespace iterator_detail { 189 | struct WithState { 190 | constexpr static bool hasState = true; 191 | bool state = true; 192 | }; 193 | struct WithoutState { 194 | constexpr static bool hasState = false; 195 | }; 196 | 197 | template static constexpr bool needsState 198 | = !std::is_same()(std::declval()))>::value; 199 | } // namespace iterator_detail 200 | 201 | /** 202 | * IteratorPrototype where advance is defined by the function held by `F`. 203 | */ 204 | template , typename D = dereference::ByValueReference, 205 | typename C = compare::ByValue> 206 | class Iterator final 207 | : public IteratorPrototype, 208 | public std::conditional, iterator_detail::WithState, 209 | iterator_detail::WithoutState>::type { 210 | protected: 211 | using Base = IteratorPrototype; 212 | F callback; 213 | 214 | public: 215 | template 216 | explicit Iterator(TT &&begin, TF &&_callback = F(), TD &&_dereferencer = D(), 217 | TC &&_compare = C()) 218 | : IteratorPrototype(std::forward(begin), std::forward(_dereferencer), 219 | std::forward(_compare)), 220 | callback(_callback) {} 221 | Iterator &operator++() { 222 | if constexpr (Iterator::hasState) { 223 | if (Iterator::state) { 224 | Iterator::state = callback(Base::value); 225 | } 226 | } else { 227 | callback(Base::value); 228 | } 229 | return *this; 230 | } 231 | typename Base::DereferencedType operator*() { 232 | if constexpr (Iterator::hasState) { 233 | if (!Iterator::state) { 234 | throw UndefinedIteratorException(); 235 | } 236 | } 237 | return Base::dereferencer(Base::value); 238 | } 239 | 240 | template 241 | friend bool operator==(const Iterator &lhs, const IterationEnd &); 242 | // required for C++17 or earlier 243 | template 244 | friend bool operator!=(const Iterator &lhs, const IterationEnd &); 245 | 246 | explicit operator bool() const { 247 | if constexpr (Iterator::hasState) { 248 | return Iterator::state; 249 | } else { 250 | return true; 251 | } 252 | } 253 | }; 254 | 255 | template bool operator==(const Iterator &lhs, const IterationEnd &) { 256 | return !static_cast(lhs); 257 | } 258 | 259 | // required for C++17 or earlier 260 | template 261 | bool operator!=(const Iterator &lhs, const IterationEnd &rhs) { 262 | return !(lhs == rhs); 263 | } 264 | 265 | template Iterator(const T &) -> Iterator; 266 | 267 | template Iterator(const T &, const F &) -> Iterator; 268 | 269 | template Iterator(const T &, const F &, const D &) 270 | -> Iterator; 271 | 272 | template 273 | Iterator(const T &, const F &, const D &, const C &) -> Iterator; 274 | 275 | template , typename D = dereference::ByValueReference, 276 | typename C = compare::ByValue> 277 | Iterator makeIterator(T &&t, F f = F(), D &&d = D(), C &&c = C()) { 278 | return Iterator(t, f, d, c); 279 | } 280 | 281 | /** 282 | * Iterates by incrementing a pointer value. Returns the dereferenced pointer. 283 | */ 284 | template > using ReferenceIterator 285 | = Iterator; 286 | 287 | /** 288 | * Helper class for `wrap()`. 289 | */ 290 | template struct WrappedIterator { 291 | mutable IB beginIterator; 292 | mutable IE endIterator; 293 | IB &&begin() const { return std::move(beginIterator); } 294 | IE &&end() const { return std::move(endIterator); } 295 | WrappedIterator(IB &&begin, IE &&end) 296 | : beginIterator(std::move(begin)), endIterator(std::move(end)) {} 297 | }; 298 | 299 | /** 300 | * Wraps two iterators into a single-use container with begin/end methods to match the C++ 301 | * iterator convention. 302 | */ 303 | template auto wrap(IB &&a, IE &&b) { 304 | return WrappedIterator(std::forward(a), std::forward(b)); 305 | } 306 | 307 | /** 308 | * Helper class for `range()`. 309 | */ 310 | template struct RangeIterator : public IteratorPrototype { 311 | T increment; 312 | 313 | RangeIterator(const T &start, const T &_increment = 1) 314 | : IteratorPrototype(start), increment(_increment) {} 315 | 316 | RangeIterator &operator++() { 317 | RangeIterator::value += increment; 318 | return *this; 319 | } 320 | }; 321 | 322 | template RangeIterator rangeValue(T v, T i = 1) { return RangeIterator(v, i); } 323 | 324 | /** 325 | * Returns an iterator that increases it's value from `begin` to the first value <= `end` by 326 | * `increment` for each step. 327 | */ 328 | template auto range(T begin, T end, T increment) { 329 | auto actualEnd = end - ((end - begin) % increment); 330 | return wrap(rangeValue(begin, increment), rangeValue(actualEnd, increment)); 331 | } 332 | 333 | /** 334 | * Returns an iterator that increases its value from `begin` to `end` by `1` for each step. 335 | */ 336 | template auto range(T begin, T end) { return range(begin, end, 1); } 337 | 338 | /** 339 | * Returns an iterator that increases its value from `0` to `end` by `1` for each step. 340 | */ 341 | template auto range(T end) { return range(0, end); } 342 | 343 | /** 344 | * Wraps the `rbegin` and `rend` iterators. 345 | */ 346 | template auto reverse(T &v) { return wrap(v.rbegin(), v.rend()); } 347 | 348 | /** 349 | * Returns an iterable object where all argument iterators are traversed simultaneously. 350 | * Behaviour is undefined if the iterators do not have the same length. 351 | */ 352 | template auto zip(Args &&...args) { 353 | auto begin = Iterator(std::make_tuple(args.begin()...), increment::ByTupleIncrement(), 354 | dereference::ByTupleDereference(), compare::ByLastTupleElementMatch()); 355 | auto end = Iterator(std::make_tuple(args.end()...), increment::ByTupleIncrement(), 356 | dereference::ByTupleDereference(), compare::ByLastTupleElementMatch()); 357 | return wrap(std::move(begin), std::move(end)); 358 | } 359 | 360 | /** 361 | * Returns an object that is iterated as `[index, value]`. 362 | */ 363 | template auto enumerate(T &&t) { 364 | return zip(wrap(RangeIterator(0), IterationEnd()), t); 365 | } 366 | 367 | /** 368 | * When used as a base class for an iterator type, `MakeIterable` will call the `bool init()` 369 | * member before iteration. If `init()` returns false, the iterator is empty. 370 | */ 371 | struct InitializedIterable {}; 372 | 373 | /** 374 | * Take a class `T` with that defines the methods `T::advance()` and `O T::value()` for any type 375 | * `O` and wraps it into a single-use iterable class. The return value of `T::advance()` is used 376 | * to indicate the state of the iterator. 377 | */ 378 | template struct MakeIterable { 379 | mutable Iterator, 380 | dereference::ByMemberCall, compare::ByValue> 381 | start; 382 | 383 | auto &&begin() const { 384 | if constexpr (std::is_base_of::value) { 385 | start.state = start.value.init(); 386 | } 387 | return std::move(start); 388 | } 389 | auto end() const { return IterationEnd(); } 390 | 391 | explicit MakeIterable(T &&value) : start(std::move(value)) {} 392 | template explicit MakeIterable(Args &&...args) 393 | : start(T(std::forward(args)...)) {} 394 | }; 395 | 396 | /** 397 | * Iterates over the dereferenced values between `begin` and `end`. 398 | */ 399 | template > auto valuesBetween(T *begin, T *end) { 400 | return wrap(ReferenceIterator(begin), Iterator(end)); 401 | } 402 | 403 | /** 404 | * Copy-assigns the given value to every element in a container 405 | */ 406 | template void fill(A &arr, const T &value) { 407 | for (auto &v : arr) { 408 | v = value; 409 | } 410 | } 411 | 412 | /** 413 | * Copies values from one container to another. 414 | * @param `a` - the container with values to be copies. 415 | * @param `b` - the target container. 416 | * @param `f` (optional) - a function to transform values before copying. 417 | * Behaviour is undefined if `a` and `b` do not have the same size. 418 | */ 419 | template 420 | void copy(const A &a, B &b, T &&t = T()) { 421 | for (auto [v1, v2] : zip(a, b)) { 422 | v2 = t(v1); 423 | } 424 | } 425 | 426 | /** 427 | * Returns a pointer to the value if found, otherwise `nullptr`. 428 | * Usage: `if(auto v = found(map.find(key), map)) { do_something(v); }` 429 | */ 430 | template decltype(&*std::declval()) found(const I &it, C &container) { 431 | if (it != container.end()) { 432 | return &*it; 433 | } else { 434 | return nullptr; 435 | } 436 | } 437 | 438 | /** 439 | * Removes a value from a container with `find` method. 440 | * Usage: `eraseIfFound(map.find(key), map);` 441 | */ 442 | template bool eraseIfFound(const I &it, C &container) { 443 | if (it != container.end()) { 444 | container.erase(it); 445 | return true; 446 | } else { 447 | return false; 448 | } 449 | } 450 | 451 | /** 452 | * Returns a pointer to the value if found, otherwise `nullptr`. 453 | * Usage: `if(auto v = find(map, key)) { do_something(v); }` 454 | */ 455 | template auto find(C &c, V &&v) { 456 | auto it = c.find(v); 457 | return found(it, c); 458 | } 459 | 460 | } // namespace easy_iterator 461 | --------------------------------------------------------------------------------