diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 00000000..f4abeaad --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +8.* diff --git a/.clang-format b/.clang-format index 650739d3..d9d9622a 100644 --- a/.clang-format +++ b/.clang-format @@ -8,5 +8,6 @@ BreakBeforeBinaryOperators: NonAssignment DerivePointerAlignment: false NamespaceIndentation: All FixNamespaceComments: false +IncludeBlocks: Preserve ... diff --git a/.gitignore b/.gitignore index ac51a054..0d4fed27 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ bazel-* +MODULE.bazel.lock diff --git a/.install-libcxx-travis.sh b/.install-libcxx-travis.sh new file mode 100644 index 00000000..98b18fb0 --- /dev/null +++ b/.install-libcxx-travis.sh @@ -0,0 +1,12 @@ +sudo apt-get install -y dpkg +wget -c http://launchpadlibrarian.net/360656578/libc++-helpers_6.0-2_all.deb +sudo dpkg -i libc++-helpers_6.0-2_all.deb +wget -c http://launchpadlibrarian.net/360656583/libc++abi1_6.0-2_amd64.deb +sudo dpkg -i libc++abi1_6.0-2_amd64.deb +wget -c http://launchpadlibrarian.net/360656580/libc++1_6.0-2_amd64.deb +sudo dpkg -i libc++1_6.0-2_amd64.deb +wget -c http://launchpadlibrarian.net/360656576/libc++-dev_6.0-2_amd64.deb +sudo dpkg -i libc++-dev_6.0-2_amd64.deb +wget -c http://launchpadlibrarian.net/360656581/libc++abi-dev_6.0-2_amd64.deb +sudo dpkg -i libc++abi-dev_6.0-2_amd64.deb +sudo apt-get install -f diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..349a41d2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,146 @@ +language: cpp +dist: xenial +sudo: require + +matrix: + include: + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial + packages: + - clang-5.0 + - clang++-5.0 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-5.0 + - USE_LIBCXX=1 + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial + packages: + - clang-6.0 + - clang++-6.0 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-6.0 + - USE_LIBCXX=1 + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-7 + packages: + - clang-7 + - clang++-7 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-7 + - USE_LIBCXX=1 + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-8 + packages: + - clang-8 + - clang++-8 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-8 + - USE_LIBCXX=1 + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + # travis complains about an unlisted source here if I do it with a plain + # llvm-toolchain-xenial-9 + - sourceline: 'deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-9 main' + key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' + packages: + - clang-9 + - clang++-9 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-9 + - USE_LIBCXX=1 + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-7 + - valgrind + - scons + env: + - COMPILER=g++-7 + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-8 + - valgrind + - scons + env: + - COMPILER=g++-8 + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-9 + - valgrind + - scons + env: + - COMPILER=g++-9 + +script: + - ${COMPILER} --version + - CXX="${COMPILER}" scons + - ./test_all + - valgrind ./test_all + + +before_script: + - if [ -n "${USE_LIBCXX}" ]; then + bash .install-libcxx-travis.sh; + fi + - cd test + - ./download_catch.sh diff --git a/BUILD b/BUILD index 8b162b86..f2fab7df 100644 --- a/BUILD +++ b/BUILD @@ -1,42 +1,43 @@ cc_library( name = "cppitertools", hdrs = [ - "accumulate.hpp", - "chain.hpp", - "chunked.hpp", - "combinations.hpp", - "combinations_with_replacement.hpp", - "compress.hpp", - "count.hpp", - "cycle.hpp", - "dropwhile.hpp", - "enumerate.hpp", - "filter.hpp", - "filterfalse.hpp", - "groupby.hpp", - "imap.hpp", - "itertools.hpp", - "permutations.hpp", - "powerset.hpp", - "product.hpp", - "range.hpp", - "repeat.hpp", - "reversed.hpp", - "slice.hpp", - "sliding_window.hpp", - "sorted.hpp", - "starmap.hpp", - "takewhile.hpp", - "unique_everseen.hpp", - "unique_justseen.hpp", - "zip.hpp", - "zip_longest.hpp", + "cppitertools/accumulate.hpp", + "cppitertools/batched.hpp", + "cppitertools/chain.hpp", + "cppitertools/chunked.hpp", + "cppitertools/combinations.hpp", + "cppitertools/combinations_with_replacement.hpp", + "cppitertools/compress.hpp", + "cppitertools/count.hpp", + "cppitertools/cycle.hpp", + "cppitertools/dropwhile.hpp", + "cppitertools/enumerate.hpp", + "cppitertools/filter.hpp", + "cppitertools/filterfalse.hpp", + "cppitertools/groupby.hpp", + "cppitertools/imap.hpp", + "cppitertools/itertools.hpp", + "cppitertools/permutations.hpp", + "cppitertools/powerset.hpp", + "cppitertools/product.hpp", + "cppitertools/range.hpp", + "cppitertools/repeat.hpp", + "cppitertools/reversed.hpp", + "cppitertools/slice.hpp", + "cppitertools/sliding_window.hpp", + "cppitertools/sorted.hpp", + "cppitertools/starmap.hpp", + "cppitertools/takewhile.hpp", + "cppitertools/unique_everseen.hpp", + "cppitertools/unique_justseen.hpp", + "cppitertools/zip.hpp", + "cppitertools/zip_longest.hpp", ], srcs = [ - "internal/iter_tuples.hpp", - "internal/iterator_wrapper.hpp", - "internal/iteratoriterator.hpp", - "internal/iterbase.hpp", + "cppitertools/internal/iter_tuples.hpp", + "cppitertools/internal/iterator_wrapper.hpp", + "cppitertools/internal/iteratoriterator.hpp", + "cppitertools/internal/iterbase.hpp", ], visibility = ["//visibility:public"], ) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..c7eb98ca --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.12) +project(cppitertools VERSION 2.0) + +# installation directories +set(cppitertools_INSTALL_INCLUDE_DIR "include" CACHE STRING "The installation include directory") +set(cppitertools_INSTALL_CMAKE_DIR "share" CACHE STRING "The installation cmake directory") + +# define a header-only library +add_library(cppitertools INTERFACE) +add_library(cppitertools::cppitertools ALIAS cppitertools) + +target_include_directories(cppitertools INTERFACE + $ + $) + +# require C++17 +target_compile_features(cppitertools INTERFACE cxx_std_17) + +# Make package findable +configure_file(cmake/dummy-config.cmake.in cppitertools-config.cmake @ONLY) + +# Enable version checks in find_package +include(CMakePackageConfigHelpers) +write_basic_package_version_file(cppitertools-config-version.cmake COMPATIBILITY SameMajorVersion) + +# install and export target +install( + TARGETS cppitertools + EXPORT cppitertools-targets) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cppitertools + DESTINATION ${cppitertools_INSTALL_INCLUDE_DIR}) + +install( + EXPORT cppitertools-targets + FILE cppitertools-config.cmake + NAMESPACE cppitertools:: + DESTINATION ${cppitertools_INSTALL_CMAKE_DIR}/cppitertools) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/cppitertools-config-version.cmake + DESTINATION ${cppitertools_INSTALL_CMAKE_DIR}/cppitertools) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 00000000..b4a4568e --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,3 @@ +module( + name = "cppitertools" +) diff --git a/README.md b/README.md index 46ee3750..740dacbd 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ CPPItertools ============ Range-based for loop add-ons inspired by the Python builtins and itertools -library. Like itertools and the Python3 builtins, this library uses lazy +library. Like itertools and the Python3 builtins, this library uses lazy evaluation wherever possible. -*Note*: Everthing is inside the `iter` namespace. +*Note*: Everything is inside the `iter` namespace. Follow [@cppitertools](https://twitter.com/cppitertools) for updates. +#### Build and Test Status +Status | Compilers +---- | ---- +[![Travis Build Status](https://travis-ci.com/ryanhaining/cppitertools.svg?branch=master)](https://app.travis-ci.com/github/ryanhaining/cppitertools) | gcc-7 gcc-8 gcc-9 clang-5.0 clang-6.0 clang-7 clang-8 clang-9 +[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/ryanhaining/cppitertools?svg=true)](https://ci.appveyor.com/project/ryanhaining/cppitertools) | MSVC 2017 MSVC 2019 + #### Table of Contents [range](#range)
[enumerate](#enumerate)
@@ -34,8 +40,9 @@ Follow [@cppitertools](https://twitter.com/cppitertools) for updates. [slice](#slice)
[sliding\_window](#sliding_window)
[chunked](#chunked)
+[batched](#batched)
-##### Combinatoric fuctions +##### Combinatorial functions [product](#product)
[combinations](#combinations)
[combinations\_with\_replacement](#combinations_with_replacement)
@@ -46,11 +53,10 @@ Follow [@cppitertools](https://twitter.com/cppitertools) for updates. This library is **header-only** and relies only on the C++ standard library. The only exception is `zip_longest` which uses `boost::optional`. `#include ` will include all of the provided -tools except for `zip_longest` which must be included separately. You may +tools except for `zip_longest` which must be included separately. You may also include individual pieces with the relevant header (`#include ` for example). - ### Running tests You may use either `scons` or `bazel` to build the tests. `scons` seems to work better with viewing the test output, but the same `bazel` command @@ -77,7 +83,7 @@ $ bazel test //test:test_enumerate # runs a specific test #### Requirements of passed objects Most itertools will work with iterables using InputIterators and not copy -or move any underlying elements. The itertools that need ForwardIterators or +or move any underlying elements. The itertools that need ForwardIterators or have additional requirements are noted in this document. However, the cases should be fairly obvious: any time an element needs to appear multiple times (as in `combinations` or `cycle`) or be looked at more than once (specifically, @@ -87,7 +93,7 @@ underlying iterables, but if anything noteworthy is needed it is described in this document. #### Guarantees of implementations -By implementations I mean the objects returned by the API's functions. All of +By implementations, I mean the objects returned by the API's functions. All of the implementation classes are move-constructible, not copy-constructible, not assignable. All iterators that work over another iterable are tagged as InputIterators and behave as such. @@ -96,12 +102,12 @@ as InputIterators and behave as such. If you find anything not working as you expect, not compiling when you believe it should, a divergence from the python itertools behavior, or any sort of error, please let me know. The preferable means would be to open an issue on -github. If you want to talk about an issue that you don't feel would be -appropriate as a github issue (or you just don't want to open one), -You can email me directly with whatever code you have that describes the -problem, I've been pretty responsive in the past. If I believe you are -"misusing" the library I'll try to put the blame on myself for being unclear -in this document and take the steps to clarify it. So please, contact me with +GitHub. If you want to talk about an issue that you don't feel would be +appropriate as a GitHub issue (or you just don't want to open one), +you can email me directly with whatever code you have that describes the +problem; I've been pretty responsive in the past. If I believe you are +"misusing" the library, I'll try to put the blame on myself for being unclear +in this document and take the steps to clarify it. So please, contact me with any concerns, I'm open to feedback. #### How (not) to use this library @@ -116,41 +122,42 @@ know. #### Handling of rvalues vs lvalues The rules are pretty simple, and the library can be largely used without -knowledge of them. -Let's take an example +knowledge of them. Let's take an example + ```c++ std::vector vec{2,4,6,8}; for (auto&& p : enumerate(vec)) { /* ... */ } ``` + In this case, `enumerate` will return an object that has bound a reference to `vec`. No copies are produced here, neither of `vec` nor of the elements it holds. If an rvalue was passed to enumerate, binding a reference would be unsafe. Consider: + ```c++ for (auto&& p : enumerate(std::vector{2,4,6,8})) { /* ... */ } ``` + Instead, `enumerate` will return an object that has the temporary *moved* into -it. That is, the returned object will contain a `std::vector` rather than +it. That is, the returned object will contain a `std::vector` rather than just a reference to one. This may seem like a contrived example, but it matters when `enumerate` is passed the result of a function call like `enumerate(f())`, -or, more obviously, something like `enumerate(zip(a, b))`. The object returned +or, more obviously, something like `enumerate(zip(a, b))`. The object returned from `zip` must be moved into the `enumerate` object. As a more specific result, itertools can be mixed and nested. - - #### Pipe syntax -Wherever it makes sense I've implemented the "pipe" operator that has become -common in similar libraries. When the syntax is available it is done by pulling +Wherever it makes sense, I've implemented the "pipe" operator that has become +common in similar libraries. When the syntax is available, it is done by pulling out the iterable from the call and placing it before the tool. For example: ```c++ -filter(pred, seq); // regular call -seq | filter(pred); // pipe-style -enumerate(seq); // regular call -seq | enumerate; // pipe-style. +filter(pred, seq); // regular call +seq | filter(pred); // pipe-style +enumerate(seq); // regular call +seq | enumerate; // pipe-style. ``` The following tools support pipe. The remaining I left out because although @@ -160,6 +167,7 @@ would expect them to behave: - accumulate - chain.from\_iterable - chunked +- batched - combinations - combinations\_with\_replacement - cycle @@ -177,52 +185,56 @@ would expect them to behave: - sorted - starmap - takewhile -- unique\_everseen +- unique\_everseen (\*only without custom hash and equality callables) - unique\_justseen I don't personally care for the piping style, but it seemed to be desired by the users. - range ----- Uses an underlying iterator to achieve the same effect of the python range -function. `range` can be used in three different ways: +function. `range` can be used in three different ways: + +Only the stopping point is provided. Prints `0 1 2 3 4 5 6 7 8 9` -Only the stopping point is provided. Prints `0 1 2 3 4 5 6 7 8 9` ```c++ for (auto i : range(10)) { - cout << i << '\n'; + cout << i << '\n'; } ``` -The start and stop are both provided. Prints `10 11 12 13 14` +The start and stop are both provided. Prints `10 11 12 13 14` + ```c++ for (auto i : range(10, 15)) { - cout << i << '\n'; + cout << i << '\n'; } ``` -The start, stop, and step are all provided. Prints `20 22 24 26 28` +The start, stop, and step are all provided. Prints `20 22 24 26 28` + ```c++ for (auto i : range(20, 30, 2)) { - cout << i << '\n'; + cout << i << '\n'; } ``` -Negative values are allowed as well. Prints `2 1 0 -1 -2` +Negative values are allowed as well. Prints `2 1 0 -1 -2` + ```c++ for (auto i : range(2, -3, -1)) { - cout << i << '\n'; + cout << i << '\n'; } ``` A step size of 0 results in an empty range (Python's raises an exception). The following prints nothing + ```c++ for (auto i : range(0, 10, 0)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -230,9 +242,10 @@ In addition to normal integer range operations, doubles and other numeric types are supported through the template Prints: `5.0 5.5 6.0` ... `9.5` + ```c++ for(auto i : range(5.0, 10.0, 0.5)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -242,40 +255,46 @@ recomputed at each step to avoid accumulating floating point inaccuracies (`value = start + (step * steps_taken`). The result of the latter is a bit slower but more accurate. +`range` also supports the following operations: + - `.size()` to get the number of elements in the range (not enabled for + floating point ranges). + - Accessors for `.start()`, `.stop()`, and `.step()`. + - Indexing. Given a range `r`, `r[n]` is the `n`th element in the range. + enumerate --------- - -Continually "yields" containers similar to pairs. They are basic structs with a -.index and a .element, and also work with structured binding declarations. -Usage appears as: +Continually "yields" containers similar to pairs. They are structs with the +index in `.first`, and the element in `.second`, and also work with structured +binding declarations. Usage appears as: ```c++ vector vec{2, 4, 6, 8}; for (auto&& [i, e] : enumerate(vec)) { - cout << i << ": " << e << '\n'; + cout << i << ": " << e << '\n'; } ``` filter ------ -Called as `filter(predicate, iterable)`. The predicate can be any callable. +Called as `filter(predicate, iterable)`. The predicate can be any callable. `filter` will only yield values that are true under the predicate. -Prints values greater than 4: `5 6 7 8` +Prints values greater than 4: `5 6 7 8` + ```c++ vector vec{1, 5, 4, 0, 6, 7, 3, 0, 2, 8, 3, 2, 1}; for (auto&& i : filter([] (int i) { return i > 4; }, vec)) { - cout << i <<'\n'; + cout << i <<'\n'; } - ``` If no predicate is passed, the elements themselves are tested for truth Prints only non-zero values. + ```c++ for(auto&& i : filter(vec)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -283,49 +302,73 @@ filterfalse ----------- Similar to filter, but only prints values that are false under the predicate. -Prints values not greater than 4: `1 4 3 2 3 2 1 ` +Prints values not greater than 4: `1 4 3 2 3 2 1` + ```c++ vector vec{1, 5, 4, 0, 6, 7, 3, 0, 2, 8, 3, 2, 1}; for (auto&& i : filterfalse([] (int i) { return i > 4; }, vec)) { - cout << i <<'\n'; + cout << i <<'\n'; } - ``` If no predicate is passed, the elements themselves are tested for truth. Prints only zero values. + ```c++ for(auto&& i : filterfalse(vec)) { - cout << i << '\n'; + cout << i << '\n'; } - ``` + unique\_everseen ---------------- +---------------- *Additional Requirements*: Underlying values must be copy-constructible. This is a filter adaptor that only generates values that have never been seen -before. For this to work your object must be specialized for `std::hash`. +before. Prints `1 2 3 4 5 6 7 8 9` + ```c++ vector v {1,2,3,4,3,2,1,5,6,7,7,8,9,8,9,6}; for (auto&& i : unique_everseen(v)) { - cout << i << ' '; + cout << i << ' '; +} +``` + +`unique_everseen` uses an `undordered_set` so it needs hashable elements. For +types that don't work with `std::hash` or `std::equal_to`, `unique_everseen` +also provides an overload taking a hash callable and an equality callable. +This **does not** work with the pipe syntax. + +```c++ +vector v { /* ... */ }; +for (auto&& w : unique_everseen(v, WidgetHash{}, WidgetEq{})) { + cout << w.name() << ' '; } ``` unique\_justseen --------------- +---------------- Another filter adaptor that only omits consecutive duplicates. Prints `1 2 3 4 3 2 1` -Example Usage: + ```c++ vector v {1,1,1,2,2,3,3,3,4,3,2,1,1,1}; for (auto&& i : unique_justseen(v)) { - cout << i << ' '; + cout << i << ' '; +} +``` + +If elements cannot be directly compared with equality, you can pass in a key +callable. + +```c++ +vector v { /* ... */ }; +for (auto&& p : unique_justseen(v, [] (const Person& p) { return p.name; })) { + cout << p.name() << ' ' << p.age() << '\n'; } ``` @@ -334,11 +377,12 @@ takewhile Yields elements from an iterable until the first element that is false under the predicate is encountered. -Prints `1 2 3 4`. (5 is false under the predicate) +Prints `1 2 3 4`. (5 is false under the predicate) + ```c++ vector ivec{1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1}; for (auto&& i : takewhile([] (int i) {return i < 5;}, ivec)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -348,10 +392,11 @@ Yields all elements after and including the first element that is true under the predicate. Prints `5 6 7 1 2` + ```c++ vector ivec{1, 2, 3, 4, 5, 6, 7, 1, 2}; for (auto&& i : dropwhile([] (int i) {return i < 5;}, ivec)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -359,18 +404,18 @@ cycle ----- *Additional Requirements*: Input must have a ForwardIterator - -Repeatedly produces all values of an iterable. The loop will be infinite, so a +Repeatedly produces all values of an iterable. The loop will be infinite, so a `break` or other control flow structure is necessary to exit. Prints `1 2 3` repeatedly until `some_condition` is true + ```c++ vector vec{1, 2, 3}; for (auto&& i : cycle(vec)) { - cout << i << '\n'; - if (some_condition) { - break; - } + cout << i << '\n'; + if (some_condition) { + break; + } } ``` @@ -378,19 +423,21 @@ repeat ------ Repeatedly produces a single argument forever, or a given number of times. `repeat` will bind a reference when passed an lvalue and move when given -an rvalue. It will then yield a reference to the same item until completion. +an rvalue. It will then yield a reference to the same item until completion. The below prints `1` five times. + ```c++ for (auto&& e : repeat(1, 5)) { - cout << e << '\n'; + cout << e << '\n'; } ``` The below prints `2` forever + ```c++ for (auto&& e : repeat(2)) { - cout << e << '\n'; + cout << e << '\n'; } ``` @@ -403,16 +450,17 @@ step of 1.
`count(i, st)` will start counting from `i` with a step of `st`. *Technical limitations*: Unlike Python which can use its long integer -types when needed, count() would eventually exceed the +types when needed, `count()` would eventually exceed the maximum possible value for its type (or minimum with a negative step). `count` is actually implemented as a `range` with the stopping point being the `std::numeric_limits::max()` for the integral type (`long` by default) The below will print `0 1 2` ... etc + ```c++ for (auto&& i : count()) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -422,31 +470,32 @@ groupby a reference, the reference must remain valid after the iterator is incremented. Roughly equivalent to requiring the Input have a ForwardIterator. -Separate an iterable into groups sharing a common key. The following example +Separate an iterable into groups sharing a common key. The following example creates a new group whenever a string of a different length is encountered. + ```c++ vector vec = { - "hi", "ab", "ho", - "abc", "def", - "abcde", "efghi" + "hi", "ab", "ho", + "abc", "def", + "abcde", "efghi" }; for (auto&& gb : groupby(vec, [] (const string &s) {return s.length(); })) { - cout << "key: " << gb.first << '\n'; - cout << "content: "; - for (auto&& s : gb.second) { - cout << s << " "; - } - cout << '\n'; + cout << "key: " << gb.first << '\n'; + cout << "content: "; + for (auto&& s : gb.second) { + cout << s << " "; + } + cout << '\n'; } ``` + *Note*: Just like Python's `itertools.groupby`, this doesn't do any sorting. It just iterates through, making a new group each time there is a key change. Thus, if the group is unsorted, the same key may appear multiple times. starmap ------- - Takes a sequence of tuple-like objects (anything that works with `std::get`) and unpacks each object into individual arguments for each function call. The below example takes a `vector` of `pairs` of ints, and passes them @@ -456,21 +505,21 @@ the first and second arguments to the function. ```c++ vector> v = {{2, 3}, {5, 2}, {3, 4}}; // {base, exponent} for (auto&& i : starmap([](int b, int e){return pow(b, e);}, v)) { - // ... + // ... } ``` `starmap` can also work over a tuple-like object of tuple-like objects even when the contained objects are different as long as the functor works with -multiple types of calls. For example, a `Callable` struct with overloads +multiple types of calls. For example, a `Callable` struct with overloads for its `operator()` will work as long as all overloads have the same return type ```c++ struct Callable { - int operator()(int i) const; - int operator()(int i, char c) const; - int operator()(double d, int i, char c) const; + int operator()(int i) const; + int operator()(int i, char c) const; + int operator()(double d, int i, char c) const; }; ``` @@ -478,36 +527,40 @@ This will work with a tuple of mixed types ```c++ auto t = make_tuple( - make_tuple(5), // first form - make_pair(3, 'c'), // second - make_tuple(1.0, 1, '1')); // third + make_tuple(5), // first form + make_pair(3, 'c'), // second + make_tuple(1.0, 1, '1')); // third for (auto&& i : starmap(Callable{}, t)) { - // ... + // ... } ``` accumulate -------- +---------- *Additional Requirements*: Type return from functor (with reference removed) must be assignable. Differs from `std::accumulate` (which in my humble opinion should be named -`std::reduce` or `std::foldl`). It is similar to a functional reduce where one -can see all of the intermediate results. By default, it keeps a running sum. +`std::reduce` or `std::foldl`). It is similar to a functional reduce where one +can see all of the intermediate results. By default, it keeps a running sum. + Prints: `1 3 6 10 15` + ```c++ for (auto&& i : accumulate(range(1, 6))) { - cout << i << '\n'; + cout << i << '\n'; } ``` + A second, optional argument may provide an alternative binary function -to compute results. The following example multiplies the numbers, rather +to compute results. The following example multiplies the numbers, rather than adding them. + Prints: `1 2 6 24 120` ```c++ for (auto&& i : accumulate(range(1, 6), std::multiplies{})) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -518,10 +571,11 @@ zip --- Takes an arbitrary number of ranges of different types and efficiently iterates over them in parallel (so an iterator to each container is incremented -simultaneously). When you dereference an iterator to "zipped" range you get a +simultaneously). When you dereference an iterator to "zipped" range you get a tuple of the elements the iterators were holding. Example usage: + ```c++ array iseq{{1,2,3,4}}; vector fseq{1.2,1.4,12.3,4.5,9.9}; @@ -529,42 +583,43 @@ vector sseq{"i","like","apples","a lot","dude"}; array dseq{{1.2,1.2,1.2,1.2,1.2}}; for (auto&& [i, f, s, d] : zip(iseq, fseq, sseq, dseq)) { - cout << i << ' ' << f << ' ' << s << ' ' << d << '\n'; - f = 2.2f; // modifies the underlying 'fseq' sequence + cout << i << ' ' << f << ' ' << s << ' ' << d << '\n'; + f = 2.2f; // modifies the underlying 'fseq' sequence } ``` zip\_longest ------------ +------------ Terminates on the longest sequence instead of the shortest. Repeatedly yields a tuple of `boost::optional`s where `T` is the type -yielded by the sequences' respective iterators. Because of its boost +yielded by the sequences' respective iterators. Because of its boost dependency, `zip_longest` is not in `itertools.hpp` and must be included separately. -The following loop prints either "Just " or "Nothing" for each +The following loop prints either "Just \" or "Nothing" for each element in each tuple yielded. ```c++ vector v1 = {0, 1, 2, 3}; vector v2 = {10, 11}; for (auto&& [x, y] : zip_longest(v1, v2)) { - cout << '{'; - if (x) { - cout << "Just " << *x; - } else { - cout << "Nothing"; - } - cout << ", "; - if (y) { - cout << "Just " << *y; - } else { - cout << "Nothing"; - } - cout << "}\n"; + cout << '{'; + if (x) { + cout << "Just " << *x; + } else { + cout << "Nothing"; + } + cout << ", "; + if (y) { + cout << "Just " << *y; + } else { + cout << "Nothing"; + } + cout << "}\n"; } ``` The output is: + ``` {Just 0, Just 10} {Just 1, Just 11} @@ -574,27 +629,27 @@ The output is: imap ---- - -Takes a function and one or more iterables. The number of iterables must -match the number of arguments to the function. Applies the function to -each element (or elements) in the iterable(s). Terminates on the shortest +Takes a function and one or more iterables. The number of iterables must +match the number of arguments to the function. Applies the function to +each element (or elements) in the iterable(s). Terminates on the shortest sequence. Prints the squares of the numbers in vec: `1 4 9 16 25` ```c++ vector vec{1, 2, 3, 4, 5}; for (auto&& i : imap([] (int x) {return x * x;}, vec)) { - cout << i << '\n'; + cout << i << '\n'; } ``` With more than one sequence, the below adds corresponding elements from each vector together, printing `11 23 35 47 59 71` + ```c++ vector vec1{1, 3, 5, 7, 9, 11}; vector vec2{10, 20, 30, 40, 50, 60}; for (auto&& i : imap([] (int x, int y) { return x + y; }, vec1, vec2)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -602,10 +657,8 @@ for (auto&& i : imap([] (int x, int y) { return x + y; }, vec1, vec2)) { `std::map`, and because it is more related to `itertools.imap` than the python builtin `map`. - compress -------- - Yields only the values corresponding to true in the selectors iterable. Terminates on the shortest sequence. @@ -614,7 +667,7 @@ Prints `2 6` vector ivec{1, 2, 3, 4, 5, 6}; vector bvec{false, true, false, false, false, true}; for (auto&& i : compress(ivec, bvec) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -624,10 +677,10 @@ sorted Allows iteration over a sequence in sorted order. `sorted` does **not** produce a new sequence, copy elements, or modify the original -sequence. It only provides a way to iterate over existing elements. +sequence. It only provides a way to iterate over existing elements. `sorted` also takes an optional second [comparator](http://en.cppreference.com/w/cpp/concept/Compare) -argument. If not provided, defaults to `std::less`.
+argument. If not provided, defaults to `std::less`.
Iterables passed to sorted are required to have an iterator with an `operator*() const` member. @@ -636,7 +689,7 @@ The below outputs `0 1 2 3 4`. ```c++ unordered_set nums{4, 0, 2, 1, 3}; for (auto&& i : sorted(nums)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -654,31 +707,31 @@ vector vec1{1,2,3,4,5,6}; array arr1{{7,8,9,10}}; for (auto&& i : chain(empty,vec1,arr1)) { - cout << i << '\n'; + cout << i << '\n'; } ``` chain.from\_iterable -------------------- - +-------------------- Similar to chain, but rather than taking a variadic number of iterables, it takes an iterable of iterables and chains the contained iterables together. A simple example is shown below using a vector of vectors to represent a 2d ragged array, and prints it in row-major order. + ```c++ vector> matrix = { - {1, 2, 3}, - {4, 5}, - {6, 8, 9, 10, 11, 12} + {1, 2, 3}, + {4, 5}, + {6, 8, 9, 10, 11, 12} }; for (auto&& i : chain.from_iterable(matrix)) { - cout << i << '\n'; + cout << i << '\n'; } ``` reversed -------- +-------- *Additional Requirements*: Input must be compatible with `std::rbegin()` and `std::rend()` @@ -686,7 +739,7 @@ Iterates over elements of a sequence in reverse order. ```c++ for (auto&& i : reversed(a)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -697,18 +750,19 @@ Returns selected elements from a range, parameters are start, stop and step. the range returned is [start,stop) where you only take every step element This outputs `0 3 6 9 12` + ```c++ vector a{0,1,2,3,4,5,6,7,8,9,10,11,12,13}; for (auto&& i : slice(a,0,15,3)) { - cout << i << '\n'; + cout << i << '\n'; } ``` sliding\_window -------------- +--------------- *Additional Requirements*: Input must have a ForwardIterator -Takes a section from a range and increments the whole section. If the +Takes a section from a range and increments the whole section. If the window size is larger than the length of the input, the `sliding_window` will yield nothing (begin == end). @@ -726,90 +780,127 @@ take a section of size 4, output is: ``` Example Usage: + ```c++ vector v = {1,2,3,4,5,6,7,8,9}; for (auto&& sec : sliding_window(v,4)) { - for (auto&& i : sec) { - cout << i << ' '; - i.get() = 90; - } - cout << '\n'; + for (auto&& i : sec) { + cout << i << ' '; + i.get() = 90; + } + cout << '\n'; } ``` -chunked ------- -chunked will yield subsequent chunkes of an iterable in blocks of a specified +chunked +------- +chunked will yield subsequent chunks of an iterable in blocks of a specified size. The final chunk may be shorter than the rest if the chunk size given -does not evenly divide the length of the iterable +does not evenly divide the length of the iterable. Example usage: + ```c++ vector v {1,2,3,4,5,6,7,8,9}; for (auto&& sec : chunked(v,4)) { - for (auto&& i : sec) { - cout << i << ' '; - } - cout << '\n'; + for (auto&& i : sec) { + cout << i << ' '; + } + cout << '\n'; } ``` The above prints: + ``` 1 2 3 4 5 6 7 8 9 ``` +batched +------- +batched will yield a given number N of batches containing subsequent elements from an iterable, +assuming the iterable contains at least N elements. +The size of each batch is immaterial, but the implementation guarantees that no two batches will +differ in size by more than 1. + +Example usage: + +```c++ +vector v {1,2,3,4,5,6,7,8,9}; +for (auto&& sec : batched(v,4)) { + for (auto&& i : sec) { + cout << i << ' '; + } + cout << '\n'; +} +``` + +The above prints: + +``` +1 2 3 +4 5 +6 7 +8 9 +``` + product ------- +------- *Additional Requirements*: Input must have a ForwardIterator Generates the cartesian product of the given ranges put together. Example usage: + ```c++ vector v1{1,2,3}; vector v2{7,8}; vector v3{"the","cat"}; vector v4{"hi","what's","up","dude"}; for (auto&& [a, b, c, d] : product(v1,v2,v3,v4)) { - cout << a << ", " << b << ", " << c << ", " << d << '\n'; + cout << a << ", " << b << ", " << c << ", " << d << '\n'; } ``` -Product also accepts a "repeat" as a template argument. Currently this is the only way to do repeats. **If you are reading this and need `product(seq, 3)` instead of `product<3>(seq)` please open an issue**. +Product also accepts a "repeat" as a template argument. Currently this is the +only way to do repeats. **If you are reading this and need `product(seq, 3)` +instead of `product<3>(seq)` please open an issue**. Example usage: + ```c++ std::string s = "abc"; // equivalent of product(s, s, s); for (auto&& t : product<3>(s)) { - // ... + // ... } ``` combinations ------------ +------------ *Additional Requirements*: Input must have a ForwardIterator Generates n length unique sequences of the input range. Example usage: + ```c++ vector v = {1,2,3,4,5}; for (auto&& i : combinations(v,3)) { - for (auto&& j : i ) cout << j << " "; - cout << '\n'; + for (auto&& j : i ) cout << j << " "; + cout << '\n'; } ``` combinations\_with\_replacement ------------------------------ +------------------------------- *Additional Requirements*: Input must have a ForwardIterator -Like combinations, but with replacement of each element. The +Like combinations, but with replacement of each element. The below is printed by the loop that follows: + ``` {A, A} {A, B} @@ -818,43 +909,46 @@ below is printed by the loop that follows: {B, C} {C, C} ``` + ```c++ for (auto&& v : combinations_with_replacement(s, 2)) { - cout << '{' << v[0] << ", " << v[1] << "}\n"; + cout << '{' << v[0] << ", " << v[1] << "}\n"; } ``` permutations ------------ -*Additional Requirements*: Input must have a ForwardIterator. Iterator must +------------ +*Additional Requirements*: Input must have a ForwardIterator. Iterator must have an `operator*() const`. Generates all the permutations of a range using `std::next_permutation`. Example usage: + ```c++ vector v = {1,2,3,4,5}; for (auto&& vec : permutations(v)) { - for (auto&& i : vec) { - cout << i << ' '; - } - cout << '\n'; + for (auto&& i : vec) { + cout << i << ' '; + } + cout << '\n'; } ``` powerset -------- +-------- *Additional Requirements*: Input must have a ForwardIterator Generates every possible subset of a set, runs in O(2^n). Example usage: + ```c++ vector vec {1,2,3,4,5,6,7,8,9}; for (auto&& v : powerset(vec)) { - for (auto&& i : v) { - cout << i << " "; - } - cout << '\n'; + for (auto&& i : v) { + cout << i << " "; + } + cout << '\n'; } ``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..201281d4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +If you have discovered a security vulnerability in this project, please report it +privately. **Do not disclose it as a public issue.** This gives me time to work with you +to fix the issue before public exposure, reducing the chance that the exploit will be +used before a patch is released. + +You may submit the report in the following ways: + +- send an email to haining.cpp@gmail.com; and/or +- send me a [private vulnerability report](https://github.com/ryanhaining/cppitertools/security/advisories/new) + +Please provide the following information in your report: + +- A description of the vulnerability and its impact +- How to reproduce the issue + +This project is maintained by a single maintainer on a reasonable-effort basis. As such, +I ask that you give me 90 days to work on a fix before public exposure. diff --git a/cmake/dummy-config.cmake.in b/cmake/dummy-config.cmake.in new file mode 100644 index 00000000..d8c788f1 --- /dev/null +++ b/cmake/dummy-config.cmake.in @@ -0,0 +1,5 @@ +# Dummy config file +# When a dependency is added with add_subdirectory, but searched with find_package + +# Redirect to the directory added with add_subdirectory +add_subdirectory(@PROJECT_SOURCE_DIR@ @PROJECT_BINARY_DIR@) \ No newline at end of file diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 00000000..114b620f --- /dev/null +++ b/conanfile.py @@ -0,0 +1,44 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps + + +class CppIterTools(ConanFile): + name = 'cppitertools' + version = '3.0' + author = 'Ryan Haining ' + homepage = 'https://github.com/ryanhaining/cppitertools' + url = homepage + topics = ('itertools', 'cppitertools') + license = "BSD 2-Clause 'Simplified' License" + description = 'Range-based for loop add-ons inspired by the Python builtins and itertools library. ' \ + 'Like itertools and the Python3 builtins, this library uses lazy evaluation wherever possible.' + settings = 'build_type', 'compiler', 'os', 'arch' + exports = 'LICENSE.md' + + exports_sources = ( + 'cppitertools/*', + 'cppitertools/internal/*', + 'CMakeLists.txt', + 'cmake/dummy-config.cmake.in') + + def layout(self): + cmake_layout(self) + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.bindirs = [] + self.cpp_info.libdirs = [] diff --git a/accumulate.hpp b/cppitertools/accumulate.hpp similarity index 88% rename from accumulate.hpp rename to cppitertools/accumulate.hpp index 0d43a20e..2c5bc54e 100644 --- a/accumulate.hpp +++ b/cppitertools/accumulate.hpp @@ -17,7 +17,7 @@ namespace iter { using AccumulateFn = IterToolFnOptionalBindSecond>; } - constexpr impl::AccumulateFn accumulate{}; + inline constexpr impl::AccumulateFn accumulate{}; } template @@ -28,8 +28,9 @@ class iter::impl::Accumulator { friend AccumulateFn; - using AccumVal = std::remove_reference_t, iterator_deref>>; + using AccumVal = std::remove_cv_t< + std::remove_reference_t, iterator_deref>>>; Accumulator(Container&& container, AccumulateFunc accumulate_func) : container_(std::forward(container)), @@ -52,8 +53,8 @@ class iter::impl::Accumulator { using iterator_category = std::input_iterator_tag; using value_type = AccumVal; using difference_type = std::ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; + using pointer = const value_type*; + using reference = const value_type&; Iterator(IteratorWrapper&& sub_iter, IteratorWrapper&& sub_end, AccumulateFunc& accumulate_fun) @@ -65,11 +66,11 @@ class iter::impl::Accumulator { ? std::nullopt : std::make_optional(*sub_iter_)} {} - const AccumVal& operator*() const { + reference operator*() const { return *acc_val_; } - const AccumVal* operator->() const { + pointer operator->() const { return &*acc_val_; } diff --git a/cppitertools/batched.hpp b/cppitertools/batched.hpp new file mode 100644 index 00000000..f3d00ece --- /dev/null +++ b/cppitertools/batched.hpp @@ -0,0 +1,163 @@ +#ifndef ITER_BATCHED_HPP_ +#define ITER_BATCHED_HPP_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iteratoriterator.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Batcher; + + using BatchedFn = IterToolFnBindSizeTSecond; + } + inline constexpr impl::BatchedFn batched{}; +} + +template +class iter::impl::Batcher { + private: + Container container_; + std::size_t num_batches_; + + Batcher(Container&& container, std::size_t const num_batches) + : container_(std::forward(container)), num_batches_{num_batches} {} + + friend BatchedFn; + + template + using IndexVector = std::vector>; + template + using DerefVec = IterIterWrapper>; + + public: + Batcher(Batcher&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + std::shared_ptr> batch_ = + std::make_shared>(); + IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + std::size_t num_batches_; + std::size_t size_; + std::size_t count_; + + bool done() const { + return batch_->empty(); + } + + void refill_batch() { + batch_->get().clear(); + if (count_ < num_batches_) { + std::size_t const batch_size(size_ / num_batches_ + std::min(1, (size_ % num_batches_) / (count_ + 1))); + batch_->get().reserve(batch_size); + for (std::size_t i = 0; i < batch_size; ++i) { + batch_->get().push_back(sub_iter_); + ++sub_iter_; + } + ++count_; + } + } + + public: + using iterator_category = std::input_iterator_tag; + using value_type = DerefVec; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + template + struct distance_helper { + static constexpr difference_type distance(Iter1 it1, Iter2 it2) { + difference_type dist(0); + for (; it1 != it2; ++it1) + ++dist; + return dist; + } + }; + + template + struct distance_helper && std::is_arithmetic_v>> { + static constexpr difference_type distance(Iter1 it1, Iter2 it2) { + return std::distance(it1, it2); + } + }; + + template + difference_type distance(Iter1 it1, Iter2 it2) const { + return distance_helper::distance(it1, it2); + } + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end, std::size_t num_batches) + : sub_iter_{std::move(sub_iter)}, + sub_end_{std::move(sub_end)}, + num_batches_{num_batches}, + size_{static_cast(distance(sub_iter_, sub_end_))}, + count_{0} { + refill_batch(); + } + + Iterator& operator++() { + refill_batch(); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + template + bool operator==(const Iterator& other) const { + return done() == other.done() + && (done() || !(sub_iter_ != other.sub_iter_)); + } + + DerefVec& operator*() { + return *batch_; + } + + DerefVec* operator->() { + return batch_.get(); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_), num_batches_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), num_batches_}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_)), num_batches_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), num_batches_}; + } +}; + +#endif diff --git a/chain.hpp b/cppitertools/chain.hpp similarity index 82% rename from chain.hpp rename to cppitertools/chain.hpp index 8a0522e3..05557387 100644 --- a/chain.hpp +++ b/cppitertools/chain.hpp @@ -1,10 +1,6 @@ #ifndef ITER_CHAIN_HPP_ #define ITER_CHAIN_HPP_ -#include "internal/iter_tuples.hpp" -#include "internal/iterator_wrapper.hpp" -#include "internal/iterbase.hpp" - #include #include #include @@ -12,6 +8,10 @@ #include #include +#include "internal/iter_tuples.hpp" +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + namespace iter { namespace impl { template @@ -25,6 +25,16 @@ namespace iter { // rather than a chain function, use a callable object to support // from_iterable class ChainMaker; + + template + struct AsTupleOfConstImpl; + + template + struct AsTupleOfConstImpl> + : type_is...>> {}; + + template + using AsTupleOfConst = typename AsTupleOfConstImpl::type; } } @@ -33,6 +43,26 @@ class iter::impl::Chained { private: friend ChainMaker; + template + class IteratorDataPair { + IteratorDataPair() = delete; + + public: + using IterTupTypeA = iterator_tuple_type; + using IterTupTypeB = iterator_tuple_type; + + template + static bool get_and_check_not_equal( + const IterTupTypeA& lhs, const IterTupTypeB& rhs) { + return std::get(lhs) != std::get(rhs); + } + + using NeqFunc = bool (*)(const IterTupTypeA&, const IterTupTypeB&); + + constexpr static std::array neq_comparers{ + {get_and_check_not_equal...}}; + }; + template class IteratorData { IteratorData() = delete; @@ -66,16 +96,9 @@ class iter::impl::Chained { ++std::get(iters); } - template - static bool get_and_check_not_equal( - const IterTupType& lhs, const IterTupType& rhs) { - return std::get(lhs) != std::get(rhs); - } - using DerefFunc = DerefType (*)(IterTupType&); using ArrowFunc = ArrowType (*)(IterTupType&); using IncFunc = void (*)(IterTupType&); - using NeqFunc = bool (*)(const IterTupType&, const IterTupType&); constexpr static std::array derefers{ {get_and_deref...}}; @@ -86,9 +109,6 @@ class iter::impl::Chained { constexpr static std::array incrementers{ {get_and_increment...}}; - constexpr static std::array neq_comparers{ - {get_and_check_not_equal...}}; - using TraitsValue = iterator_traits_deref>; }; @@ -109,17 +129,21 @@ class iter::impl::Chained { void check_for_end_and_adjust() { while (index_ < sizeof...(Is) - && !(IterData::neq_comparers[index_](iters_, ends_))) { + && !(IteratorDataPair::neq_comparers[index_]( + iters_, ends_))) { ++index_; } } public: + template + friend class Iterator; + using iterator_category = std::input_iterator_tag; using value_type = typename IteratorData::TraitsValue; using difference_type = std::ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; + using pointer = typename IteratorData::ArrowType; + using reference = typename IteratorData::DerefType; Iterator(std::size_t i, typename IterData::IterTupType&& iters, typename IterData::IterTupType&& ends) @@ -131,7 +155,7 @@ class iter::impl::Chained { return IterData::derefers[index_](iters_); } - decltype(auto) operator-> () { + decltype(auto) operator->() { return IterData::arrowers[index_](iters_); } @@ -147,14 +171,16 @@ class iter::impl::Chained { return ret; } - // TODO make const and non-const iterators comparable - bool operator!=(const Iterator& other) const { + template + bool operator!=(const Iterator& other) const { return index_ != other.index_ || (index_ != sizeof...(Is) - && IterData::neq_comparers[index_](iters_, other.iters_)); + && IteratorDataPair::neq_comparers[index_]( + iters_, other.iters_)); } - bool operator==(const Iterator& other) const { + template + bool operator==(const Iterator& other) const { return !(*this != other); } }; @@ -169,12 +195,12 @@ class iter::impl::Chained { {get_end(std::get(tup_))...}}; } - Iterator> begin() const { + Iterator> begin() const { return {0, {get_begin(std::as_const(std::get(tup_)))...}, {get_end(std::as_const(std::get(tup_)))...}}; } - Iterator> end() const { + Iterator> end() const { return {sizeof...(Is), {get_end(std::as_const(std::get(tup_)))...}, {get_end(std::as_const(std::get(tup_)))...}}; } @@ -204,7 +230,14 @@ class iter::impl::ChainedFromIterable { std::optional sub_iter_p_; std::optional sub_end_p_; - void next_sub_iterable() { + void advance_while_empty_sub_iterable() { + while (top_level_iter_ != top_level_end_ && sub_iter_p_ == sub_end_p_) { + ++top_level_iter_; + update_sub_iterable(); + } + } + + void update_sub_iterable() { if (top_level_iter_ != top_level_end_) { sub_iterable_.reset(*top_level_iter_); sub_iter_p_ = @@ -216,6 +249,11 @@ class iter::impl::ChainedFromIterable { } } + void next_sub_iterable() { + update_sub_iterable(); + advance_while_empty_sub_iterable(); + } + public: using iterator_category = std::input_iterator_tag; using value_type = iterator_traits_deref>; @@ -305,9 +343,7 @@ class iter::impl::ChainMaker { }; namespace iter { - namespace { - constexpr auto chain = iter::impl::ChainMaker{}; - } + inline constexpr auto chain = iter::impl::ChainMaker{}; } #endif diff --git a/chunked.hpp b/cppitertools/chunked.hpp similarity index 89% rename from chunked.hpp rename to cppitertools/chunked.hpp index 87f0a0de..13fa652f 100644 --- a/chunked.hpp +++ b/cppitertools/chunked.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -19,7 +20,7 @@ namespace iter { using ChunkedFn = IterToolFnBindSizeTSecond; } - constexpr impl::ChunkedFn chunked{}; + inline constexpr impl::ChunkedFn chunked{}; } template @@ -45,20 +46,21 @@ class iter::impl::Chunker { private: template friend class Iterator; + std::shared_ptr> chunk_ = + std::make_shared>(); IteratorWrapper sub_iter_; IteratorWrapper sub_end_; - DerefVec chunk_; std::size_t chunk_size_ = 0; bool done() const { - return chunk_.empty(); + return chunk_->empty(); } void refill_chunk() { - chunk_.get().clear(); + chunk_->get().clear(); std::size_t i{0}; while (i < chunk_size_ && sub_iter_ != sub_end_) { - chunk_.get().push_back(sub_iter_); + chunk_->get().push_back(sub_iter_); ++sub_iter_; ++i; } @@ -76,7 +78,7 @@ class iter::impl::Chunker { : sub_iter_{std::move(sub_iter)}, sub_end_{std::move(sub_end)}, chunk_size_{s} { - chunk_.get().reserve(chunk_size_); + chunk_->get().reserve(chunk_size_); refill_chunk(); } @@ -103,11 +105,11 @@ class iter::impl::Chunker { } DerefVec& operator*() { - return chunk_; + return *chunk_; } DerefVec* operator->() { - return &chunk_; + return chunk_.get(); } }; diff --git a/combinations.hpp b/cppitertools/combinations.hpp similarity index 85% rename from combinations.hpp rename to cppitertools/combinations.hpp index 7572142c..cdee9cdd 100644 --- a/combinations.hpp +++ b/cppitertools/combinations.hpp @@ -15,7 +15,7 @@ namespace iter { using CombinationsFn = IterToolFnBindSizeTSecond; } - constexpr impl::CombinationsFn combinations{}; + inline constexpr impl::CombinationsFn combinations{}; } template @@ -73,6 +73,12 @@ class iter::impl::Combinator { } } + static Iterator zero_length_end(ContainerT& container) { + Iterator it{container, 0}; + it.steps_ = 0; + return it; + } + CombIteratorDeref& operator*() { return indices_; } @@ -82,6 +88,11 @@ class iter::impl::Combinator { } Iterator& operator++() { + if (indices_.get().empty()) { + // zero-length case. + ++steps_; + return *this; + } for (auto iter = indices_.get().rbegin(); iter != indices_.get().rend(); ++iter) { ++(*iter); @@ -94,11 +105,10 @@ class iter::impl::Combinator { if (!(dumb_next(*iter, dist) != get_end(*container_p_))) { if ((iter + 1) != indices_.get().rend()) { size_t inc = 1; - for (auto down = iter; ; --down) { + for (auto down = iter;; --down) { (*down) = dumb_next(*(iter + 1), 1 + inc); ++inc; - if (down == indices_.get().rbegin()) - break; + if (down == indices_.get().rbegin()) break; } } else { steps_ = COMPLETE; @@ -138,6 +148,9 @@ class iter::impl::Combinator { } Iterator end() { + if (length_ == 0) { + return Iterator::zero_length_end(container_); + } return {container_, 0}; } @@ -146,6 +159,9 @@ class iter::impl::Combinator { } Iterator> end() const { + if (length_ == 0) { + return Iterator>::zero_length_end(container_); + } return {std::as_const(container_), 0}; } }; diff --git a/combinations_with_replacement.hpp b/cppitertools/combinations_with_replacement.hpp similarity index 83% rename from combinations_with_replacement.hpp rename to cppitertools/combinations_with_replacement.hpp index b7e20d7d..39db37bd 100644 --- a/combinations_with_replacement.hpp +++ b/cppitertools/combinations_with_replacement.hpp @@ -15,7 +15,7 @@ namespace iter { using CombinationsWithReplacementFn = IterToolFnBindSizeTSecond; } - constexpr impl::CombinationsWithReplacementFn combinations_with_replacement{}; + inline constexpr impl::CombinationsWithReplacementFn combinations_with_replacement{}; } template @@ -60,6 +60,12 @@ class iter::impl::CombinatorWithReplacement { ? 0 : COMPLETE} {} + static Iterator zero_length_end(ContainerT& container) { + Iterator it{container, 0}; + it.steps_ = 0; + return it; + } + CombIteratorDeref& operator*() { return indices_; } @@ -69,15 +75,19 @@ class iter::impl::CombinatorWithReplacement { } Iterator& operator++() { + if (indices_.get().empty()) { + // zero-length case. + ++steps_; + return *this; + } for (auto iter = indices_.get().rbegin(); iter != indices_.get().rend(); ++iter) { ++(*iter); if (!(*iter != get_end(*container_p_))) { if ((iter + 1) != indices_.get().rend()) { - for (auto down = iter; ; --down) { + for (auto down = iter;; --down) { (*down) = dumb_next(*(iter + 1)); - if (down == indices_.get().rbegin()) - break; + if (down == indices_.get().rbegin()) break; } } else { steps_ = COMPLETE; @@ -117,6 +127,9 @@ class iter::impl::CombinatorWithReplacement { } Iterator end() { + if (length_ == 0) { + return Iterator::zero_length_end(container_); + } return {container_, 0}; } @@ -125,6 +138,9 @@ class iter::impl::CombinatorWithReplacement { } Iterator> end() const { + if (length_ == 0) { + return Iterator>::zero_length_end(container_); + } return {std::as_const(container_), 0}; } }; diff --git a/compress.hpp b/cppitertools/compress.hpp similarity index 100% rename from compress.hpp rename to cppitertools/compress.hpp diff --git a/count.hpp b/cppitertools/count.hpp similarity index 75% rename from count.hpp rename to cppitertools/count.hpp index 1694589c..327596bf 100644 --- a/count.hpp +++ b/cppitertools/count.hpp @@ -8,8 +8,7 @@ namespace iter { template constexpr auto count(T start, T step) noexcept { - // if step is < 0, set the stop to numeric min, otherwise numeric max - T stop = step < T(0) ? std::numeric_limits::min() + T stop = step < T(0) ? std::numeric_limits::lowest() : std::numeric_limits::max(); return range(start, stop, step); } diff --git a/cycle.hpp b/cppitertools/cycle.hpp similarity index 98% rename from cycle.hpp rename to cppitertools/cycle.hpp index 1d3ecaef..fb3ca9c7 100644 --- a/cycle.hpp +++ b/cppitertools/cycle.hpp @@ -15,7 +15,7 @@ namespace iter { using CycleFn = IterToolFn; } - constexpr impl::CycleFn cycle{}; + inline constexpr impl::CycleFn cycle{}; } template diff --git a/dropwhile.hpp b/cppitertools/dropwhile.hpp similarity index 83% rename from dropwhile.hpp rename to cppitertools/dropwhile.hpp index ca190471..86942826 100644 --- a/dropwhile.hpp +++ b/cppitertools/dropwhile.hpp @@ -16,7 +16,7 @@ namespace iter { using DropWhileFn = IterToolFnOptionalBindFirst; } - constexpr impl::DropWhileFn dropwhile{}; + inline constexpr impl::DropWhileFn dropwhile{}; } template @@ -29,7 +29,7 @@ class iter::impl::Dropper { Dropper(FilterFunc filter_func, Container&& container) : container_(std::forward(container)), - filter_func_(filter_func) {} + filter_func_(std::move(filter_func)) {} public: Dropper(Dropper&&) = default; @@ -39,12 +39,13 @@ class iter::impl::Dropper { template friend class Iterator; using Holder = DerefHolder>; - IteratorWrapper sub_iter_; + mutable IteratorWrapper sub_iter_; IteratorWrapper sub_end_; - Holder item_; + mutable Holder item_; FilterFunc* filter_func_; - void inc_sub_iter() { + // see comments from filter about mutability + void inc_sub_iter() const { ++sub_iter_; if (sub_iter_ != sub_end_) { item_.reset(*sub_iter_); @@ -52,12 +53,19 @@ class iter::impl::Dropper { } // skip all values for which the predicate is true - void skip_passes() { + void skip_passes() const { while (sub_iter_ != sub_end_ && std::invoke(*filter_func_, item_.get())) { inc_sub_iter(); } } + void init_if_first_use() const { + if (!item_ && sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + skip_passes(); + } + } + public: using iterator_category = std::input_iterator_tag; using value_type = iterator_traits_deref; @@ -69,22 +77,20 @@ class iter::impl::Dropper { IteratorWrapper&& sub_end, FilterFunc& filter_func) : sub_iter_{std::move(sub_iter)}, sub_end_{std::move(sub_end)}, - filter_func_(&filter_func) { - if (sub_iter_ != sub_end_) { - item_.reset(*sub_iter_); - } - skip_passes(); - } + filter_func_(&filter_func) {} typename Holder::reference operator*() { + init_if_first_use(); return item_.get(); } typename Holder::pointer operator->() { + init_if_first_use(); return item_.get_ptr(); } Iterator& operator++() { + init_if_first_use(); inc_sub_iter(); return *this; } @@ -97,6 +103,8 @@ class iter::impl::Dropper { template bool operator!=(const Iterator& other) const { + init_if_first_use(); + other.init_if_first_use(); return sub_iter_ != other.sub_iter_; } diff --git a/enumerate.hpp b/cppitertools/enumerate.hpp similarity index 95% rename from enumerate.hpp rename to cppitertools/enumerate.hpp index 6580b783..5d59feab 100644 --- a/enumerate.hpp +++ b/cppitertools/enumerate.hpp @@ -33,16 +33,16 @@ namespace iter { using EnumerateFn = IterToolFnOptionalBindSecond; } - constexpr impl::EnumerateFn enumerate{}; + inline constexpr impl::EnumerateFn enumerate{}; } namespace std { template - class tuple_size> + struct tuple_size> : public tuple_size> {}; template - class tuple_element> + struct tuple_element> : public tuple_element> {}; } diff --git a/filter.hpp b/cppitertools/filter.hpp similarity index 79% rename from filter.hpp rename to cppitertools/filter.hpp index 10a793c6..d743d8b1 100644 --- a/filter.hpp +++ b/cppitertools/filter.hpp @@ -24,11 +24,13 @@ namespace iter { using FilterFn = IterToolFnOptionalBindFirst; } - constexpr impl::FilterFn filter{}; + inline constexpr impl::FilterFn filter{}; } template class iter::impl::Filtered { + static_assert(!std::is_reference_v); + private: Container container_; mutable FilterFunc filter_func_; @@ -39,7 +41,7 @@ class iter::impl::Filtered { // Value constructor for use only in the filter function Filtered(FilterFunc filter_func, Container&& container) : container_(std::forward(container)), - filter_func_(filter_func) {} + filter_func_(std::move(filter_func)) {} public: Filtered(Filtered&&) = default; @@ -50,12 +52,14 @@ class iter::impl::Filtered { template friend class Iterator; using Holder = DerefHolder>; - IteratorWrapper sub_iter_; + mutable IteratorWrapper sub_iter_; IteratorWrapper sub_end_; - Holder item_; + mutable Holder item_; FilterFunc* filter_func_; - void inc_sub_iter() { + // All of these are marked const because the sub_iter_ is lazily + // initialized. The morality of this is questionable. + void inc_sub_iter() const { ++sub_iter_; if (sub_iter_ != sub_end_) { item_.reset(*sub_iter_); @@ -64,40 +68,45 @@ class iter::impl::Filtered { // increment until the iterator points to is true on the // predicate. Called by constructor and operator++ - void skip_failures() { + void skip_failures() const { while ( sub_iter_ != sub_end_ && !std::invoke(*filter_func_, item_.get())) { inc_sub_iter(); } } + void init_if_first_use() const { + if (!item_ && sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + skip_failures(); + } + } + public: using iterator_category = std::input_iterator_tag; using value_type = iterator_traits_deref; using difference_type = std::ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; + using pointer = typename Holder::pointer; + using reference = typename Holder::reference; Iterator(IteratorWrapper&& sub_iter, IteratorWrapper&& sub_end, FilterFunc& filter_func) : sub_iter_{std::move(sub_iter)}, sub_end_{std::move(sub_end)}, - filter_func_(&filter_func) { - if (sub_iter_ != sub_end_) { - item_.reset(*sub_iter_); - } - skip_failures(); - } + filter_func_(&filter_func) {} typename Holder::reference operator*() { + init_if_first_use(); return item_.get(); } typename Holder::pointer operator->() { + init_if_first_use(); return item_.get_ptr(); } Iterator& operator++() { + init_if_first_use(); inc_sub_iter(); skip_failures(); return *this; @@ -111,6 +120,8 @@ class iter::impl::Filtered { template bool operator!=(const Iterator& other) const { + init_if_first_use(); + other.init_if_first_use(); return sub_iter_ != other.sub_iter_; } diff --git a/filterfalse.hpp b/cppitertools/filterfalse.hpp similarity index 90% rename from filterfalse.hpp rename to cppitertools/filterfalse.hpp index f0f22628..269724dd 100644 --- a/filterfalse.hpp +++ b/cppitertools/filterfalse.hpp @@ -38,7 +38,7 @@ namespace iter { using FilterFalseFn = IterToolFnOptionalBindFirst; } - constexpr impl::FilterFalseFn filterfalse{}; + inline constexpr impl::FilterFalseFn filterfalse{}; } // Delegates to Filtered with PredicateFlipper @@ -48,7 +48,8 @@ class iter::impl::FilterFalsed friend FilterFalseFn; FilterFalsed(FilterFunc in_filter_func, Container&& in_container) : Filtered, Container>( - {in_filter_func}, std::forward(in_container)) {} + {std::move(in_filter_func)}, + std::forward(in_container)) {} }; #endif diff --git a/groupby.hpp b/cppitertools/groupby.hpp similarity index 93% rename from groupby.hpp rename to cppitertools/groupby.hpp index 4f2fffb5..1c73f9f6 100644 --- a/groupby.hpp +++ b/cppitertools/groupby.hpp @@ -18,16 +18,9 @@ namespace iter { template class GroupProducer; - struct Identity { - template - const T& operator()(const T& t) const { - return t; - } - }; - using GroupByFn = IterToolFnOptionalBindSecond; } - constexpr impl::GroupByFn groupby{}; + inline constexpr impl::GroupByFn groupby{}; } template @@ -42,7 +35,8 @@ class iter::impl::GroupProducer { using key_func_ret = std::invoke_result_t>; GroupProducer(Container&& container, KeyFunc key_func) - : container_(std::forward(container)), key_func_(key_func) {} + : container_(std::forward(container)), + key_func_(std::move(key_func)) {} public: GroupProducer(GroupProducer&&) = default; @@ -184,6 +178,8 @@ class iter::impl::GroupProducer { friend class Iterator; friend class GroupIterator; Iterator& owner_; + // The key function may return a reference, so we need to call forward, not + // move, when going for efficiency. key_func_ret key_; // completed is set if a Group is iterated through @@ -198,7 +194,7 @@ class iter::impl::GroupProducer { bool completed = false; Group(Iterator& owner, key_func_ret key) - : owner_(owner), key_(key) {} + : owner_(owner), key_(std::forward>(key)) {} public: ~Group() { @@ -210,7 +206,9 @@ class iter::impl::GroupProducer { // move-constructible, non-copy-constructible, non-assignable Group(Group&& other) noexcept - : owner_(other.owner_), key_{other.key_}, completed{other.completed} { + : owner_(other.owner_), + key_{std::forward>(other.key_)}, + completed{other.completed} { other.completed = true; } diff --git a/cppitertools/imap.hpp b/cppitertools/imap.hpp new file mode 100644 index 00000000..21ca2e81 --- /dev/null +++ b/cppitertools/imap.hpp @@ -0,0 +1,28 @@ +#ifndef ITER_IMAP_H_ +#define ITER_IMAP_H_ + +#include + +#include "starmap.hpp" +#include "zip.hpp" + +namespace iter { + namespace impl { + struct IMapFn : PipeableAndBindFirst { + template + auto operator()(MapFunc map_func, Containers&&... containers) const + // explicitly specifying type here to allow more expressions that only + // care about the type, and don't need a valid implementation. + // See #66 + -> StarMapper(containers)...))> { + return starmap( + std::move(map_func), zip(std::forward(containers)...)); + } + using PipeableAndBindFirst::operator(); + }; + } + inline constexpr impl::IMapFn imap{}; +} + +#endif diff --git a/internal/iter_tuples.hpp b/cppitertools/internal/iter_tuples.hpp similarity index 100% rename from internal/iter_tuples.hpp rename to cppitertools/internal/iter_tuples.hpp diff --git a/internal/iterator_wrapper.hpp b/cppitertools/internal/iterator_wrapper.hpp similarity index 96% rename from internal/iterator_wrapper.hpp rename to cppitertools/internal/iterator_wrapper.hpp index 2d91fc02..f068da97 100644 --- a/internal/iterator_wrapper.hpp +++ b/cppitertools/internal/iterator_wrapper.hpp @@ -94,6 +94,10 @@ class iter::impl::IteratorWrapperImpl { } not_equal; return std::visit(not_equal, sub_iter_or_end_, other.sub_iter_or_end_); } + + bool operator==(const IteratorWrapperImpl& other) const { + return !(*this != other); + } }; #endif diff --git a/internal/iteratoriterator.hpp b/cppitertools/internal/iteratoriterator.hpp similarity index 94% rename from internal/iteratoriterator.hpp rename to cppitertools/internal/iteratoriterator.hpp index 3993a4d0..0ab23bad 100644 --- a/internal/iteratoriterator.hpp +++ b/cppitertools/internal/iteratoriterator.hpp @@ -24,7 +24,8 @@ namespace iter { template class IteratorIterator { - template friend class IteratorIterator; + template + friend class IteratorIterator; using Diff = std::ptrdiff_t; static_assert( std::is_same< @@ -37,10 +38,14 @@ namespace iter { public: using iterator_category = std::random_access_iterator_tag; - using value_type = std::remove_reference_t())>; + using value_type = std::remove_cv_t< + std::remove_reference_t())>>; using difference_type = std::ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; + using pointer = + std::remove_reference_t())>*; + using reference = std::add_lvalue_reference_t< + std::remove_reference_t())>>; + IteratorIterator() = default; IteratorIterator(const TopIter& it) : sub_iter{it} {} @@ -84,7 +89,7 @@ namespace iter { return **this->sub_iter; } - auto operator-> () const -> decltype(*sub_iter) { + auto operator->() const -> decltype(*sub_iter) { return *this->sub_iter; } diff --git a/internal/iterbase.hpp b/cppitertools/internal/iterbase.hpp similarity index 66% rename from internal/iterbase.hpp rename to cppitertools/internal/iterbase.hpp index 6232c300..e9627079 100644 --- a/internal/iterbase.hpp +++ b/cppitertools/internal/iterbase.hpp @@ -7,6 +7,7 @@ // also applies to the name of the file. No user code should include // this file directly. +#include #include #include #include @@ -15,28 +16,61 @@ #include #include +// see gcc bug 87651 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87651 +#ifdef __GNUC__ +#define NO_GCC_FRIEND_ERROR __GNUC__ < 8 +#else +#define NO_GCC_FRIEND_ERROR 1 +#endif + namespace iter { namespace impl { namespace get_iters { - // This has to be set up in a really weird way. - // This looks at first as if it could be - // decltype(auto) get_begin(T& t) { - // using std::begin; - // return begin(t); - // } - // However, without return types in the declaration, SFINAE gets - // messed up everywhere. - using std::begin; - // TODO add constexpr for c++17 + // begin() for C arrays + template + T* get_begin_impl(T (&array)[N], int) { + return array; + } + + // Prefer member begin(). + template ().begin())> + I get_begin_impl(T& r, int) { + return r.begin(); + } + + // Use ADL otherwises. + template ()))> + I get_begin_impl(T& r, long) { + return begin(r); + } + template - auto get_begin(T& t) -> decltype(begin(t)) { - return begin(t); + auto get_begin(T& t) -> decltype(get_begin_impl(std::declval(), 42)) { + return get_begin_impl(t, 42); + } + + // end() for C arrays + template + T* get_end_impl(T (&array)[N], int) { + return array + N; } - using std::end; - // TODO add constexpr for c++17 + + // Prefer member end(). + template ().end())> + I get_end_impl(T& r, int) { + return r.end(); + } + + // Use ADL otherwise. + template ()))> + I get_end_impl(T& r, long) { + return end(r); + } + template - auto get_end(T& t) -> decltype(end(t)) { - return end(t); + auto get_end(T& t) -> decltype(get_end_impl(std::declval(), 42)) { + return get_end_impl(t, 42); } } using get_iters::get_begin; @@ -51,13 +85,18 @@ namespace iter { using AsConst = decltype(std::as_const(std::declval())); // iterator_type is the type of C's iterator - template //TODO: See bug https://developercommunity.visualstudio.com/content/problem/252157/sfinae-error-depends-on-name-of-template-parameter.html for why we use T instead of Container. Should be changed back to Container when that bug is fixed in MSVC. + // TODO: See bug + // https://developercommunity.visualstudio.com/content/problem/252157/sfinae-error-depends-on-name-of-template-parameter.html + // for why we use T instead of Container. Should be + // changed back to Container when that bug is fixed in + // MSVC. + template using iterator_type = decltype(get_begin(std::declval())); // iterator_type is the type of C's iterator template - using const_iterator_type = decltype( - get_begin(std::declval&>())); + using const_iterator_type = decltype(get_begin( + std::declval&>())); // iterator_deref is the type obtained by dereferencing an iterator // to an object of type C @@ -78,7 +117,7 @@ namespace iter { template using iterator_traits_deref = - std::remove_reference_t>; + std::remove_cv_t>>; template struct IsIterable : std::false_type {}; @@ -90,6 +129,13 @@ namespace iter { template constexpr bool is_iterable = IsIterable::value; + struct Identity { + template + const T& operator()(const T& t) const { + return t; + } + }; + namespace detail { template struct ArrowHelper { @@ -152,9 +198,9 @@ namespace iter { template struct is_random_access_iter::iterator_category, - std::random_access_iterator_tag>::value>> : std::true_type {}; + std::enable_if_t< + std::is_same::iterator_category, + std::random_access_iterator_tag>::value>> : std::true_type {}; template using has_random_access_iter = is_random_access_iter>; @@ -168,28 +214,20 @@ namespace iter { } } - template - void dumb_advance_impl( - Iter& iter, const EndIter& end, Distance distance, std::false_type) { - for (Distance i(0); i < distance && iter != end; ++i) { - ++iter; - } - } - - template - void dumb_advance_impl( - Iter& iter, const EndIter& end, Distance distance, std::true_type) { - if (static_cast(end - iter) < distance) { - iter = end; - } else { - iter += distance; - } - } - // iter will not be incremented past end template void dumb_advance(Iter& iter, const EndIter& end, Distance distance) { - dumb_advance_impl(iter, end, distance, is_random_access_iter{}); + if constexpr (is_random_access_iter{}) { + if (static_cast(end - iter) < distance) { + iter = end; + } else { + iter += distance; + } + } else { + for (Distance i(0); i < distance && iter != end; ++i) { + ++iter; + } + } } template @@ -224,9 +262,8 @@ namespace iter { std::is_same::value && are_same::value> {}; // DerefHolder holds the value gotten from an iterator dereference - // if the iterate dereferences to an lvalue references, a pointer to the - // element is stored - // if it does not, a value is stored instead + // if the iterator dereferences to an lvalue references, a pointer to the + // element is stored. if it does not, a value is stored instead // get() returns a reference to the held item // get_ptr() returns a pointer to the held item // reset() replaces the currently held item @@ -236,30 +273,34 @@ namespace iter { static_assert(!std::is_lvalue_reference::value, "Non-lvalue-ref specialization used for lvalue ref type"); // it could still be an rvalue reference - using TPlain = std::remove_reference_t; + using TPlain = std::remove_cv_t>; - std::optional item_p; + std::optional item_p_; public: using reference = TPlain&; using pointer = TPlain*; + static constexpr bool stores_value = true; + DerefHolder() = default; reference get() { - return *this->item_p; + assert(item_p_.has_value()); + return *item_p_; } pointer get_ptr() { - return this->item_p.get(); + assert(item_p_.has_value()); + return &item_p_.value(); } void reset(T&& item) { - item_p = std::move(item); + item_p_.emplace(std::move(item)); } explicit operator bool() const { - return static_cast(this->item_p); + return static_cast(item_p_); } }; @@ -271,25 +312,29 @@ namespace iter { using pointer = T*; private: - pointer item_p{}; + pointer item_p_{}; public: + static constexpr bool stores_value = false; + DerefHolder() = default; reference get() { - return *this->item_p; + assert(item_p_); + return *item_p_; } pointer get_ptr() { - return this->item_p; + assert(item_p_); + return item_p_; } void reset(reference item) { - this->item_p = &item; + item_p_ = &item; } explicit operator bool() const { - return this->item_p != nullptr; + return item_p_ != nullptr; } }; @@ -299,6 +344,14 @@ namespace iter { template struct Pipeable { template +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 14 + [[gnu::no_dangling]] +#endif + friend decltype(auto) operator|(T&& x, Pipeable&& p) { + return static_cast(p)(std::forward(x)); + } + + template friend decltype(auto) operator|(T&& x, const Pipeable& p) { return static_cast(p)(std::forward(x)); } @@ -321,11 +374,17 @@ namespace iter { protected: template struct FnPartial : Pipeable> { + static_assert(!std::is_reference_v); mutable T stored_arg; - constexpr FnPartial(T in_t) : stored_arg(in_t) {} + constexpr FnPartial(T in_t) : stored_arg(std::move(in_t)) {} template - auto operator()(Container&& container) const { + auto operator()(Container&& container) && { + return F{}(std::move(stored_arg), std::forward(container)); + } + + template + auto operator()(Container&& container) const& { return F{}(stored_arg, std::forward(container)); } }; @@ -337,6 +396,42 @@ namespace iter { } }; + // Pipeable callable which allows binding of the second argument + // f(a, b) is the same as a | f(b) + // f(a) with an iterable is the same as f(a, DefaultT{}) + template + struct PipeableAndBindOptionalSecond : Pipeable { + protected: + template + struct FnPartial : Pipeable> { + mutable T stored_arg; + constexpr FnPartial(T in_t) : stored_arg(std::move(in_t)) {} + + template + auto operator()(Container&& container) && { + return F{}(std::forward(container), std::move(stored_arg)); + } + + template + auto operator()(Container&& container) const& { + return F{}(std::forward(container), stored_arg); + } + }; + + public: + template >> + FnPartial> operator()(T&& t) const { + return {std::forward(t)}; + } + + template >> + auto operator()(Container&& container) const { + return static_cast(*this)( + std::forward(container), DefaultT{}); + } + }; + // This is a complicated class to generate a callable that can work: // (1) with just a single (iterable) passed, and DefaultT substituted // (2) with an iterable and a callable @@ -348,22 +443,14 @@ namespace iter { using Base = PipeableAndBindFirst>; - protected: - template - auto operator()(Container&& container, std::false_type) const { - return static_cast(*this)( - std::forward(container)); - } - - template - auto operator()(Container&& container, std::true_type) const { - return (*this)(DefaultT{}, std::forward(container)); - } - public: template auto operator()(T&& t) const { - return (*this)(std::forward(t), IsIterable{}); + if constexpr (IsIterable{}) { + return (*this)(DefaultT{}, std::forward(t)); + } else { + return static_cast(*this)(std::forward(t)); + } } template struct FnPartial : Pipeable> { mutable T stored_arg; - constexpr FnPartial(T in_t) : stored_arg(in_t) {} + constexpr FnPartial(T in_t) : stored_arg(std::move(in_t)) {} template - auto operator()(Container&& container) const { + auto operator()(Container&& container) && { + return IterToolFnOptionalBindSecond{}( + std::forward(container), std::move(stored_arg)); + } + + template + auto operator()(Container&& container) const& { return IterToolFnOptionalBindSecond{}( std::forward(container), stored_arg); } diff --git a/itertools.hpp b/cppitertools/itertools.hpp similarity index 97% rename from itertools.hpp rename to cppitertools/itertools.hpp index 7dd9b9a8..bf824196 100644 --- a/itertools.hpp +++ b/cppitertools/itertools.hpp @@ -2,6 +2,7 @@ #define ITERTOOLS_ALL_HPP_ #include "accumulate.hpp" +#include "batched.hpp" #include "chain.hpp" #include "chunked.hpp" #include "combinations.hpp" diff --git a/permutations.hpp b/cppitertools/permutations.hpp similarity index 98% rename from permutations.hpp rename to cppitertools/permutations.hpp index e10872d4..f567bc68 100644 --- a/permutations.hpp +++ b/cppitertools/permutations.hpp @@ -17,7 +17,7 @@ namespace iter { class Permuter; using PermutationsFn = IterToolFn; } - constexpr impl::PermutationsFn permutations{}; + inline constexpr impl::PermutationsFn permutations{}; } template diff --git a/powerset.hpp b/cppitertools/powerset.hpp similarity index 98% rename from powerset.hpp rename to cppitertools/powerset.hpp index 3439069d..d6131ca8 100644 --- a/powerset.hpp +++ b/cppitertools/powerset.hpp @@ -18,7 +18,7 @@ namespace iter { using PowersetFn = IterToolFn; } - constexpr impl::PowersetFn powerset{}; + inline constexpr impl::PowersetFn powerset{}; } template diff --git a/product.hpp b/cppitertools/product.hpp similarity index 96% rename from product.hpp rename to cppitertools/product.hpp index 38836fba..7de5727a 100644 --- a/product.hpp +++ b/cppitertools/product.hpp @@ -80,9 +80,14 @@ class iter::impl::Productor { template class IteratorTuple, template class TupleDeref> class IteratorTempl { +#if NO_GCC_FRIEND_ERROR private: template class, template class> friend class IteratorTempl; +#else + public: +#endif + using IterTupType = IteratorTuple; IterTupType iters_; IterTupType begin_iters_; @@ -93,7 +98,7 @@ class iter::impl::Productor { using value_type = TupleDeref; using difference_type = std::ptrdiff_t; using pointer = value_type*; - using reference = value_type&; + using reference = value_type; IteratorTempl(IteratorTuple&& iters, IteratorTuple&& end_iters) @@ -126,9 +131,11 @@ class iter::impl::Productor { template class IT, template class TD> bool operator!=(const IteratorTempl& other) const { - if constexpr (sizeof...(Is) == 0) return false; - else + if constexpr (sizeof...(Is) == 0) { + return false; + } else { return (... && (std::get(iters_) != std::get(other.iters_))); + } } template class IT, @@ -141,7 +148,7 @@ class iter::impl::Productor { return {(*std::get(iters_))...}; } - auto operator-> () -> ArrowProxy { + auto operator->() -> ArrowProxy { return {**this}; } }; @@ -192,7 +199,6 @@ namespace iter { constexpr std::array, 1> product() { return {{}}; } - } namespace iter::impl { diff --git a/range.hpp b/cppitertools/range.hpp similarity index 84% rename from range.hpp rename to cppitertools/range.hpp index d4727b4b..2efa7396 100644 --- a/range.hpp +++ b/cppitertools/range.hpp @@ -123,7 +123,51 @@ class iter::impl::Range { constexpr Range(T start, T stop, T step = 1) noexcept : start_{start}, stop_{stop}, step_{step} {} + // if val is "before" the stopping point. + static constexpr bool is_within_range( + T val, T stop_val, [[maybe_unused]] T step_val) { + if constexpr (std::is_unsigned{}) { + return val < stop_val; + } else { + return !(step_val > 0 && val >= stop_val) + && !(step_val < 0 && val <= stop_val); + } + } + public: + constexpr T start() const noexcept { + return start_; + } + + constexpr T stop() const noexcept { + return stop_; + } + + constexpr T step() const noexcept { + return step_; + } + + constexpr T operator[](std::size_t index) const noexcept { + return start() + (step() * index); + } + + constexpr std::size_t size() const noexcept { + static_assert(!std::is_floating_point_v, + "range size() not supperted with floating point types"); + if (!is_within_range(start(), stop(), step())) { + return 0; + } + + auto diff = stop() - start(); + auto res = diff / step(); + assert(res >= 0); + auto result = static_cast(res); + if (diff % step()) { + ++result; + } + return result; + } + // the reference type here is T, which doesn't strictly follow all // of the rules, but std::vector::iterator::reference isn't // a reference type either, this isn't any worse @@ -139,15 +183,8 @@ class iter::impl::Range { const Iterator& lhs, const Iterator& rhs) noexcept { assert(!lhs.is_end); assert(rhs.is_end); - if - constexpr(std::is_unsigned{}) { - return lhs.data.value() < rhs.data.value(); - } - else { - return !(lhs.data.step() > 0 && lhs.data.value() >= rhs.data.value()) - && !(lhs.data.step() < 0 - && lhs.data.value() <= rhs.data.value()); - } + return is_within_range( + lhs.data.value(), rhs.data.value(), lhs.data.step()); } static bool not_equal_to_end( @@ -183,7 +220,7 @@ class iter::impl::Range { return *this; } - Iterator operator++(int)noexcept { + Iterator operator++(int) noexcept { auto ret = *this; ++*this; return ret; diff --git a/repeat.hpp b/cppitertools/repeat.hpp similarity index 92% rename from repeat.hpp rename to cppitertools/repeat.hpp index 01739c6c..327c21b0 100644 --- a/repeat.hpp +++ b/cppitertools/repeat.hpp @@ -83,6 +83,14 @@ class iter::impl::RepeaterWithCount { constexpr Iterator end() const { return {&this->elem_, 0}; } + + constexpr Iterator rbegin() const { + return begin(); + } + + constexpr Iterator rend() const { + return end(); + } }; template @@ -131,7 +139,7 @@ class iter::impl::Repeater { return *this; } - constexpr Iterator operator++(int)const { + constexpr Iterator operator++(int) const { return *this; } @@ -159,6 +167,14 @@ class iter::impl::Repeater { constexpr Iterator end() const { return {nullptr}; } + + constexpr Iterator rbegin() const { + return begin(); + } + + constexpr Iterator rend() const { + return end(); + } }; template diff --git a/reversed.hpp b/cppitertools/reversed.hpp similarity index 98% rename from reversed.hpp rename to cppitertools/reversed.hpp index 3b914bb1..022088f6 100644 --- a/reversed.hpp +++ b/cppitertools/reversed.hpp @@ -45,7 +45,7 @@ namespace iter { using ReversedFn = IterToolFn; } - constexpr impl::ReversedFn reversed{}; + inline constexpr impl::ReversedFn reversed{}; } template diff --git a/slice.hpp b/cppitertools/slice.hpp similarity index 92% rename from slice.hpp rename to cppitertools/slice.hpp index 22855645..38557016 100644 --- a/slice.hpp +++ b/cppitertools/slice.hpp @@ -51,7 +51,7 @@ class iter::impl::Sliced { using value_type = iterator_traits_deref; using difference_type = std::ptrdiff_t; using pointer = value_type*; - using reference = value_type&; + using reference = iterator_deref; Iterator(IteratorWrapper&& sub_iter, IteratorWrapper&& sub_end, DifferenceType start, @@ -131,10 +131,9 @@ struct iter::impl::SliceFn { private: friend SliceFn; - constexpr FnPartial(DifferenceType start, DifferenceType stop, - DifferenceType step) noexcept : start_{start}, - stop_{stop}, - step_{step} {} + constexpr FnPartial( + DifferenceType start, DifferenceType stop, DifferenceType step) noexcept + : start_{start}, stop_{stop}, step_{step} {} DifferenceType start_; DifferenceType stop_; DifferenceType step_; @@ -159,8 +158,8 @@ struct iter::impl::SliceFn { template >> - constexpr FnPartial operator()(DifferenceType stop) const - noexcept { + constexpr FnPartial operator()( + DifferenceType stop) const noexcept { return {0, stop, 1}; } @@ -173,7 +172,7 @@ struct iter::impl::SliceFn { }; namespace iter { - constexpr impl::SliceFn slice{}; + inline constexpr impl::SliceFn slice{}; } #endif diff --git a/sliding_window.hpp b/cppitertools/sliding_window.hpp similarity index 89% rename from sliding_window.hpp rename to cppitertools/sliding_window.hpp index fcb41e00..01c348ba 100644 --- a/sliding_window.hpp +++ b/cppitertools/sliding_window.hpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace iter { @@ -15,7 +16,7 @@ namespace iter { class WindowSlider; using SlidingWindowFn = IterToolFnBindSizeTSecond; } - constexpr impl::SlidingWindowFn sliding_window{}; + inline constexpr impl::SlidingWindowFn sliding_window{}; } template @@ -41,8 +42,9 @@ class iter::impl::WindowSlider { private: template friend class Iterator; + std::shared_ptr> window_ = + std::make_shared>(); IteratorWrapper sub_iter_; - DerefVec window_; public: using iterator_category = std::input_iterator_tag; @@ -56,7 +58,7 @@ class iter::impl::WindowSlider { : sub_iter_(std::move(sub_iter)) { std::size_t i{0}; while (i < window_sz && sub_iter_ != sub_end) { - window_.get().push_back(sub_iter_); + window_->get().push_back(sub_iter_); ++i; if (i != window_sz) { ++sub_iter_; @@ -75,17 +77,17 @@ class iter::impl::WindowSlider { } DerefVec& operator*() { - return window_; + return *window_; } DerefVec* operator->() { - return window_; + return window_.get(); } Iterator& operator++() { ++sub_iter_; - window_.get().pop_front(); - window_.get().push_back(sub_iter_); + window_->get().pop_front(); + window_->get().push_back(sub_iter_); return *this; } diff --git a/sorted.hpp b/cppitertools/sorted.hpp similarity index 99% rename from sorted.hpp rename to cppitertools/sorted.hpp index 4e963228..a704af3f 100644 --- a/sorted.hpp +++ b/cppitertools/sorted.hpp @@ -15,7 +15,7 @@ namespace iter { class SortedView; using SortedFn = IterToolFnOptionalBindSecond>; } - constexpr impl::SortedFn sorted{}; + inline constexpr impl::SortedFn sorted{}; } template diff --git a/starmap.hpp b/cppitertools/starmap.hpp similarity index 87% rename from starmap.hpp rename to cppitertools/starmap.hpp index d3fb678b..f9c2d260 100644 --- a/starmap.hpp +++ b/cppitertools/starmap.hpp @@ -34,8 +34,10 @@ class iter::impl::StarMapper { mutable Func func_; Container container_; - using StarIterDeref = std::remove_reference_t>()))>; + using StarIterDeref = + decltype(std::apply(func_, std::declval>())); + using StarIterDerefValue = + std::remove_cv_t>; StarMapper(Func f, Container&& c) : func_(std::move(f)), container_(std::forward(c)) {} @@ -53,10 +55,10 @@ class iter::impl::StarMapper { public: using iterator_category = std::input_iterator_tag; - using value_type = StarIterDeref; + using value_type = StarIterDerefValue; using difference_type = std::ptrdiff_t; using pointer = value_type*; - using reference = value_type&; + using reference = StarIterDeref; Iterator(Func& f, IteratorWrapper&& sub_iter) : func_(&f), sub_iter_(std::move(sub_iter)) {} @@ -86,7 +88,7 @@ class iter::impl::StarMapper { return std::apply(*func_, *sub_iter_); } - auto operator-> () -> ArrowProxy { + auto operator->() -> ArrowProxy { return {**this}; } }; @@ -130,7 +132,12 @@ class iter::impl::TupleStarMapper { class IteratorData { public: template - static auto get_and_call_with_tuple(Func& f, TupTypeT& t) -> decltype(std::apply(f, std::get(t))) { //TODO: Remove duplicated expression in decltype, using decltype(auto) as return type, when all compilers correctly deduce type (i.e. MSVC cl 19.15 does not do it). + static auto get_and_call_with_tuple(Func& f, TupTypeT& t) + -> decltype(std::apply(f, + std::get(t))) { // TODO: Remove duplicated expression in + // decltype, using decltype(auto) as return + // type, when all compilers correctly deduce + // type (i.e. MSVC cl 19.15 does not do it). return std::apply(f, std::get(t)); } @@ -169,7 +176,7 @@ class iter::impl::TupleStarMapper { return IteratorData::callers[index_](*func_, *tup_); } - auto operator-> () { + auto operator->() { return ArrowProxy{**this}; } @@ -245,7 +252,7 @@ struct iter::impl::StarMapFn : PipeableAndBindFirst { }; namespace iter { - constexpr impl::StarMapFn starmap{}; + inline constexpr impl::StarMapFn starmap{}; } #endif diff --git a/takewhile.hpp b/cppitertools/takewhile.hpp similarity index 81% rename from takewhile.hpp rename to cppitertools/takewhile.hpp index 8e28fcf2..3a351d0c 100644 --- a/takewhile.hpp +++ b/cppitertools/takewhile.hpp @@ -16,7 +16,7 @@ namespace iter { using TakeWhileFn = IterToolFnOptionalBindFirst; } - constexpr impl::TakeWhileFn takewhile{}; + inline constexpr impl::TakeWhileFn takewhile{}; } template @@ -29,7 +29,7 @@ class iter::impl::Taker { Taker(FilterFunc filter_func, Container&& container) : container_(std::forward(container)), - filter_func_(filter_func) {} + filter_func_(std::move(filter_func)) {} public: Taker(Taker&&) = default; @@ -40,11 +40,14 @@ class iter::impl::Taker { template friend class Iterator; using Holder = DerefHolder>; - IteratorWrapper sub_iter_; + // I want this mutable so I can use operator* reliably in the const + // context of init_if_first_use + mutable IteratorWrapper sub_iter_; IteratorWrapper sub_end_; - Holder item_; + mutable Holder item_; FilterFunc* filter_func_; + // see comments from filter about mutability void inc_sub_iter() { ++sub_iter_; if (sub_iter_ != sub_end_) { @@ -52,12 +55,19 @@ class iter::impl::Taker { } } - void check_current() { + void check_current() const { if (sub_iter_ != sub_end_ && !std::invoke(*filter_func_, item_.get())) { sub_iter_ = sub_end_; } } + void init_if_first_use() const { + if (!item_ && sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + check_current(); + } + } + public: using iterator_category = std::input_iterator_tag; using value_type = iterator_traits_deref; @@ -69,22 +79,20 @@ class iter::impl::Taker { IteratorWrapper&& sub_end, FilterFunc& filter_func) : sub_iter_{std::move(sub_iter)}, sub_end_{std::move(sub_end)}, - filter_func_(&filter_func) { - if (sub_iter_ != sub_end_) { - item_.reset(*sub_iter_); - } - check_current(); - } + filter_func_(&filter_func) {} typename Holder::reference operator*() { + init_if_first_use(); return item_.get(); } typename Holder::pointer operator->() { + init_if_first_use(); return item_.get_ptr(); } Iterator& operator++() { + init_if_first_use(); inc_sub_iter(); check_current(); return *this; @@ -98,6 +106,8 @@ class iter::impl::Taker { template bool operator!=(const Iterator& other) const { + init_if_first_use(); + other.init_if_first_use(); return sub_iter_ != other.sub_iter_; } diff --git a/cppitertools/unique_everseen.hpp b/cppitertools/unique_everseen.hpp new file mode 100644 index 00000000..2195a4bd --- /dev/null +++ b/cppitertools/unique_everseen.hpp @@ -0,0 +1,51 @@ +#ifndef ITER_UNIQUE_EVERSEEN_HPP_ +#define ITER_UNIQUE_EVERSEEN_HPP_ + +#include "filter.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + struct UniqueEverseenFn : Pipeable { + private: + template + using Key = std::decay_t>; + + public: + template + auto operator()(Container&& container, const Hash& hash, + const KeyEqual& key_equal) const { + // You can't pass a hash function or an equality function without + // passing a bucket_count as well. We get the default bucket count here + // the first time this function runs. + static auto default_bucket_count = + std::unordered_set{}.bucket_count(); + using elem_type = iterator_deref; + auto func = + [elem_seen = + std::unordered_set, Hash, KeyEqual>( + default_bucket_count, hash, key_equal)]( + const std::remove_reference_t& e) mutable { + return elem_seen.insert(e).second; + }; + return filter(func, std::forward(container)); + } + + template + auto operator()(Container&& container) const { + return (*this)(std::forward(container), + std::hash>{}, std::equal_to>{}); + } + }; + } + + inline constexpr impl::UniqueEverseenFn unique_everseen{}; +} + +#endif diff --git a/cppitertools/unique_justseen.hpp b/cppitertools/unique_justseen.hpp new file mode 100644 index 00000000..e86d20ba --- /dev/null +++ b/cppitertools/unique_justseen.hpp @@ -0,0 +1,31 @@ +#ifndef ITER_UNIQUE_JUSTSEEN_HPP +#define ITER_UNIQUE_JUSTSEEN_HPP + +#include "groupby.hpp" +#include "imap.hpp" + +#include +#include + +namespace iter { + namespace impl { + struct UniqueJustseenFn + : PipeableAndBindOptionalSecond { + public: + using PipeableAndBindOptionalSecond:: + operator(); + template + auto operator()(Container&& container, KeyFunc key_fn) const { + // decltype(auto) return type in lambda so reference types are preserved + return imap( + [](auto&& group) -> decltype(auto) { + return *get_begin(group.second); + }, + groupby(std::forward(container), std::move(key_fn))); + } + }; + } + inline constexpr impl::UniqueJustseenFn unique_justseen{}; +} + +#endif diff --git a/zip.hpp b/cppitertools/zip.hpp similarity index 93% rename from zip.hpp rename to cppitertools/zip.hpp index ee973b9e..98bfd3c4 100644 --- a/zip.hpp +++ b/cppitertools/zip.hpp @@ -41,9 +41,14 @@ class iter::impl::Zipped { template class IteratorTuple, template class TupleDeref> class Iterator { + // see gcc bug 87651 +#if NO_GCC_FRIEND_ERROR private: template class, template class> friend class Iterator; +#else + public: +#endif IteratorTuple iters_; public: @@ -51,7 +56,7 @@ class iter::impl::Zipped { using value_type = TupleDeref; using difference_type = std::ptrdiff_t; using pointer = value_type*; - using reference = value_type&; + using reference = value_type; Iterator(IteratorTuple&& iters) : iters_(std::move(iters)) {} @@ -69,9 +74,11 @@ class iter::impl::Zipped { template class IT, template class TD> bool operator!=(const Iterator& other) const { - if constexpr (sizeof...(Is) == 0) return false; - else + if constexpr (sizeof...(Is) == 0) { + return false; + } else { return (... && (std::get(iters_) != std::get(other.iters_))); + } } template class IT, @@ -84,7 +91,7 @@ class iter::impl::Zipped { return {(*std::get(iters_))...}; } - auto operator-> () -> ArrowProxy { + auto operator->() -> ArrowProxy { return {**this}; } }; diff --git a/zip_longest.hpp b/cppitertools/zip_longest.hpp similarity index 98% rename from zip_longest.hpp rename to cppitertools/zip_longest.hpp index 89e637e3..238ec569 100644 --- a/zip_longest.hpp +++ b/cppitertools/zip_longest.hpp @@ -49,10 +49,14 @@ class iter::impl::ZippedLongest { template class IterTuple, template class OptTempl> class Iterator { +#if NO_GCC_FRIEND_ERROR private: template class, template class> friend class Iterator; +#else + public: +#endif IterTuple iters_; IterTuple ends_; diff --git a/examples/SConstruct b/examples/SConstruct index c1ed4d10..cb5c373c 100644 --- a/examples/SConstruct +++ b/examples/SConstruct @@ -3,7 +3,7 @@ import os env = Environment( ENV=os.environ, CXXFLAGS= ['-g', '-Wall', '-Wextra', - '-pedantic', '-std=c++1z', + '-pedantic', '-std=c++17', '-I/usr/local/include' ], CPPPATH='..', @@ -15,6 +15,7 @@ env['ENV']['TERM'] = os.environ['TERM'] progs = Split( ''' accumulate + batched chain chunked combinatoric diff --git a/examples/accumulate_examples.cpp b/examples/accumulate_examples.cpp index 9d9a2f67..4d8569d2 100644 --- a/examples/accumulate_examples.cpp +++ b/examples/accumulate_examples.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/examples/batched_examples.cpp b/examples/batched_examples.cpp new file mode 100644 index 00000000..1dfa6abf --- /dev/null +++ b/examples/batched_examples.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include + +int main() { + std::vector v {1,2,3,4,5,6,7,8,9}; + std::cout << "num batches: 5\n"; + for (auto&& sec : iter::batched(v, 5)) { + for (auto&& i : sec) { + std::cout << i << " "; + } + std::cout << '\n'; + } + + std::cout << "num batches: 4\n"; + for (auto&& sec : iter::batched(v, 4)) { + for (auto&& i : sec) { + std::cout << i << " "; + } + std::cout << '\n'; + } + + std::cout << "num batches: 6\n"; + for (auto&& sec : iter::batched(v, 6)) { + for (auto&& i : sec) { + std::cout << i << " "; + } + std::cout << '\n'; + } +} diff --git a/examples/chain_examples.cpp b/examples/chain_examples.cpp index 607fa040..cdcd7fed 100644 --- a/examples/chain_examples.cpp +++ b/examples/chain_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/chunked_examples.cpp b/examples/chunked_examples.cpp index 9c3046f5..c3325df1 100644 --- a/examples/chunked_examples.cpp +++ b/examples/chunked_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/combinatoric_examples.cpp b/examples/combinatoric_examples.cpp index 653d428c..0915f985 100644 --- a/examples/combinatoric_examples.cpp +++ b/examples/combinatoric_examples.cpp @@ -1,8 +1,8 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/examples/compress_examples.cpp b/examples/compress_examples.cpp index b60197a9..dbabbbb9 100644 --- a/examples/compress_examples.cpp +++ b/examples/compress_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/count_examples.cpp b/examples/count_examples.cpp index 10ac8a72..70b876cc 100644 --- a/examples/count_examples.cpp +++ b/examples/count_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/examples/cycle_examples.cpp b/examples/cycle_examples.cpp index 7f973d66..0598431a 100644 --- a/examples/cycle_examples.cpp +++ b/examples/cycle_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/dropwhile_examples.cpp b/examples/dropwhile_examples.cpp index 8c628f1a..c8f993c0 100644 --- a/examples/dropwhile_examples.cpp +++ b/examples/dropwhile_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/enumerate_examples.cpp b/examples/enumerate_examples.cpp index 899c3b39..822abbbf 100644 --- a/examples/enumerate_examples.cpp +++ b/examples/enumerate_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/filter_examples.cpp b/examples/filter_examples.cpp index d28c443a..5ce16af2 100644 --- a/examples/filter_examples.cpp +++ b/examples/filter_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/filterfalse_examples.cpp b/examples/filterfalse_examples.cpp index dd937cee..0edc004a 100644 --- a/examples/filterfalse_examples.cpp +++ b/examples/filterfalse_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/groupby_examples.cpp b/examples/groupby_examples.cpp index 8d4a9918..5525f9ff 100644 --- a/examples/groupby_examples.cpp +++ b/examples/groupby_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/imap_examples.cpp b/examples/imap_examples.cpp index 812281b6..95fee802 100644 --- a/examples/imap_examples.cpp +++ b/examples/imap_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/mixed_examples.cpp b/examples/mixed_examples.cpp index 08ff774b..9f1ba056 100644 --- a/examples/mixed_examples.cpp +++ b/examples/mixed_examples.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/examples/range_examples.cpp b/examples/range_examples.cpp index 1dd03199..ff328ee7 100644 --- a/examples/range_examples.cpp +++ b/examples/range_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/examples/repeat_examples.cpp b/examples/repeat_examples.cpp index 24d37a8d..2dceaf98 100644 --- a/examples/repeat_examples.cpp +++ b/examples/repeat_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/examples/reversed_examples.cpp b/examples/reversed_examples.cpp index d4d804d9..1bb8dbed 100644 --- a/examples/reversed_examples.cpp +++ b/examples/reversed_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/slice_examples.cpp b/examples/slice_examples.cpp index 42a16e33..d9f2501a 100644 --- a/examples/slice_examples.cpp +++ b/examples/slice_examples.cpp @@ -1,7 +1,7 @@ #include -#include -#include +#include +#include #include #include diff --git a/examples/sliding_window_examples.cpp b/examples/sliding_window_examples.cpp index ff286202..7658182d 100644 --- a/examples/sliding_window_examples.cpp +++ b/examples/sliding_window_examples.cpp @@ -1,4 +1,4 @@ -#include "sliding_window.hpp" +#include "cppitertools/sliding_window.hpp" #include #include diff --git a/examples/sorted_examples.cpp b/examples/sorted_examples.cpp index 109d137d..4e6b5a22 100644 --- a/examples/sorted_examples.cpp +++ b/examples/sorted_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/starmap_examples.cpp b/examples/starmap_examples.cpp index 9797756c..b375ef3e 100644 --- a/examples/starmap_examples.cpp +++ b/examples/starmap_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/takewhile_examples.cpp b/examples/takewhile_examples.cpp index 3062a141..d86d6442 100644 --- a/examples/takewhile_examples.cpp +++ b/examples/takewhile_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/unique_everseen_examples.cpp b/examples/unique_everseen_examples.cpp index a670f0b1..52dc02fa 100644 --- a/examples/unique_everseen_examples.cpp +++ b/examples/unique_everseen_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/unique_justseen_examples.cpp b/examples/unique_justseen_examples.cpp index 878cd4f8..7c8cd9aa 100644 --- a/examples/unique_justseen_examples.cpp +++ b/examples/unique_justseen_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/zip_examples.cpp b/examples/zip_examples.cpp index d35dd0cf..51febf13 100644 --- a/examples/zip_examples.cpp +++ b/examples/zip_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/zip_longest_examples.cpp b/examples/zip_longest_examples.cpp index 5f8dc9e7..d3c80173 100644 --- a/examples/zip_longest_examples.cpp +++ b/examples/zip_longest_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/imap.hpp b/imap.hpp deleted file mode 100644 index f022e6d0..00000000 --- a/imap.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef ITER_IMAP_H_ -#define ITER_IMAP_H_ - -#include "starmap.hpp" -#include "zip.hpp" - -#include - -namespace iter { - namespace impl { - struct IMapFn : PipeableAndBindFirst { - template - decltype(auto) operator()( - MapFunc map_func, Containers&&... containers) const { - return starmap(map_func, zip(std::forward(containers)...)); - } - using PipeableAndBindFirst::operator(); - }; - } - constexpr impl::IMapFn imap{}; -} - -#endif diff --git a/test/BUILD b/test/BUILD index 5c7c5d9c..cc9aefe6 100644 --- a/test/BUILD +++ b/test/BUILD @@ -2,6 +2,7 @@ load(":make_tests.bzl", "itertools_tests") progs = [ "accumulate", + "batched", "chain", "chunked", "combinations", @@ -36,4 +37,13 @@ progs = [ "helpers", ] +SANITIZE = "-fsanitize=address,undefined" + +cc_library( + name = "test_main", + srcs = ["test_main.cpp", "catch.hpp"], + copts = [SANITIZE, "-Wall", "-Wextra", "-std=c++17", "-g"], + linkopts = [SANITIZE], +) + itertools_tests(progs) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 315e4b04..e9140f47 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,10 +9,13 @@ cmake_minimum_required(VERSION 3.8) project(cppitertools_tests CXX) set (CMAKE_CXX_STANDARD 17) +find_package(Boost 1.60.0 REQUIRED) include_directories( .. + ${Boost_INCLUDE_DIRS} ) + include(CheckIncludeFileCXX) set(CMAKE_REQUIRED_INCLUDES ${PROJECT_SOURCE_DIR}) CHECK_INCLUDE_FILE_CXX(catch.hpp _has_catch) diff --git a/test/SConstruct b/test/SConstruct index 5c4bc272..98af3051 100644 --- a/test/SConstruct +++ b/test/SConstruct @@ -11,9 +11,19 @@ env = Environment( # allows highighting to print to terminal from compiler output env['ENV']['TERM'] = os.environ['TERM'] +try: + env.Replace(CXX=os.environ['CXX']) +except KeyError: + pass + +if env['CXX'].startswith('clang++'): + env['CXXFLAGS'].append('-stdlib=libc++') + env['LINKFLAGS'].append('-stdlib=libc++') + progs = Split( ''' accumulate + batched chain chunked combinations diff --git a/test/download_catch.sh b/test/download_catch.sh index 2988d1b6..4f1ed06b 100755 --- a/test/download_catch.sh +++ b/test/download_catch.sh @@ -1,2 +1,2 @@ #!/usr/bin/env sh -wget -c https://github.com/catchorg/Catch2/releases/download/v2.0.1/catch.hpp +wget -c https://github.com/catchorg/Catch2/releases/download/v2.13.10/catch.hpp diff --git a/test/helpers.hpp b/test/helpers.hpp index 7c466145..f94250f3 100644 --- a/test/helpers.hpp +++ b/test/helpers.hpp @@ -1,9 +1,9 @@ #ifndef TEST_HELPER_H_ #define TEST_HELPER_H_ -#include - +#include #include +#include #include #include #include @@ -31,6 +31,27 @@ namespace itertest { SolidInt(SolidInt&&) = delete; }; + class Integer { + private: + int i_{}; + + public: + constexpr Integer(int i) : i_{i} {} + constexpr bool is_zero() const { + return i_ == 0; + } + constexpr bool operator==(const Integer& other) const { + return i_ == other.i_; + } + constexpr bool operator!=(const Integer& other) const { + return i_ != other.i_; + } + + constexpr bool is_positive() const { + return i_ > 0; + } + }; + namespace { struct DoubleDereferenceError : std::exception { const char* what() const noexcept override { @@ -187,6 +208,10 @@ namespace itertest { template struct IsIterator : std::false_type {}; + template + struct ReferenceMatchesDeref + : std::is_same())> {}; + template struct IsIterator())), // copyctor @@ -195,8 +220,8 @@ namespace itertest { decltype(std::declval().operator->()), // operator-> decltype(++std::declval()), // prefix ++ decltype(std::declval()++), // postfix ++ - decltype( - std::declval() != std::declval()), // != + decltype(std::declval() + != std::declval()), // != decltype(std::declval() == std::declval()) // == >> : std::true_type {}; @@ -390,4 +415,40 @@ class IntCharPairRange : DiffEndRange, IncIntCharPair>({0, 'a'}, stop) {} }; +inline bool less_than_five(int i) { + return i < 5; +} + +class LessThanValue { + private: + int compare_val; + + public: + LessThanValue(int v) : compare_val(v) {} + + bool operator()(int i) { + return i < this->compare_val; + } +}; + +class MoveOnlyLessThanValue { + private: + // unique_ptr is better for triggering asan than an int if there's a dangling + // reference to the callable + std::unique_ptr compare_val; + + public: + MoveOnlyLessThanValue(int v) : compare_val{std::make_unique(v)} {} + + MoveOnlyLessThanValue(const MoveOnlyLessThanValue&) = delete; + MoveOnlyLessThanValue& operator=(const MoveOnlyLessThanValue&) = delete; + + MoveOnlyLessThanValue(MoveOnlyLessThanValue&&) = default; + MoveOnlyLessThanValue& operator=(MoveOnlyLessThanValue&&) = default; + + bool operator()(int i) { + return i < *compare_val; + } +}; + #endif diff --git a/test/make_tests.bzl b/test/make_tests.bzl index 883f0576..68a428b8 100644 --- a/test/make_tests.bzl +++ b/test/make_tests.bzl @@ -2,7 +2,10 @@ def itertools_tests(progs): for p in progs: native.cc_test( name = "test_{}".format(p), - srcs = ["test_{}.cpp".format(p), "test_main.cpp", "catch.hpp", "helpers.hpp"], - deps = ["//:cppitertools",], - copts = ["-I.", "-std=c++17", "-Wall", "-Wextra", "-pedantic", "-g"], + srcs = ["test_{}.cpp".format(p), "catch.hpp", "helpers.hpp"], + deps = [ + "//:cppitertools", + ":test_main", + ], + copts = ["-I.", "-std=c++17", "-Wall", "-Wextra", "-pedantic", "-g"], ) diff --git a/test/test_accumulate.cpp b/test/test_accumulate.cpp index 02467fb2..c868f0c5 100644 --- a/test/test_accumulate.cpp +++ b/test/test_accumulate.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" #include @@ -131,11 +131,24 @@ TEST_CASE("accumulate: operator->", "[accumulate]") { } TEST_CASE("accumulate: iterator meets requirements", "[accumulate]") { - Vec ns{}; - auto a = accumulate(ns, [](int a, int b) { return a + b; }); - auto it = std::begin(a); - it = std::begin(a); - REQUIRE(itertest::IsIterator::value); + std::vector ns{}; + SECTION("with reference return type") { + auto acc = accumulate(ns, [](int& a, int&) -> int& { return a; }); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + + SECTION("with const reference return type") { + auto acc = accumulate(ns, [](int& a, int&) -> const int& { return a; }); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + + SECTION("with value return type") { + auto acc = accumulate(ns, [](int a, int) -> int { return a; }); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } } TEST_CASE( diff --git a/test/test_batched.cpp b/test/test_batched.cpp new file mode 100644 index 00000000..098acecc --- /dev/null +++ b/test/test_batched.cpp @@ -0,0 +1,135 @@ +#include + +#include +#include +#include +#include + +#include "catch.hpp" +#include "helpers.hpp" + +using iter::batched; +using Vec = std::vector; +using ResVec = std::vector; + +TEST_CASE("batched: basic test", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6}; + ResVec results; + SECTION("Normal call") { + for (auto&& g : batched(ns, 2)) { + results.emplace_back(std::begin(g), std::end(g)); + } + } + SECTION("Pipe") { + for (auto&& g : ns | batched(2)) { + results.emplace_back(std::begin(g), std::end(g)); + } + } + + ResVec rc = {{1, 2, 3}, {4 ,5, 6}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: uneven batch sizes", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + ResVec results; + for (auto&& g : batched(ns, 6)) { + results.emplace_back(std::begin(g), std::end(g)); + } + + ResVec rc = {{1, 2}, {3, 4}, {5, 6}, {7}, {8}, {9}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: const batched", "[batched][const]") { + Vec ns = {1, 2, 3, 4, 5, 6}; + ResVec results; + SECTION("Normal call") { + const auto& ch = batched(ns, 2); + for (auto&& g : ch) { + results.emplace_back(std::begin(g), std::end(g)); + } + } + ResVec rc = {{1, 2, 3}, {4, 5, 6}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: const iterators can be compared to non-const iterators", + "[batched][const]") { + auto c = batched(Vec{}, 1); + const auto& cc = c; + (void)(std::begin(c) == std::end(cc)); +} + +TEST_CASE("batched: len(iterable) % num_batches != 0", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6, 7}; + ResVec results; + for (auto&& g : batched(ns, 3)) { + results.emplace_back(std::begin(g), std::end(g)); + } + + ResVec rc = {{1, 2, 3}, {4, 5}, {6, 7}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: num_batches > len(iterable)", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6, 7}; + ResVec results; + for (auto&& g : batched(ns, 9)) { + results.emplace_back(std::begin(g), std::end(g)); + } + + ResVec rc = {{1}, {2}, {3}, {4}, {5}, {6}, {7}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: iterators can be compared", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6, 7}; + auto g = batched(ns, 3); + auto it = std::begin(g); + REQUIRE(it == std::begin(g)); + REQUIRE_FALSE(it != std::begin(g)); + ++it; + REQUIRE(it != std::begin(g)); + REQUIRE_FALSE(it == std::begin(g)); +} + +TEST_CASE("batched: size 0 is empty", "[batched]") { + Vec ns{1, 2, 3}; + auto g = batched(ns, 0); + REQUIRE(std::begin(g) == std::end(g)); +} + +TEST_CASE("batched: Works with different begin and end types", "[batched]") { + CharRange cr{'f'}; + std::vector> results; + for (auto&& g : batched(cr, 3)) { + results.emplace_back(std::begin(g), std::end(g)); + } + std::vector> rc = {{'a', 'b'}, {'c', 'd'}, {'e'}}; + REQUIRE(results == rc); +} + +TEST_CASE("batched: empty iterable gives empty batched", "[batched]") { + Vec ns{}; + auto g = batched(ns, 1); + REQUIRE(std::begin(g) == std::end(g)); +} + +TEST_CASE("batched: iterator meets requirements", "[batched]") { + std::string s{}; + auto c = batched(s, 1); + REQUIRE(itertest::IsIterator::value); +} + +template +using ImpT = decltype(batched(std::declval(), 1)); +TEST_CASE("batched: has correct ctor and assign ops", "[batched]") { + REQUIRE(itertest::IsMoveConstructibleOnly>::value); + REQUIRE(itertest::IsMoveConstructibleOnly>::value); +} diff --git a/test/test_chain.cpp b/test/test_chain.cpp index 32b9f184..29a81512 100644 --- a/test/test_chain.cpp +++ b/test/test_chain.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" #include @@ -10,8 +10,8 @@ #include "catch.hpp" using iter::chain; -using itertest::SolidInt; using itertest::BasicIterable; +using itertest::SolidInt; using Vec = const std::vector; TEST_CASE("chain: three strings", "[chain]") { @@ -28,8 +28,8 @@ TEST_CASE("chain: three strings", "[chain]") { TEST_CASE("chain: const iteration", "[chain][const]") { std::string s1{"abc"}; - /* const */ std::string s2{"mno"}; - auto ch = chain(s1, s2, std::string{"xyz"}); + const std::string s2{"mno"}; + const auto ch = chain(s1, s2, std::string{"xyz"}); Vec v(std::begin(ch), std::end(ch)); Vec vc{'a', 'b', 'c', 'm', 'n', 'o', 'x', 'y', 'z'}; @@ -37,14 +37,34 @@ TEST_CASE("chain: const iteration", "[chain][const]") { REQUIRE(v == vc); } -// TODO make this work -#if 0 -TEST_CASE("chain: const iterators can be compared to non-const itertors", "[chain][const]") { - auto ch = chain(std::string{}, std::string{}); - const auto& cch = ch; - (void)(std::begin(ch) == std::end(cch)); +TEST_CASE("chain: const iterators can be compared to non-const itertors", + "[chain][const]") { + std::string s1{"abc"}; + std::list li{'m', 'n', 'o'}; + auto ch = chain(s1, li); + + const auto cch = chain(s1, li); + SECTION("begin and const begin compare equal") { + REQUIRE(std::begin(ch) == std::begin(cch)); + } + SECTION("begin and const end compare not-equal") { + REQUIRE_FALSE(std::begin(ch) == std::end(cch)); + } + SECTION("end and const end compare equal") { + REQUIRE(std::end(ch) == std::end(cch)); + } + SECTION( + "const and non-const iterator compare equal/not-equal at appropriate " + "pos.") { + auto iter = ch.begin(); + iter++; + auto citer = cch.begin(); + citer++; + REQUIRE(iter == citer); + citer++; + REQUIRE_FALSE(iter == citer); + } } -#endif TEST_CASE("chain: with different container types", "[chain]") { std::string s1{"abc"}; @@ -218,10 +238,32 @@ TEST_CASE( "chain.from_iterable: const iterators can be compared to non-const " "iterators", "[chain.from_iterable][const]") { - std::vector> v{}; + std::vector> v{{1, 2}, {4, 6}}; auto ch = chain.from_iterable(v); const auto& cch = ch; - (void)(std::begin(ch) == std::end(cch)); + + SECTION("begin and const end compare not-equal") { + REQUIRE_FALSE(std::begin(ch) == std::end(cch)); + } + SECTION("begin and const begin compare equal") { + REQUIRE(std::begin(ch) == std::begin(cch)); + } + SECTION("end and const end compare not-equal") { + REQUIRE(std::end(ch) == std::end(cch)); + } + SECTION( + "const and non-const iterator compare equal/not-equal at appropriate " + "pos.") { + auto iter = ch.begin(); + iter++; + auto citer = cch.begin(); + citer++; + REQUIRE(iter == citer); + citer++; + REQUIRE_FALSE(iter == citer); + iter++; + REQUIRE(iter == citer); + } } TEST_CASE("chain.fromm_iterable: Works with different begin and end types", @@ -233,6 +275,16 @@ TEST_CASE("chain.fromm_iterable: Works with different begin and end types", REQUIRE(v == vc); } +TEST_CASE("chain.from_iterable: Works with empty subiterable", + "[chain.from_iterable]") { + std::vector> ivv{ + {}, {2, 4, 6}, {}, {8, 10, 12}, {14, 16, 18}, {}}; + auto ch = chain.from_iterable(ivv); + const std::vector v(std::begin(ch), std::end(ch)); + const std::vector vi = {2, 4, 6, 8, 10, 12, 14, 16, 18}; + REQUIRE(v == vi); +} + TEST_CASE( "chain.from_iterable: iterators cant be copy constructed " "and assigned", @@ -299,8 +351,8 @@ template using ImpT2 = decltype(chain.from_iterable(std::declval())); TEST_CASE("chain.from_iterable: has correct ctor and assign ops", "[chain.from_iterable]") { - REQUIRE(itertest::IsMoveConstructibleOnly>>:: - value); - REQUIRE(itertest::IsMoveConstructibleOnly&>>:: - value); + REQUIRE(itertest::IsMoveConstructibleOnly< + ImpT2>>::value); + REQUIRE(itertest::IsMoveConstructibleOnly< + ImpT2&>>::value); } diff --git a/test/test_chunked.cpp b/test/test_chunked.cpp index 7e3165a0..3aaca797 100644 --- a/test/test_chunked.cpp +++ b/test/test_chunked.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/test/test_combinations.cpp b/test/test_combinations.cpp index f5c153cf..14548240 100644 --- a/test/test_combinations.cpp +++ b/test/test_combinations.cpp @@ -4,8 +4,7 @@ #undef CHAR_RANGE_DEFAULT_CONSTRUCTIBLE #undef DEFINE_DEFAULT_ITERATOR_CTOR -#include - +#include #include #include #include @@ -95,10 +94,31 @@ TEST_CASE("combinations: size too large gives no results", "[combinations]") { REQUIRE(std::begin(c) == std::end(c)); } -TEST_CASE("combinations: size 0 gives nothing", "[combinations]") { +TEST_CASE("combinations: size 0 gives one empty result", "[combinations]") { std::string s{"ABCD"}; - auto c = combinations(s, 0); - REQUIRE(std::begin(c) == std::end(c)); + + CharCombSet ans = {{}}; + + CharCombSet sc; + for (auto&& v : combinations(s, 0)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + + REQUIRE(ans == sc); +} + +TEST_CASE("combinations: size 0 gives one empty result for empty input", + "[combinations]") { + std::string s{}; + + CharCombSet ans = {{}}; + + CharCombSet sc; + for (auto&& v : combinations(s, 0)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + + REQUIRE(ans == sc); } TEST_CASE( diff --git a/test/test_combinations_with_replacement.cpp b/test/test_combinations_with_replacement.cpp index 61f30120..2c3ee0f6 100644 --- a/test/test_combinations_with_replacement.cpp +++ b/test/test_combinations_with_replacement.cpp @@ -1,5 +1,4 @@ -#include - +#include #include #include #include @@ -96,8 +95,23 @@ TEST_CASE("combinations_with_replacement: big size is no problem", TEST_CASE("combinations_with_replacement: 0 size is empty", "[combinations_with_replacement]") { std::string s{"A"}; - auto cwr = combinations_with_replacement(s, 0); - REQUIRE(std::begin(cwr) == std::end(cwr)); + CharCombSet sc; + for (auto v : combinations_with_replacement(s, 0)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + CharCombSet ans = {{}}; + REQUIRE(ans == sc); +} + +TEST_CASE("combinations_with_replacement: 0 size is empty with empty container", + "[combinations_with_replacement]") { + std::string s{}; + CharCombSet sc; + for (auto v : combinations_with_replacement(s, 0)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + CharCombSet ans = {{}}; + REQUIRE(ans == sc); } TEST_CASE("combinations_with_replacement: operator->", diff --git a/test/test_compress.cpp b/test/test_compress.cpp index 1330e9c2..70aa3f89 100644 --- a/test/test_compress.cpp +++ b/test/test_compress.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" #include diff --git a/test/test_count.cpp b/test/test_count.cpp index f0f0de68..d5248ac2 100644 --- a/test/test_count.cpp +++ b/test/test_count.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" #include @@ -65,6 +65,68 @@ TEST_CASE("count: with step > 1", "[count]") { REQUIRE(v == vc); } +TEST_CASE("count: with unsigned", "[count]") { + constexpr unsigned int uint_max = + static_cast(std::numeric_limits::max()); + std::vector v{}; + int steps = 0; + for (auto i : count(uint_max)) { + v.push_back(i); + if (steps == 2) { + break; + } + ++steps; + } + const std::vector vc{uint_max, uint_max + 1, uint_max + 2}; + + REQUIRE(v == vc); +} + +TEST_CASE("count: with negative step", "[count]") { + std::vector v{}; + int steps = 0; + for (auto i : count(0, -1)) { + v.push_back(i); + if (steps == 2) { + break; + } + ++steps; + } + const std::vector vc{0, -1, -2}; + + REQUIRE(v == vc); +} + +TEST_CASE("count: with double", "[count]") { + std::vector v{}; + int steps = 0; + for (auto i : count(1.0, 0.5)) { + v.push_back(i); + if (steps == 3) { + break; + } + ++steps; + } + const std::vector vc{1.0, 1.5, 2.0, 2.5}; + + REQUIRE(v == vc); +} + +TEST_CASE("count: with negative double step", "[count]") { + std::vector v{}; + int steps = 0; + for (auto i : count(1.0, -0.5)) { + v.push_back(i); + if (steps == 3) { + break; + } + ++steps; + } + const std::vector vc{1.0, 0.5, 0.0, -0.5}; + + REQUIRE(v == vc); +} + TEST_CASE("count: can bo constexpr", "[count]") { constexpr auto c = count(); constexpr auto c2 = count(5); diff --git a/test/test_cycle.cpp b/test/test_cycle.cpp index 84981878..0974a87c 100644 --- a/test/test_cycle.cpp +++ b/test/test_cycle.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" diff --git a/test/test_dropwhile.cpp b/test/test_dropwhile.cpp index 38e686aa..077bfd73 100644 --- a/test/test_dropwhile.cpp +++ b/test/test_dropwhile.cpp @@ -1,29 +1,58 @@ -#include - -#include "helpers.hpp" - +#include #include #include #include #include "catch.hpp" +#include "helpers.hpp" using iter::dropwhile; using Vec = const std::vector; -namespace { - class LessThanValue { - private: - int compare_val; +TEST_CASE("dropwhile: handles different callable types", "[dropwhile]") { + Vec ns = {1, 3, 4, 20, 2, 4, 6, 8}; + Vec vc = {20, 2, 4, 6, 8}; + std::vector v; + SECTION("with function pointer") { + auto d = dropwhile(less_than_five, ns); + v = Vec(std::begin(d), std::end(d)); + } + + SECTION("with callable object") { + auto d = dropwhile(LessThanValue{5}, ns); + v = Vec(std::begin(d), std::end(d)); + } - public: - LessThanValue(int v) : compare_val(v) {} + SECTION("with lvalue callable object") { + auto lt = LessThanValue{5}; + SECTION("normal call") { + auto d = dropwhile(lt, ns); + v = Vec(std::begin(d), std::end(d)); + } + SECTION("pipe") { + auto d = ns | dropwhile(lt); + v = Vec(std::begin(d), std::end(d)); + } + } - bool operator()(int i) { - return i < this->compare_val; + SECTION("with move-only callable object") { + SECTION("normal call") { + auto d = dropwhile(MoveOnlyLessThanValue{5}, ns); + v = Vec(std::begin(d), std::end(d)); + } + SECTION("pipe") { + auto d = ns | dropwhile(MoveOnlyLessThanValue{5}); + v = Vec(std::begin(d), std::end(d)); } - }; + } + + SECTION("with lambda") { + auto ltf = [](int i) { return i < 5; }; + auto d = dropwhile(ltf, ns); + v = Vec(std::begin(d), std::end(d)); + } + REQUIRE(v == vc); } TEST_CASE("dropwhile: skips initial elements", "[dropwhile]") { @@ -60,11 +89,12 @@ TEST_CASE("dropwhile: handles pointer to member", "[dropwhile]") { REQUIRE(v == vc); } -TEST_CASE("dropwhile: drop empty strings at front", "[dropwhile]") { - const std::vector words = {"", "", "check", "", "test"}; - auto dw = dropwhile(&std::string::empty, words); - const std::vector v(std::begin(dw), std::end(dw)); - const std::vector vc = {"check", "", "test"}; +TEST_CASE("dropwhile: drop zeros from front", "[dropwhile]") { + using itertest::Integer; + const std::vector nums = {0, 0, 3, 4, 0, 5, 0}; + auto dw = dropwhile(&Integer::is_zero, nums); + const std::vector v(std::begin(dw), std::end(dw)); + const std::vector vc = {3, 4, 0, 5, 0}; REQUIRE(v == vc); } @@ -104,7 +134,12 @@ TEST_CASE("dropwhile: skips all elements when all are true under predicate", "[dropwhile]") { Vec ns{3, 4, 5, 6}; auto d = dropwhile([](int i) { return i != 0; }, ns); - REQUIRE(std::begin(d) == std::end(d)); + SECTION("normal compare") { + REQUIRE(std::begin(d) == std::end(d)); + } + SECTION("reversed compare") { + REQUIRE(std::end(d) == std::begin(d)); + } } TEST_CASE("dropwhile: identity", "[dropwhile]") { @@ -118,7 +153,12 @@ TEST_CASE("dropwhile: identity", "[dropwhile]") { TEST_CASE("dropwhile: empty case is empty", "[dropwhile]") { Vec ns{}; auto d = dropwhile([](int i) { return i != 0; }, ns); - REQUIRE(std::begin(d) == std::end(d)); + SECTION("normal compare") { + REQUIRE(std::begin(d) == std::end(d)); + } + SECTION("reversed compare") { + REQUIRE(std::end(d) == std::begin(d)); + } } TEST_CASE("dropwhile: only drops from beginning", "[dropwhile]") { @@ -136,12 +176,6 @@ TEST_CASE("dropwhile: operator->", "[dropwhile]") { REQUIRE(it->size() == 6); } -namespace { - int less_than_five(int i) { - return i < 5; - } -} - TEST_CASE("dropwhile: works with function pointer", "[dropwhile]") { Vec ns{1, 2, 3, 4, 5, 6, 7, 8}; auto d = dropwhile(less_than_five, ns); diff --git a/test/test_enumerate.cpp b/test/test_enumerate.cpp index d5fb4238..777146e6 100644 --- a/test/test_enumerate.cpp +++ b/test/test_enumerate.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" diff --git a/test/test_filter.cpp b/test/test_filter.cpp index 73f684ee..445476c8 100644 --- a/test/test_filter.cpp +++ b/test/test_filter.cpp @@ -1,56 +1,58 @@ -#include - -#include "helpers.hpp" - +#include #include #include #include #include "catch.hpp" +#include "helpers.hpp" using iter::filter; using Vec = const std::vector; -namespace { - bool less_than_five(int i) { - return i < 5; - } - - class LessThanValue { - private: - int compare_val; - - public: - LessThanValue(int v) : compare_val(v) {} - - bool operator()(int i) { - return i < this->compare_val; - } - }; -} - TEST_CASE("filter: handles different callable types", "[filter]") { Vec ns = {1, 2, 5, 6, 3, 1, 7, -1, 5}; Vec vc = {1, 2, 3, 1, -1}; + std::vector v; SECTION("with function pointer") { auto f = filter(less_than_five, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); } SECTION("with callable object") { auto f = filter(LessThanValue{5}, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); + } + + SECTION("with lvalue callable object") { + auto lt = LessThanValue{5}; + SECTION("normal call") { + auto f = filter(lt, ns); + v = Vec(std::begin(f), std::end(f)); + } + SECTION("pipe") { + auto f = ns | filter(lt); + v = Vec(std::begin(f), std::end(f)); + } + } + + SECTION("with move-only callable object") { + SECTION("normal call") { + auto f = filter(MoveOnlyLessThanValue{5}, ns); + v = Vec(std::begin(f), std::end(f)); + } + SECTION("pipe") { + auto f = ns | filter(MoveOnlyLessThanValue{5}); + v = Vec(std::begin(f), std::end(f)); + } } SECTION("with lambda") { auto ltf = [](int i) { return i < 5; }; auto f = filter(ltf, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); } + REQUIRE(v == vc); } TEST_CASE("filter: handles pointer to member", "[filter]") { @@ -149,7 +151,12 @@ TEST_CASE("filter: all elements fail predicate", "[filter]") { Vec ns{10, 20, 30, 40, 50}; auto f = filter(less_than_five, ns); - REQUIRE(std::begin(f) == std::end(f)); + SECTION("normal compare") { + REQUIRE(std::begin(f) == std::end(f)); + } + SECTION("reversed compare") { + REQUIRE(std::end(f) == std::begin(f)); + } } TEST_CASE("filter: doesn't move or copy elements of iterable", "[filter]") { diff --git a/test/test_filterfalse.cpp b/test/test_filterfalse.cpp index 7a74a0df..ad79646a 100644 --- a/test/test_filterfalse.cpp +++ b/test/test_filterfalse.cpp @@ -1,63 +1,58 @@ -#include - -#include "helpers.hpp" - +#include #include #include #include #include "catch.hpp" +#include "helpers.hpp" using iter::filterfalse; using Vec = const std::vector; -namespace { - bool less_than_five(int i) { - return i < 5; - } - - class LessThanValue { - private: - int compare_val; - - public: - LessThanValue(int v) : compare_val(v) {} - - bool operator()(int i) { - return i < this->compare_val; - } - }; -} - TEST_CASE("filterfalse: handles different callable types", "[filterfalse]") { Vec ns = {1, 2, 5, 6, 3, 1, 7, -1, 5}; Vec vc = {5, 6, 7, 5}; + std::vector v; SECTION("with function pointer") { auto f = filterfalse(less_than_five, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); } SECTION("with callable object") { - std::vector v; - SECTION("Normal call") { - auto f = filterfalse(LessThanValue{5}, ns); - v.assign(std::begin(f), std::end(f)); + auto f = filterfalse(LessThanValue{5}, ns); + v = Vec(std::begin(f), std::end(f)); + } + + SECTION("with lvalue callable object") { + auto lt = LessThanValue{5}; + SECTION("normal call") { + auto f = filterfalse(lt, ns); + v = Vec(std::begin(f), std::end(f)); } - SECTION("Pipe") { - auto f = ns | filterfalse(LessThanValue{5}); - v.assign(std::begin(f), std::end(f)); + SECTION("pipe") { + auto f = ns | filterfalse(lt); + v = Vec(std::begin(f), std::end(f)); + } + } + + SECTION("with move-only callable object") { + SECTION("normal call") { + auto f = filterfalse(MoveOnlyLessThanValue{5}, ns); + v = Vec(std::begin(f), std::end(f)); + } + SECTION("pipe") { + auto f = ns | filterfalse(MoveOnlyLessThanValue{5}); + v = Vec(std::begin(f), std::end(f)); } - REQUIRE(v == vc); } SECTION("with lambda") { auto ltf = [](int i) { return i < 5; }; auto f = filterfalse(ltf, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); } + REQUIRE(v == vc); } TEST_CASE("filterfalse: handles pointer to member", "[filterfalse]") { diff --git a/test/test_groupby.cpp b/test/test_groupby.cpp index 1f86a40e..02798f39 100644 --- a/test/test_groupby.cpp +++ b/test/test_groupby.cpp @@ -1,8 +1,9 @@ -#include +#include #include "helpers.hpp" #include +#include #include #include @@ -21,22 +22,75 @@ namespace { } }; + struct MoveOnlySizer { + // here to trigger asan if a dangling reference gets used + std::unique_ptr counter_ = std::make_unique(); + + MoveOnlySizer(const MoveOnlySizer&) = delete; + MoveOnlySizer& operator=(const MoveOnlySizer&) = delete; + + MoveOnlySizer(MoveOnlySizer&&) = default; + MoveOnlySizer& operator=(MoveOnlySizer&&) = default; + + int operator()(const std::string& s) { + ++*counter_; + return s.size(); + } + }; + const std::vector vec = { "hi", "ab", "ho", "abc", "def", "abcde", "efghi"}; + + struct Person { + std::string name; + int id; + bool operator==(const Person& other) const { + return id == other.id; + } + }; + + std::string& get_name(Person& p) { + return p.name; + } + + template + std::vector extract_person_group(G g) { + return {std::begin(g), std::end(g)}; + } +} + +TEST_CASE("groupby: handle key function that returns reference", "[groupby]") { + std::vector people = {{"first", 1}, {"first", 2}, {"first", 3}}; + std::vector keys; + std::vector> groups; + + for (auto&& gb : groupby(people, get_name)) { + groups.push_back(extract_person_group(std::move(gb.second))); + keys.push_back(gb.first); + } + + const std::vector kc = {"first"}; + const std::vector> gc = { + {{"first", 1}, {"first", 2}, {"first", 3}}}; + + REQUIRE(people[0].name == "first"); + REQUIRE(gc[0][0].name == "first"); + REQUIRE(keys == kc); + REQUIRE(groups == gc); } -TEST_CASE("groupby: works with lambda, callable, and function pointer") { +TEST_CASE("groupby: handles different callable types", "[groupby]") { std::vector keys; std::vector> groups; - SECTION("Function pointer") { - SECTION("Normal call") { + SECTION("with function pointer") { + SECTION("normal call") { for (auto&& gb : groupby(vec, length)) { keys.push_back(gb.first); groups.emplace_back(std::begin(gb.second), std::end(gb.second)); } } - SECTION("Pipe") { + SECTION("pipe") { for (auto&& gb : vec | groupby(length)) { keys.push_back(gb.first); groups.emplace_back(std::begin(gb.second), std::end(gb.second)); @@ -44,23 +98,55 @@ TEST_CASE("groupby: works with lambda, callable, and function pointer") { } } - SECTION("Callable object") { - for (auto&& gb : groupby(vec, Sizer{})) { - keys.push_back(gb.first); - groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + SECTION("with callable object") { + SECTION("normal call") { + for (auto&& gb : groupby(vec, Sizer{})) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + SECTION("pipe") { + for (auto&& gb : vec | groupby(Sizer{})) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } } } - SECTION("lambda function") { - for (auto&& gb : - groupby(vec, [](const std::string& s) { return s.size(); })) { - keys.push_back(gb.first); - groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + SECTION("with lvalue callable object") { + auto sizer = Sizer{}; + SECTION("normal call") { + for (auto&& gb : groupby(vec, sizer)) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + SECTION("pipe") { + for (auto&& gb : vec | groupby(sizer)) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + } + + SECTION("with move-only callable object") { + SECTION("normal call") { + for (auto&& gb : groupby(vec, MoveOnlySizer{})) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + SECTION("pipe") { + for (auto&& gb : vec | groupby(MoveOnlySizer{})) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } } } - SECTION("pointer to member") { - for (auto&& gb : groupby(vec, &std::string::size)) { + SECTION("with lambda") { + for (auto&& gb : + groupby(vec, [](const std::string& s) { return s.size(); })) { keys.push_back(gb.first); groups.emplace_back(std::begin(gb.second), std::end(gb.second)); } @@ -70,13 +156,32 @@ TEST_CASE("groupby: works with lambda, callable, and function pointer") { REQUIRE(keys == kc); const std::vector> gc = { - {"hi", "ab", "ho"}, {"abc", "def"}, {"abcde", "efghi"}, + {"hi", "ab", "ho"}, + {"abc", "def"}, + {"abcde", "efghi"}, }; REQUIRE(groups == gc); } -TEST_CASE("groupby: handles pointer to member", "[groupby]") { +TEST_CASE("groupby: handles pointer to member function", "[groupby]") { + std::vector nums = { + 10, 20, 30, -5, 40, -6, -7, 50, 60, -8, -9, -10, -11, 70}; + + std::vector> groups; + std::vector keys; + for (auto&& gb : groupby(nums, &itertest::Integer::is_positive)) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + const std::vector kc = {true, false, true, false, true, false, true}; + const std::vector> gc = { + {10, 20, 30}, {-5}, {40}, {-6, -7}, {50, 60}, {-8, -9, -10, -11}, {70}}; + REQUIRE(keys == kc); + REQUIRE(groups == gc); +} + +TEST_CASE("groupby: handles pointer to data member", "[groupby]") { using itertest::Point; const std::vector ps = { {0, 2}, {0, 4}, {1, 3}, {1, 7}, {1, 10}, {1, 12}, {3, 5}}; @@ -151,7 +256,9 @@ TEST_CASE("groupby: const iteration", "[groupby][const]") { REQUIRE(keys == kc); const std::vector> gc = { - {"hi", "ab", "ho"}, {"abc", "def"}, {"abcde", "efghi"}, + {"hi", "ab", "ho"}, + {"abc", "def"}, + {"abcde", "efghi"}, }; REQUIRE(groups == gc); @@ -203,7 +310,8 @@ TEST_CASE("groupby: groups can be skipped completely", "[groupby]") { REQUIRE(keys == kc); const std::vector> gc = { - {"hi", "ab", "ho"}, {"abcde", "efghi"}, + {"hi", "ab", "ho"}, + {"abcde", "efghi"}, }; REQUIRE(groups == gc); @@ -226,7 +334,9 @@ TEST_CASE("groupby: groups can be skipped partially", "[groupby]") { REQUIRE(keys == kc); const std::vector> gc = { - {"hi", "ab", "ho"}, {"abc"}, {"abcde", "efghi"}, + {"hi", "ab", "ho"}, + {"abc"}, + {"abcde", "efghi"}, }; REQUIRE(groups == gc); @@ -253,7 +363,12 @@ TEST_CASE("groupby: single argument uses elements as keys", "[groupby]") { REQUIRE(keys == kc); std::vector> gc = { - {5, 5}, {6, 6}, {19, 19, 19, 19}, {69}, {0}, {10, 10}, + {5, 5}, + {6, 6}, + {19, 19, 19, 19}, + {69}, + {0}, + {10, 10}, }; REQUIRE(groups == gc); diff --git a/test/test_imap.cpp b/test/test_imap.cpp index b2be404d..cfda992b 100644 --- a/test/test_imap.cpp +++ b/test/test_imap.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" @@ -17,10 +17,29 @@ namespace { return i + 1; } - class PlusOner { + struct PlusOner { + int operator()(int i) const { + return i + 1; + } + }; + + class MoveOnlyAdder { + private: + // unique_ptr is better for triggering asan than an int if there's a + // dangling reference to the callable + std::unique_ptr add_amount_; + public: + MoveOnlyAdder(int v) : add_amount_{std::make_unique(v)} {} + + MoveOnlyAdder(const MoveOnlyAdder&) = delete; + MoveOnlyAdder& operator=(const MoveOnlyAdder&) = delete; + + MoveOnlyAdder(MoveOnlyAdder&&) = default; + MoveOnlyAdder& operator=(MoveOnlyAdder&&) = default; + int operator()(int i) { - return i + 1; + return i + *add_amount_; } }; @@ -33,31 +52,48 @@ namespace { } } -TEST_CASE("imap: works with lambda, callable, and function", "[imap]") { - Vec ns = {10, 20, 30}; +TEST_CASE("imap: handles different callable types", "[imap]") { + Vec ns = {10, 15, 300}; + Vec vc = {11, 16, 301}; std::vector v; - SECTION("with lambda") { - auto im = imap([](int i) { return i + 1; }, ns); - v.assign(std::begin(im), std::end(im)); + SECTION("with function pointer") { + auto m = imap(plusone, ns); + v = Vec(std::begin(m), std::end(m)); } - SECTION("with callable") { - SECTION("Normal call") { - auto im = imap(PlusOner{}, ns); - v.assign(std::begin(im), std::end(im)); + SECTION("with callable object") { + auto m = imap(PlusOner{}, ns); + v = Vec(std::begin(m), std::end(m)); + } + + SECTION("with lvalue callable object") { + auto lt = PlusOner{}; + SECTION("normal call") { + auto m = imap(lt, ns); + v = Vec(std::begin(m), std::end(m)); } - SECTION("Pipe") { - auto im = ns | imap(PlusOner{}); - v.assign(std::begin(im), std::end(im)); + SECTION("pipe") { + auto m = ns | imap(lt); + v = Vec(std::begin(m), std::end(m)); } } - SECTION("with function") { - auto im = imap(PlusOner{}, ns); - v.assign(std::begin(im), std::end(im)); + SECTION("with move-only callable object") { + SECTION("normal call") { + auto m = imap(MoveOnlyAdder{1}, ns); + v = Vec(std::begin(m), std::end(m)); + } + SECTION("pipe") { + auto m = ns | imap(MoveOnlyAdder{1}); + v = Vec(std::begin(m), std::end(m)); + } } - Vec vc = {11, 21, 31}; + SECTION("with lambda") { + auto ltf = [](int i) { return i + 1; }; + auto m = imap(ltf, ns); + v = Vec(std::begin(m), std::end(m)); + } REQUIRE(v == vc); } diff --git a/test/test_iterator_wrapper.cpp b/test/test_iterator_wrapper.cpp index 6c79a278..e5b0238a 100644 --- a/test/test_iterator_wrapper.cpp +++ b/test/test_iterator_wrapper.cpp @@ -1,7 +1,7 @@ // NOTE this header tests implementation details #include "catch.hpp" -#include "internal/iterator_wrapper.hpp" +#include "cppitertools/internal/iterator_wrapper.hpp" // I'm using a std::vector of 1 int instead of just an int in order to give // the iterator types non-trivial constructors, destructors, and assignment. diff --git a/test/test_iteratoriterator.cpp b/test/test_iteratoriterator.cpp index dd2c4987..11c1b16c 100644 --- a/test/test_iteratoriterator.cpp +++ b/test/test_iteratoriterator.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/test/test_iterbase.cpp b/test/test_iterbase.cpp index fecfb9a4..c904b841 100644 --- a/test/test_iterbase.cpp +++ b/test/test_iterbase.cpp @@ -2,8 +2,8 @@ // on any of this. Users of the library must consider all of this undocumented // -#include -#include +#include +#include #include #include #include @@ -63,6 +63,7 @@ TEST_CASE("are_same", "[iterbase]") { } TEST_CASE("DerefHolder lvalue reference", "[iterbase]") { + REQUIRE_FALSE(it::DerefHolder::stores_value); it::DerefHolder dh; int a = 2; int b = 5; @@ -78,6 +79,7 @@ TEST_CASE("DerefHolder lvalue reference", "[iterbase]") { } TEST_CASE("DerefHolder non-reference", "[iterbase]") { + REQUIRE(it::DerefHolder::stores_value); it::DerefHolder dh; int a = 2; int b = 5; @@ -85,6 +87,7 @@ TEST_CASE("DerefHolder non-reference", "[iterbase]") { dh.reset(std::move(a)); REQUIRE(dh.get() == 2); REQUIRE(&dh.get() != &a); + REQUIRE(dh.get_ptr() != &a); dh.reset(std::move(b)); REQUIRE(dh.get() == 5); @@ -94,3 +97,35 @@ TEST_CASE("get_begin returns correct type", "[iterbase]") { std::vector v; REQUIRE((std::is_same{})); } + +namespace NS1 { + + struct Dummy { + auto begin() { + return 0; + } + auto end() { + return 0; + } + }; + + template + auto begin(T& t) { + return t.begin(); + } + + template + auto end(T& t) { + return t.end(); + } + +} // namespace NS1 + +TEST_CASE("Detects is_iterable with ADL conflicts", "[iterbase]") { + int a[1]{}; + const int b[1]{}; + REQUIRE(iter::impl::is_iterable); + REQUIRE(iter::impl::is_iterable>); + REQUIRE(iter::impl::is_iterable); + REQUIRE(iter::impl::is_iterable); +} diff --git a/test/test_mixed.cpp b/test/test_mixed.cpp index 46d47f03..b190adf6 100644 --- a/test/test_mixed.cpp +++ b/test/test_mixed.cpp @@ -1,12 +1,11 @@ // mixing different itertools, there is nothing called iter::mixed() -#include "itertools.hpp" - -#include "catch.hpp" - #include #include +#include "catch.hpp" +#include "cppitertools/itertools.hpp" + class MyUnMovable { int val; @@ -75,8 +74,8 @@ TEST_CASE("filtering doesn't dereference multiple times", "[imap][filter]") { } TEST_CASE("dropwhile doesn't dereference multiple times", "[imap][dropwhile]") { - using iter::imap; using iter::dropwhile; + using iter::imap; std::array arr = {{{41}, {42}, {43}}}; @@ -128,9 +127,9 @@ TEST_CASE("sorted(chain.from_iterable)", "[sorted][chain.from_iterable]") { } TEST_CASE("filter into enumerate with pipe", "[filter][enumerate]") { - using iter::imap; - using iter::filter; using iter::enumerate; + using iter::filter; + using iter::imap; std::array arr = {{{41}, {42}, {43}, {44}}}; auto seq = @@ -144,8 +143,93 @@ TEST_CASE("filter into enumerate with pipe", "[filter][enumerate]") { REQUIRE(v == vc); } +TEST_CASE("enumerate(filter(chunked()))", "[filter][enumerate][chunked]") { + using iter::chunked; + using iter::enumerate; + using iter::filter; + std::vector v(500); + auto chunks = chunked(v, 100); + auto filtered = filter([](auto&) { return true; }, chunks); + for (auto&& [i, chunk] : enumerate(filtered)) { + (void)i; + REQUIRE(chunk.size() == 100); + } +} + +TEST_CASE("zip(filter(chunked()))", "[filter][chunked][zip]") { + using iter::chunked; + using iter::filter; + using iter::zip; + std::vector v(500); + auto chunks = chunked(v, 100); + auto filtered = filter([](auto&) { return true; }, chunks); + for (auto&& [chunk] : zip(filtered)) { + REQUIRE(chunk.size() == 100); + } +} + +TEST_CASE("zip(filter(sliding_window()))", "[filter][sliding_window][zip]") { + using iter::filter; + using iter::sliding_window; + using iter::zip; + std::vector v(15); + auto windows = sliding_window(v, 10); + auto filtered = filter([](auto&) { return true; }, windows); + for (auto&& [window] : zip(filtered)) { + REQUIRE(window.size() == 10); + } +} + +TEST_CASE("imap(filter(groupby()))", "[filter][groupby][imap])") { + using iter::filter; + using iter::groupby; + using iter::imap; + + std::vector v{true, true, true, false, false, true, true}; + auto a = groupby(v, [](bool b) { return b; }); + auto b = filter([](auto& g) { return g.first; }, a); + auto c = imap( + [](auto& g) { return std::distance(g.second.begin(), g.second.end()); }, + b); + for (auto x : c) { + (void)x; + } +} + +TEST_CASE("imap(dropwhile(groupby()))", "[dropwhile][groupby][imap])") { + using iter::dropwhile; + using iter::groupby; + using iter::imap; + + std::vector v{false, false, true, true, false, true, true}; + auto a = groupby(v, [](bool b) { return b; }); + auto b = dropwhile([](auto& g) { return g.first; }, a); + auto c = imap( + [](auto& g) { return std::distance(g.second.begin(), g.second.end()); }, + b); + for (auto x : c) { + (void)x; + } +} + +TEST_CASE("imap(takewhile(groupby()))", "[takewhile][groupby][imap])") { + using iter::groupby; + using iter::imap; + using iter::takewhile; + + std::vector v{true, true, true, false, false}; + auto a = groupby(v, [](bool b) { return b; }); + auto b = takewhile([](auto& g) { return g.first; }, a); + auto c = imap( + [](auto& g) { return std::distance(g.second.begin(), g.second.end()); }, + b); + for (auto x : c) { + (void)x; + } +} + TEST_CASE("chain.from_iterable: accept imap result that yields rvalues", - "[chain.from_iterable][imap]") { + "[chain.from_iterable][imap]") { using iter::chain; using iter::imap; const std::vector> ns = {{'a'}, {'q'}, {'x', 'z'}}; @@ -156,3 +240,38 @@ TEST_CASE("chain.from_iterable: accept imap result that yields rvalues", REQUIRE(v == vc); } + +TEST_CASE( + "filter(enumerate()), DerefHolder works correctly (see github issue #62", + "[filter][enumerate]") { + using iter::enumerate; + using iter::filter; + std::vector ns = {50, 55, 60, 65}; + auto f = + iter::filter([](auto& i) { return std::get<0>(i) > 1; }, enumerate(ns)); + const std::vector> v(std::begin(f), std::end(f)); + + const std::vector> vc = {{2, 60}, {3, 65}}; + + REQUIRE(v == vc); +} + +TEST_CASE("reversed(repeat(v, n))", "[repeat][reversed]") { + using iter::repeat; + using iter::reversed; + + auto rr = reversed(repeat('x', 5)); + std::string s(rr.begin(), rr.end()); + + REQUIRE(s == "xxxxx"); +} + +TEST_CASE("reversed(repeat(v))", "[repeat][reversed]") { + using iter::repeat; + using iter::reversed; + + auto rr = reversed(repeat('x')); + auto it = rr.begin(); + + REQUIRE(*it == 'x'); +} diff --git a/test/test_permutations.cpp b/test/test_permutations.cpp index 5da85fc8..8d77d2f2 100644 --- a/test/test_permutations.cpp +++ b/test/test_permutations.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" diff --git a/test/test_powerset.cpp b/test/test_powerset.cpp index c18dd1f7..44a7d8b5 100644 --- a/test/test_powerset.cpp +++ b/test/test_powerset.cpp @@ -1,4 +1,4 @@ -#include +#include #define CHAR_RANGE_DEFAULT_CONSTRUCTIBLE #include "helpers.hpp" diff --git a/test/test_product.cpp b/test/test_product.cpp index aa7ed1bb..a453da99 100644 --- a/test/test_product.cpp +++ b/test/test_product.cpp @@ -1,4 +1,4 @@ -#include +#include #define DEFINE_BASIC_ITERABLE_COPY_CTOR #define DEFINE_BASIC_ITERABLE_CONST_BEGIN_AND_END @@ -221,6 +221,7 @@ TEST_CASE("product: iterator meets requirements", "[product]") { std::string s{"abc"}; auto c = product(s, s); REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); } template diff --git a/test/test_range.cpp b/test/test_range.cpp index dab748e3..b2b15f98 100644 --- a/test/test_range.cpp +++ b/test/test_range.cpp @@ -1,4 +1,4 @@ -#include "range.hpp" +#include "cppitertools/range.hpp" #include #include @@ -10,6 +10,58 @@ using Vec = const std::vector; using iter::range; +TEST_CASE("range: .start(), .stop(), and .step()", "[range]") { + SECTION("one arg") { + auto r = range(3); + REQUIRE(r.start() == 0); + REQUIRE(r.stop() == 3); + REQUIRE(r.step() == 1); + + // make sure iterators aren't changing the value + auto it = r.begin(); + ++it; + + REQUIRE(r.start() == 0); + REQUIRE(r.stop() == 3); + REQUIRE(r.step() == 1); + } + + SECTION("two args") { + auto r = range(2, 10); + REQUIRE(r.start() == 2); + REQUIRE(r.stop() == 10); + REQUIRE(r.step() == 1); + } + + SECTION("three args") { + auto r = range(-6, 20, 3); + REQUIRE(r.start() == -6); + REQUIRE(r.stop() == 20); + REQUIRE(r.step() == 3); + } + + SECTION("one arg (double)") { + auto r = range(3.5); + REQUIRE(r.start() == 0); + REQUIRE(r.stop() == Approx(3.5)); + REQUIRE(r.step() == Approx(1.0)); + } + + SECTION("two args (double)") { + auto r = range(20.1, 31.7); + REQUIRE(r.start() == Approx(20.1)); + REQUIRE(r.stop() == Approx(31.7)); + REQUIRE(r.step() == Approx(1.0)); + } + + SECTION("three args (double)") { + auto r = range(-6.3, 5.7, 0.1); + REQUIRE(r.start() == Approx(-6.3)); + REQUIRE(r.stop() == Approx(5.7)); + REQUIRE(r.step() == Approx(0.1)); + } +} + TEST_CASE("range: works with only stop", "[range]") { auto r = range(5); Vec v(std::begin(r), std::end(r)); @@ -260,3 +312,74 @@ TEST_CASE("range: iterator meets forward iterator requirements", "[range]") { REQUIRE(itertest::IsForwardIterator::value); REQUIRE(itertest::IsForwardIterator::value); } + +TEST_CASE("range: operator[] simple tests", "[range]") { + SECTION("range(start)") { + auto r = range(4); + REQUIRE(r[0] == 0); + REQUIRE(r[1] == 1); + REQUIRE(r[2] == 2); + REQUIRE(r[3] == 3); + } + SECTION("range(start, stop)") { + auto r = range(10, 14); + REQUIRE(r[0] == 10); + REQUIRE(r[1] == 11); + REQUIRE(r[2] == 12); + REQUIRE(r[3] == 13); + } + SECTION("range(start, stop, step)") { + auto r = range(20, 30, 3); + REQUIRE(r[0] == 20); + REQUIRE(r[1] == 23); + REQUIRE(r[2] == 26); + REQUIRE(r[3] == 29); + } + SECTION("range(start, stop, step) with double") { + auto r = range(50.0, 50.99, 0.2); + REQUIRE(r[0] == Approx(50.0)); + REQUIRE(r[1] == Approx(50.2)); + REQUIRE(r[2] == Approx(50.4)); + REQUIRE(r[3] == Approx(50.6)); + REQUIRE(r[4] == Approx(50.8)); + } +} + +TEMPLATE_TEST_CASE("range: .size() with signed integrals", "[range]", + signed char, short, int, long, long long) { + constexpr TestType N = 5; + constexpr TestType INC = 1; + for (TestType start = -N; start < N; start += INC) { + for (TestType stop = -N; stop < N; stop += INC) { + for (TestType step = -N; step < N; step += INC) { + if (step == 0) { + continue; + } + auto r = range(start, stop, step); + REQUIRE(r.size() + == static_cast( + std::distance(std::begin(r), std::end(r)))); + } + } + } +} + +TEMPLATE_TEST_CASE("range: .size() with unsigned integrals", "[range]", + unsigned char, unsigned short, unsigned int, unsigned long, + unsigned long long) { + constexpr TestType N = 5; + constexpr TestType INC = 1; + for (TestType start = 0; start < N; start += INC) { + for (TestType stop = 0; stop < N; stop += INC) { + for (TestType step = 0; step < N; step += INC) { + if (step == 0) { + continue; + } + auto r = range(start, stop, step); + REQUIRE(r.size() + == static_cast( + std::distance(std::begin(r), std::end(r)))); + } + } + } +} diff --git a/test/test_repeat.cpp b/test/test_repeat.cpp index 0fc62e72..5427909a 100644 --- a/test/test_repeat.cpp +++ b/test/test_repeat.cpp @@ -1,12 +1,10 @@ -#include - -#include "helpers.hpp" - #include +#include #include #include #include "catch.hpp" +#include "helpers.hpp" using iter::repeat; @@ -86,6 +84,22 @@ TEST_CASE("repeat: iterator meets requirements", "[repeat]") { REQUIRE(itertest::IsIterator::value); } +TEST_CASE("repeat: one-argument is reversible", "[repeat]") { + auto r = repeat('c'); + auto it = std::rbegin(r); + (void)(it != std::rend(r)); + + REQUIRE(*it == 'c'); + ++it; + REQUIRE(*it == 'c'); +} + +TEST_CASE("repeat: two-argument is reversible", "[repeat]") { + auto r = repeat('b', 4); + std::string s(std::rbegin(r), std::rend(r)); + REQUIRE(s == "bbbb"); +} + template using ImpT = decltype(repeat(std::declval())); diff --git a/test/test_reversed.cpp b/test/test_reversed.cpp index 530a17fc..10dfe4d9 100644 --- a/test/test_reversed.cpp +++ b/test/test_reversed.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/test/test_slice.cpp b/test/test_slice.cpp index 38386b73..fc5b42fe 100644 --- a/test/test_slice.cpp +++ b/test/test_slice.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -148,9 +148,18 @@ TEST_CASE("slice: with iterable doesn't move or copy elems", "[slice]") { } TEST_CASE("slice: iterator meets requirements", "[slice]") { - std::string s{"abcdef"}; - auto c = slice(s, 1, 3); - REQUIRE(itertest::IsIterator::value); + SECTION("with iterable yielding references") { + std::string s{"abcdef"}; + auto c = slice(s, 1, 3); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + SECTION("with iterable yielding values") { + itertest::InputIterable it{}; + auto c = slice(it, 1, 3); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } } template diff --git a/test/test_sliding_window.cpp b/test/test_sliding_window.cpp index a302e1dc..e031ce75 100644 --- a/test/test_sliding_window.cpp +++ b/test/test_sliding_window.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/test/test_sorted.cpp b/test/test_sorted.cpp index ff151576..ea80decf 100644 --- a/test/test_sorted.cpp +++ b/test/test_sorted.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -46,7 +46,7 @@ TEST_CASE("sorted: const iteration", "[sorted][const]") { REQUIRE(v == vc); } -//FIXME: This test currently fails (STL assertion fails on MSVC with debug library, simple test failure on gcc). The problem is 'sorted' will sort twice, once for non-const and once for const container; the resulting iterators are thus not on the same container (violating domain of == as specified in C++17 [forward.iterators]¶2). Remove [!hide] tag when fixed. +//FIXME: This test currently fails (STL assertion fails on MSVC with debug library, simple test failure on gcc). The problem is 'sorted' will sort twice, once for non-const and once for const container; the resulting iterators are thus not on the same container (violating domain of == as specified in C++17 [forward.iterators]�2). Remove [!hide] tag when fixed. TEST_CASE("sorted: const iterators can be compared to non-const iterators", "[sorted][const][!hide]") { auto s = sorted(Vec{1}); diff --git a/test/test_starmap.cpp b/test/test_starmap.cpp index 4c12b7c1..c8758669 100644 --- a/test/test_starmap.cpp +++ b/test/test_starmap.cpp @@ -1,10 +1,10 @@ -#include - +#include #include "helpers.hpp" #include #include #include +#include #include #include "catch.hpp" @@ -16,6 +16,18 @@ namespace { return d * i; } + int& larger_ref(int& a, int& b) { + return a > b ? a : b; + } + + const int& larger_const_ref(const int& a, const int& b) { + return a > b ? a : b; + } + + int larger(int a, int b) { + return a > b ? a : b; + } + std::string g(const std::string& s, int i, char c) { std::stringstream ss; ss << s << ' ' << i << ' ' << c; @@ -35,16 +47,42 @@ namespace { return a; } }; + + struct Adder { + long operator()(long a, int b) { + return a + b; + } + }; + + struct MoveOnlyAddAndPlus { + private: + // unique_ptr is better for triggering asan than an int if there's a + // dangling reference to the callable + std::unique_ptr add_amount_; + + public: + MoveOnlyAddAndPlus(int v) : add_amount_{std::make_unique(v)} {} + + MoveOnlyAddAndPlus(const MoveOnlyAddAndPlus&) = delete; + MoveOnlyAddAndPlus& operator=(const MoveOnlyAddAndPlus&) = delete; + + MoveOnlyAddAndPlus(MoveOnlyAddAndPlus&&) = default; + MoveOnlyAddAndPlus& operator=(MoveOnlyAddAndPlus&&) = default; + + int operator()(long a, int b) { + return a + b + *add_amount_; + } + }; } TEST_CASE("starmap: works with function pointer and lambda", "[starmap]") { - using Vec = const std::vector; const std::vector> v1 = {{1l, 2}, {3l, 11}, {6l, 7}}; - Vec vc = {2l, 33l, 42l}; + const std::vector greater_vc = {2l, 33l, 42l}; + const std::vector added_vc = {3l, 14l, 13l}; - std::vector v; - SECTION("with function") { - SECTION("Normal call") { + SECTION("with function pointer") { + std::vector v; + SECTION("normal call") { auto sm = starmap(f, v1); v.assign(std::begin(sm), std::end(sm)); } @@ -52,13 +90,54 @@ TEST_CASE("starmap: works with function pointer and lambda", "[starmap]") { auto sm = v1 | starmap(f); v.assign(std::begin(sm), std::end(sm)); } + REQUIRE(v == greater_vc); + } + + SECTION("with callable object") { + std::vector v; + SECTION("normal call") { + auto sm = starmap(Adder{}, v1); + v.assign(std::begin(sm), std::end(sm)); + } + SECTION("pipe") { + auto sm = v1 | starmap(Adder{}); + v.assign(std::begin(sm), std::end(sm)); + } + REQUIRE(v == added_vc); + } + + SECTION("with lvalue callable object") { + std::vector v; + auto adder = Adder{}; + SECTION("normal call") { + auto sm = starmap(adder, v1); + v.assign(std::begin(sm), std::end(sm)); + } + SECTION("pipe") { + auto sm = v1 | starmap(adder); + v.assign(std::begin(sm), std::end(sm)); + } + REQUIRE(v == std::vector{3l, 14l, 13l}); + } + + SECTION("with move-only callable object") { + const std::vector sum_plus_one_vc = {4l, 14l, 13l}; + std::vector v; + SECTION("normal call") { + auto m = starmap(MoveOnlyAddAndPlus{1}, v1); + v.assign(std::begin(m), std::end(m)); + } + SECTION("pipe") { + auto m = v1 | starmap(MoveOnlyAddAndPlus{1}); + v.assign(std::begin(m), std::end(m)); + } } SECTION("with lambda") { auto sm = starmap([](long a, int b) { return a * b; }, v1); - v.assign(std::begin(sm), std::end(sm)); + std::vector v(std::begin(sm), std::end(sm)); + REQUIRE(v == greater_vc); } - REQUIRE(v == vc); } TEST_CASE("starmap: works with pointer to member function", "[starmap]") { @@ -74,7 +153,8 @@ TEST_CASE("starmap: works with pointer to member function", "[starmap]") { TEST_CASE("starmap: vector of pairs const iteration", "[starmap][const]") { using Vec = const std::vector; - const std::vector> v1 = {{1.0, 2}, {3.0, 11}, {6.0, 7}}; + const std::vector> v1 = { + {1.0, 2}, {3.0, 11}, {6.0, 7}}; const auto sm = starmap(Callable{}, v1); std::vector v(std::begin(sm), std::end(sm)); @@ -182,3 +262,36 @@ TEST_CASE( auto sm = starmap(Callable{}, tup); REQUIRE(itertest::IsIterator::value); } + +TEST_CASE("starmap: iterator dereference type matches 'reference' type alias", + "[starmap]") { + std::vector> input; + SECTION("with reference return type") { + auto sm = iter::starmap(larger_ref, input); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + SECTION("with const reference return type") { + auto sm = iter::starmap(larger_const_ref, input); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + SECTION("with value return type") { + auto sm = iter::starmap(larger, input); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } +} + +TEST_CASE("starmap: iterator has correct 'value' type alias", "[starmap]") { + std::vector> input; + SECTION("with reference return type") { + auto sm = iter::starmap(larger_ref, input); + REQUIRE(std::is_same_v); + } + SECTION("with const reference return type") { + auto sm = iter::starmap(larger_const_ref, input); + REQUIRE(std::is_same_v); + } + SECTION("with value return type") { + auto sm = iter::starmap(larger, input); + REQUIRE(std::is_same_v); + } +} diff --git a/test/test_takewhile.cpp b/test/test_takewhile.cpp index 23ad8462..f562b438 100644 --- a/test/test_takewhile.cpp +++ b/test/test_takewhile.cpp @@ -1,6 +1,5 @@ -#include - #include +#include #include #include #include @@ -11,50 +10,49 @@ using iter::takewhile; using Vec = const std::vector; -namespace { - bool under_ten(int i) { - return i < 10; +TEST_CASE("takewhile: handles different callable types", "[takewhile]") { + Vec ns = {1, 3, 4, 20, 2, 4, 6, 8}; + Vec vc = {1, 3, 4}; + std::vector v; + SECTION("with function pointer") { + auto tw = takewhile(less_than_five, ns); + v = Vec(std::begin(tw), std::end(tw)); } - struct UnderTen { - bool operator()(int i) { - return i < 10; - } - }; -} - -TEST_CASE("takewhile: works with lambda, callable, and function pointer", - "[takewhile]") { - Vec ns = {1, 3, 5, 20, 2, 4, 6, 8}; - SECTION("function pointer") { - auto tw = takewhile(under_ten, ns); - Vec v(std::begin(tw), std::end(tw)); - Vec vc = {1, 3, 5}; - REQUIRE(v == vc); + SECTION("with callable object") { + auto tw = takewhile(LessThanValue{5}, ns); + v = Vec(std::begin(tw), std::end(tw)); } - SECTION("callable object") { - std::vector v; - SECTION("Normal call") { - auto tw = takewhile(UnderTen{}, ns); - v.assign(std::begin(tw), std::end(tw)); + SECTION("with lvalue callable object") { + auto lt = LessThanValue{5}; + SECTION("normal call") { + auto tw = takewhile(lt, ns); + v = Vec(std::begin(tw), std::end(tw)); } - - SECTION("Pipe") { - auto tw = ns | takewhile(UnderTen{}); - v.assign(std::begin(tw), std::end(tw)); + SECTION("pipe") { + auto tw = ns | takewhile(lt); + v = Vec(std::begin(tw), std::end(tw)); } + } - Vec vc = {1, 3, 5}; - REQUIRE(v == vc); + SECTION("with move-only callable object") { + SECTION("normal call") { + auto tw = takewhile(MoveOnlyLessThanValue{5}, ns); + v = Vec(std::begin(tw), std::end(tw)); + } + SECTION("pipe") { + auto tw = ns | takewhile(MoveOnlyLessThanValue{5}); + v = Vec(std::begin(tw), std::end(tw)); + } } - SECTION("lambda") { - auto tw = takewhile([](int i) { return i < 10; }, ns); - Vec v(std::begin(tw), std::end(tw)); - Vec vc = {1, 3, 5}; - REQUIRE(v == vc); + SECTION("with lambda") { + auto ltf = [](int i) { return i < 5; }; + auto tw = takewhile(ltf, ns); + v = Vec(std::begin(tw), std::end(tw)); } + REQUIRE(v == vc); } TEST_CASE("takewhile: handles pointer to member", "[takewhile]") { @@ -78,7 +76,7 @@ TEST_CASE("takewhile: handles pointer to member", "[takewhile]") { TEST_CASE("takewhile: supports const iteration", "[takewhile][const]") { Vec ns = {1, 3, 5, 20, 2, 4, 6, 8}; - const auto tw = takewhile(UnderTen{}, ns); + const auto tw = takewhile(LessThanValue{10}, ns); Vec v(std::begin(tw), std::end(tw)); Vec vc = {1, 3, 5}; REQUIRE(v == vc); @@ -86,7 +84,7 @@ TEST_CASE("takewhile: supports const iteration", "[takewhile][const]") { TEST_CASE("takewhile: const iterator and non-const iterator are comparable", "[takewhile][const]") { - auto tw = takewhile(UnderTen{}, Vec{}); + auto tw = takewhile(LessThanValue{10}, Vec{}); const auto& ctw = tw; (void)(std::begin(tw) == std::end(ctw)); } @@ -117,15 +115,20 @@ TEST_CASE("takewhile: identity", "[takewhile]") { TEST_CASE("takewhile: everything passes predicate", "[takewhile]") { Vec ns{1, 2, 3}; - auto tw = takewhile(under_ten, ns); + auto tw = takewhile(less_than_five, ns); Vec v(std::begin(tw), std::end(tw)); Vec vc = {1, 2, 3}; } TEST_CASE("takewhile: empty iterable is empty", "[takewhile]") { Vec ns{}; - auto tw = takewhile(under_ten, ns); - REQUIRE(std::begin(tw) == std::end(tw)); + auto tw = takewhile(less_than_five, ns); + SECTION("normal compare") { + REQUIRE(std::begin(tw) == std::end(tw)); + } + SECTION("reversed compare") { + REQUIRE(std::end(tw) == std::begin(tw)); + } } TEST_CASE( @@ -133,23 +136,33 @@ TEST_CASE( "[takewhile]") { SECTION("First element is only element") { Vec ns = {20}; - auto tw = takewhile(under_ten, ns); - REQUIRE(std::begin(tw) == std::end(tw)); + auto tw = takewhile(less_than_five, ns); + SECTION("normal compare") { + REQUIRE(std::begin(tw) == std::end(tw)); + } + SECTION("reversed compare") { + REQUIRE(std::end(tw) == std::begin(tw)); + } } SECTION("First element followed by elements that pass") { Vec ns = {20, 1, 1}; - auto tw = takewhile(under_ten, ns); - REQUIRE(std::begin(tw) == std::end(tw)); + auto tw = takewhile(less_than_five, ns); + SECTION("normal compare") { + REQUIRE(std::begin(tw) == std::end(tw)); + } + SECTION("reversed compare") { + REQUIRE(std::end(tw) == std::begin(tw)); + } } } TEST_CASE("takewhile: moves rvalues, binds to lvalues", "[takewhile]") { itertest::BasicIterable bi{1, 2}; - takewhile(under_ten, bi); + takewhile(less_than_five, bi); REQUIRE_FALSE(bi.was_moved_from()); - takewhile(under_ten, std::move(bi)); + takewhile(less_than_five, std::move(bi)); REQUIRE(bi.was_moved_from()); } diff --git a/test/test_unique_everseen.cpp b/test/test_unique_everseen.cpp index d70de075..c5fedad3 100644 --- a/test/test_unique_everseen.cpp +++ b/test/test_unique_everseen.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" @@ -85,3 +85,33 @@ TEST_CASE( REQUIRE(itertest::IsMoveConstructibleOnly>::value); REQUIRE(itertest::IsMoveConstructibleOnly>::value); } + +struct IntWrapper { + int n; +}; + +struct IntWrapperHash { + int operator()(const IntWrapper& iw) const { + return iw.n % 10; + } +}; + +struct IntWrapperEq { + int operator()(const IntWrapper& lhs, const IntWrapper& rhs) const { + return lhs.n == rhs.n; + } +}; + +TEST_CASE("unique_everseen: works with custom hash and equality functions", + "[unique_everseen]") { + std::vector iwv = { + {2}, {3}, {4}, {2}, {10}, {2}, {2}, {12}, {10}}; + Vec vc{2, 3, 4, 10, 12}; + + std::vector v; + for (auto&& iw : unique_everseen(iwv, IntWrapperHash{}, IntWrapperEq{})) { + v.push_back(iw.n); + } + + REQUIRE(v == vc); +} diff --git a/test/test_unique_justseen.cpp b/test/test_unique_justseen.cpp index c6671e5f..530ab869 100644 --- a/test/test_unique_justseen.cpp +++ b/test/test_unique_justseen.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" @@ -91,3 +91,58 @@ TEST_CASE( REQUIRE(itertest::IsMoveConstructibleOnly>::value); REQUIRE(itertest::IsMoveConstructibleOnly>::value); } + +struct IntWrapper { + int n; +}; + +struct IntWrapperKey { + int operator()(const IntWrapper& iw) const { + return iw.n; + } +}; + +struct MoveOnlyIntWrapperKey { + MoveOnlyIntWrapperKey(const MoveOnlyIntWrapperKey&) = delete; + MoveOnlyIntWrapperKey& operator=(const MoveOnlyIntWrapperKey&) = delete; + + MoveOnlyIntWrapperKey(MoveOnlyIntWrapperKey&&) = default; + MoveOnlyIntWrapperKey& operator=(MoveOnlyIntWrapperKey&&) = default; + int operator()(const IntWrapper& iw) const { + return iw.n; + } +}; + +TEST_CASE("unique_justseen: works with key function", "[unique_justseen]") { + std::vector iwv = { + {2}, {3}, {4}, {2}, {10}, {2}, {2}, {12}, {10}}; + Vec vc{2, 3, 4, 2, 10, 2, 12, 10}; + + std::vector v; + SECTION("with callable") { + SECTION("Normal call") { + for (auto&& iw : unique_justseen(iwv, IntWrapperKey{})) { + v.push_back(iw.n); + } + } + SECTION("Pipe") { + for (auto&& iw : iwv | unique_justseen(IntWrapperKey{})) { + v.push_back(iw.n); + } + } + } + SECTION("with move-only callable") { + SECTION("Normal call") { + for (auto&& iw : unique_justseen(iwv, MoveOnlyIntWrapperKey{})) { + v.push_back(iw.n); + } + } + SECTION("Pipe") { + for (auto&& iw : iwv | unique_justseen(MoveOnlyIntWrapperKey{})) { + v.push_back(iw.n); + } + } + } + + REQUIRE(v == vc); +} diff --git a/test/test_zip.cpp b/test/test_zip.cpp index 7bf8ff96..87c927f8 100644 --- a/test/test_zip.cpp +++ b/test/test_zip.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" @@ -140,8 +140,10 @@ TEST_CASE("zip: iterator meets requirements", "[zip]") { std::string s{}; auto c = zip(s); REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); auto c2 = zip(s, s); REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); } template diff --git a/test/test_zip_longest.cpp b/test/test_zip_longest.cpp index 4759a412..cdf29269 100644 --- a/test/test_zip_longest.cpp +++ b/test/test_zip_longest.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" diff --git a/unique_everseen.hpp b/unique_everseen.hpp deleted file mode 100644 index 35322ca8..00000000 --- a/unique_everseen.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef ITER_UNIQUE_EVERSEEN_HPP_ -#define ITER_UNIQUE_EVERSEEN_HPP_ - -#include "filter.hpp" -#include "internal/iterbase.hpp" - -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - struct UniqueEverseenFn : Pipeable { - template - auto operator()(Container&& container) const { - using elem_type = impl::iterator_deref; - auto func = [elem_seen = std::unordered_set>()]( - const std::remove_reference_t& e) mutable { - return elem_seen.insert(e).second; - }; - return filter(func, std::forward(container)); - } - }; - } - - constexpr impl::UniqueEverseenFn unique_everseen{}; -} - -#endif diff --git a/unique_justseen.hpp b/unique_justseen.hpp deleted file mode 100644 index 2d63b7ea..00000000 --- a/unique_justseen.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef ITER_UNIQUE_JUSTSEEN_HPP -#define ITER_UNIQUE_JUSTSEEN_HPP - -#include "groupby.hpp" -#include "imap.hpp" - -#include -#include - -namespace iter { - namespace impl { - struct UniqueJustseenFn : Pipeable { - template - auto operator()(Container&& container) const { - // decltype(auto) return type in lambda so reference types are preserved - return imap([](auto&& group) -> decltype( - auto) { return *get_begin(group.second); }, - groupby(std::forward(container))); - } - }; - } - constexpr impl::UniqueJustseenFn unique_justseen{}; -} - -#endif