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/BUILD b/BUILD index bab14566..f2fab7df 100644 --- a/BUILD +++ b/BUILD @@ -1,43 +1,43 @@ cc_library( name = "cppitertools", hdrs = [ - "accumulate.hpp", - "batched.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 index f4f7eaeb..c7eb98ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,23 +1,6 @@ cmake_minimum_required(VERSION 3.12) project(cppitertools VERSION 2.0) -if(NOT DEFINED CACHE{cppitertools_INSTALL_CMAKE_DIR}) - message(WARNING [[ -The default value of cppitertools_INSTALL_CMAKE_DIR changed recently, from - "share/cppitertools/cmake" -to - "share" -in order to behave better with existing CMake practice. - -In order to get the previous behavior, pass - -Dcppitertools_INSTALL_CMAKE_DIR=share/cppitertools/cmake -to the CMake invocation; in order to get the new behavior without the warning, -pass - -Dcppitertools_INSTALL_CMAKE_DIR=share -explicitly. -]]) -endif() - # 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") @@ -28,7 +11,7 @@ add_library(cppitertools::cppitertools ALIAS cppitertools) target_include_directories(cppitertools INTERFACE $ - $) + $) # require C++17 target_compile_features(cppitertools INTERFACE cxx_std_17) @@ -46,8 +29,8 @@ install( EXPORT cppitertools-targets) install( - DIRECTORY . - DESTINATION ${cppitertools_INSTALL_INCLUDE_DIR}/cppitertools) + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cppitertools + DESTINATION ${cppitertools_INSTALL_INCLUDE_DIR}) install( EXPORT cppitertools-targets 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 a7f6e5ca..740dacbd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ - - 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*: Everything is inside the `iter` namespace. @@ -13,7 +11,7 @@ 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://travis-ci.com/ryanhaining/cppitertools) | gcc-7 gcc-8 gcc-9 clang-5.0 clang-6.0 clang-7 clang-8 clang-9 +[![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 @@ -44,7 +42,7 @@ Status | Compilers [chunked](#chunked)
[batched](#batched)
-##### Combinatoric fuctions +##### Combinatorial functions [product](#product)
[combinations](#combinations)
[combinations\_with\_replacement](#combinations_with_replacement)
@@ -55,11 +53,10 @@ Status | Compilers 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 @@ -86,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, @@ -110,7 +107,7 @@ 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 +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 @@ -125,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 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 @@ -187,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'; } ``` @@ -240,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'; } ``` @@ -253,45 +256,45 @@ recomputed at each step to avoid accumulating floating point inaccuracies 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. + - `.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'; } ``` @@ -299,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'; } ``` @@ -350,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'; } ``` @@ -364,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'; } ``` @@ -375,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; + } } ``` @@ -394,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'; } ``` @@ -426,9 +457,10 @@ 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'; } ``` @@ -438,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 @@ -472,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; }; ``` @@ -494,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'; } ``` @@ -534,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}; @@ -545,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} @@ -590,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'; } ``` @@ -618,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. @@ -630,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'; } ``` @@ -640,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. @@ -652,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'; } ``` @@ -670,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()` @@ -702,7 +739,7 @@ Iterates over elements of a sequence in reverse order. ```c++ for (auto&& i : reversed(a)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -713,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). @@ -742,60 +780,65 @@ 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 +------- 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. 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'; + for (auto&& i : sec) { + cout << i << ' '; + } + cout << '\n'; } ``` The above prints: + ``` 1 2 3 4 5 @@ -804,54 +847,60 @@ The above prints: ``` 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} @@ -860,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/conanfile.py b/conanfile.py index b47cbab4..114b620f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,35 +1,44 @@ -from conans import ConanFile, CMake - -import os +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps class CppIterTools(ConanFile): - name = "cppitertools" - version = "2.0" - author = "Ryan Haining " - homepage = "https://github.com/ryanhaining/cppitertools" + name = 'cppitertools' + version = '3.0' + author = 'Ryan Haining ' + homepage = 'https://github.com/ryanhaining/cppitertools' url = homepage - topics = ("conan", "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" - generators = "cmake", "cmake_find_package", "cmake_paths" - exports = "LICENSE.md" - - exports_sources = list() - for file in os.listdir("."): - if file.endswith(".hpp"): - exports_sources.append(str(file)) - print("found files: " + str(exports_sources)) - exports_sources = tuple(exports_sources) + \ - ("internal/*", "CMakeLists.txt", "cmake/dummy-config.cmake.in") - no_copy_source = True + 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' - def package(self): + 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_id(self): - self.info.header_only() + 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/batched.hpp b/cppitertools/batched.hpp similarity index 98% rename from batched.hpp rename to cppitertools/batched.hpp index e5eadf13..f3d00ece 100644 --- a/batched.hpp +++ b/cppitertools/batched.hpp @@ -20,7 +20,7 @@ namespace iter { using BatchedFn = IterToolFnBindSizeTSecond; } - constexpr impl::BatchedFn batched{}; + inline constexpr impl::BatchedFn batched{}; } template diff --git a/chain.hpp b/cppitertools/chain.hpp similarity index 89% rename from chain.hpp rename to cppitertools/chain.hpp index 23d1e054..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 @@ -43,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; @@ -76,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...}}; @@ -96,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>; }; @@ -119,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) @@ -141,7 +155,7 @@ class iter::impl::Chained { return IterData::derefers[index_](iters_); } - decltype(auto) operator-> () { + decltype(auto) operator->() { return IterData::arrowers[index_](iters_); } @@ -157,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); } }; @@ -327,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 98% rename from chunked.hpp rename to cppitertools/chunked.hpp index 1d03b6e1..13fa652f 100644 --- a/chunked.hpp +++ b/cppitertools/chunked.hpp @@ -20,7 +20,7 @@ namespace iter { using ChunkedFn = IterToolFnBindSizeTSecond; } - constexpr impl::ChunkedFn chunked{}; + inline constexpr impl::ChunkedFn chunked{}; } template 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 100% rename from count.hpp rename to cppitertools/count.hpp 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 97% rename from dropwhile.hpp rename to cppitertools/dropwhile.hpp index dc1874b0..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; diff --git a/enumerate.hpp b/cppitertools/enumerate.hpp similarity index 98% rename from enumerate.hpp rename to cppitertools/enumerate.hpp index e2dda12b..5d59feab 100644 --- a/enumerate.hpp +++ b/cppitertools/enumerate.hpp @@ -33,7 +33,7 @@ namespace iter { using EnumerateFn = IterToolFnOptionalBindSecond; } - constexpr impl::EnumerateFn enumerate{}; + inline constexpr impl::EnumerateFn enumerate{}; } namespace std { diff --git a/filter.hpp b/cppitertools/filter.hpp similarity index 93% rename from filter.hpp rename to cppitertools/filter.hpp index 2a632be2..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; @@ -84,8 +86,8 @@ class iter::impl::Filtered { 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) 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/imap.hpp b/cppitertools/imap.hpp similarity index 84% rename from imap.hpp rename to cppitertools/imap.hpp index 1e84faf0..21ca2e81 100644 --- a/imap.hpp +++ b/cppitertools/imap.hpp @@ -16,12 +16,13 @@ namespace iter { // See #66 -> StarMapper(containers)...))> { - return starmap(map_func, zip(std::forward(containers)...)); + return starmap( + std::move(map_func), zip(std::forward(containers)...)); } using PipeableAndBindFirst::operator(); }; } - constexpr impl::IMapFn imap{}; + 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 100% rename from internal/iterator_wrapper.hpp rename to cppitertools/internal/iterator_wrapper.hpp 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 80% rename from internal/iterbase.hpp rename to cppitertools/internal/iterbase.hpp index 57cc1b78..e9627079 100644 --- a/internal/iterbase.hpp +++ b/cppitertools/internal/iterbase.hpp @@ -95,8 +95,8 @@ namespace iter { // 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 @@ -117,7 +117,7 @@ namespace iter { template using iterator_traits_deref = - std::remove_reference_t>; + std::remove_cv_t>>; template struct IsIterable : std::false_type {}; @@ -129,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 { @@ -207,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 @@ -263,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 @@ -275,7 +273,7 @@ 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_; @@ -346,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)); } @@ -368,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)); } }; @@ -384,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 @@ -395,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 100% rename from itertools.hpp rename to cppitertools/itertools.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 98% rename from product.hpp rename to cppitertools/product.hpp index d7bc61e9..7de5727a 100644 --- a/product.hpp +++ b/cppitertools/product.hpp @@ -98,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) @@ -148,7 +148,7 @@ class iter::impl::Productor { return {(*std::get(iters_))...}; } - auto operator-> () -> ArrowProxy { + auto operator->() -> ArrowProxy { return {**this}; } }; diff --git a/range.hpp b/cppitertools/range.hpp similarity index 100% rename from range.hpp rename to cppitertools/range.hpp 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 98% rename from sliding_window.hpp rename to cppitertools/sliding_window.hpp index d4ab6cbc..01c348ba 100644 --- a/sliding_window.hpp +++ b/cppitertools/sliding_window.hpp @@ -16,7 +16,7 @@ namespace iter { class WindowSlider; using SlidingWindowFn = IterToolFnBindSizeTSecond; } - constexpr impl::SlidingWindowFn sliding_window{}; + inline constexpr impl::SlidingWindowFn sliding_window{}; } template 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 97% rename from takewhile.hpp rename to cppitertools/takewhile.hpp index a04d86b2..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; 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 97% rename from zip.hpp rename to cppitertools/zip.hpp index abadf45c..98bfd3c4 100644 --- a/zip.hpp +++ b/cppitertools/zip.hpp @@ -56,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)) {} @@ -91,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 100% rename from zip_longest.hpp rename to cppitertools/zip_longest.hpp diff --git a/examples/SConstruct b/examples/SConstruct index 5f9110c9..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='..', 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 index c63c05b3..1dfa6abf 100644 --- a/examples/batched_examples.cpp +++ b/examples/batched_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include 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/test/BUILD b/test/BUILD index 9ceefecd..cc9aefe6 100644 --- a/test/BUILD +++ b/test/BUILD @@ -37,10 +37,13 @@ progs = [ "helpers", ] +SANITIZE = "-fsanitize=address,undefined" + cc_library( name = "test_main", srcs = ["test_main.cpp", "catch.hpp"], - copts = ["-std=c++17", "-g"] + copts = [SANITIZE, "-Wall", "-Wextra", "-std=c++17", "-g"], + linkopts = [SANITIZE], ) itertools_tests(progs) diff --git a/test/download_catch.sh b/test/download_catch.sh index 487e1233..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.6.0/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 aa80cd3c..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 @@ -208,6 +208,10 @@ namespace itertest { template struct IsIterator : std::false_type {}; + template + struct ReferenceMatchesDeref + : std::is_same())> {}; + template struct IsIterator())), // copyctor @@ -216,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 {}; @@ -411,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/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 index bdd5e2a3..098acecc 100644 --- a/test/test_batched.cpp +++ b/test/test_batched.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/test/test_chain.cpp b/test/test_chain.cpp index b4b77cf2..29a81512 100644 --- a/test/test_chain.cpp +++ b/test/test_chain.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" #include @@ -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", 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 8998f1eb..d5248ac2 100644 --- a/test/test_count.cpp +++ b/test/test_count.cpp @@ -1,4 +1,4 @@ -#include +#include #include "helpers.hpp" #include 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 2b85c607..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)); + } - public: - LessThanValue(int v) : compare_val(v) {} + SECTION("with callable object") { + auto d = dropwhile(LessThanValue{5}, ns); + v = Vec(std::begin(d), std::end(d)); + } - bool operator()(int i) { - return i < this->compare_val; + 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)); } - }; + } + + 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]") { @@ -147,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 b10dd5be..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]") { 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 0069da14..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,14 +98,53 @@ 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") { + 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("with lambda") { for (auto&& gb : groupby(vec, [](const std::string& s) { return s.size(); })) { keys.push_back(gb.first); 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 c8783f32..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 diff --git a/test/test_mixed.cpp b/test/test_mixed.cpp index e9b92d34..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; @@ -256,3 +255,23 @@ TEST_CASE( 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 4a25211a..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 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 903f6a9f..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,14 +115,14 @@ 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); + auto tw = takewhile(less_than_five, ns); SECTION("normal compare") { REQUIRE(std::begin(tw) == std::end(tw)); } @@ -138,7 +136,7 @@ TEST_CASE( "[takewhile]") { SECTION("First element is only element") { Vec ns = {20}; - auto tw = takewhile(under_ten, ns); + auto tw = takewhile(less_than_five, ns); SECTION("normal compare") { REQUIRE(std::begin(tw) == std::end(tw)); } @@ -149,7 +147,7 @@ TEST_CASE( SECTION("First element followed by elements that pass") { Vec ns = {20, 1, 1}; - auto tw = takewhile(under_ten, ns); + auto tw = takewhile(less_than_five, ns); SECTION("normal compare") { REQUIRE(std::begin(tw) == std::end(tw)); } @@ -161,10 +159,10 @@ TEST_CASE( 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