diff --git a/.bazelversion b/.bazelversion
index 643916c0..f4abeaad 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-7.3.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/README.md b/README.md
index 7b43c218..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.
@@ -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
@@ -193,46 +191,50 @@ would expect them to behave:
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,46 +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 structs with
-the index in `.first`, and the element in `.second`, 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';
}
```
@@ -300,36 +302,38 @@ 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.
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 << ' ';
}
```
@@ -341,29 +345,30 @@ This **does not** work with the pipe syntax.
```c++
vector v { /* ... */ };
for (auto&& w : unique_everseen(v, WidgetHash{}, WidgetEq{})) {
- cout << w.name() << ' ';
+ 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';
+for (auto&& p : unique_justseen(v, [] (const Person& p) { return p.name; })) {
+ cout << p.name() << ' ' << p.age() << '\n';
}
```
@@ -372,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';
}
```
@@ -386,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';
}
```
@@ -397,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;
+ }
}
```
@@ -416,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';
}
```
@@ -448,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';
}
```
@@ -460,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
@@ -494,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;
};
```
@@ -516,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';
}
```
@@ -556,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};
@@ -567,16 +583,16 @@ 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
@@ -586,23 +602,24 @@ element in each tuple yielded.
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}
@@ -612,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';
}
```
@@ -640,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.
@@ -652,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';
}
```
@@ -662,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.
@@ -674,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';
}
```
@@ -692,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()`
@@ -724,7 +739,7 @@ Iterates over elements of a sequence in reverse order.
```c++
for (auto&& i : reversed(a)) {
- cout << i << '\n';
+ cout << i << '\n';
}
```
@@ -735,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).
@@ -764,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
@@ -826,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}
@@ -882,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/cppitertools/chain.hpp b/cppitertools/chain.hpp
index 76759eeb..05557387 100644
--- a/cppitertools/chain.hpp
+++ b/cppitertools/chain.hpp
@@ -142,8 +142,8 @@ class iter::impl::Chained {
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)
diff --git a/cppitertools/dropwhile.hpp b/cppitertools/dropwhile.hpp
index e751bfe3..86942826 100644
--- a/cppitertools/dropwhile.hpp
+++ b/cppitertools/dropwhile.hpp
@@ -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/cppitertools/filter.hpp b/cppitertools/filter.hpp
index 1e069070..d743d8b1 100644
--- a/cppitertools/filter.hpp
+++ b/cppitertools/filter.hpp
@@ -29,6 +29,8 @@ namespace iter {
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/cppitertools/filterfalse.hpp b/cppitertools/filterfalse.hpp
index 4056c402..269724dd 100644
--- a/cppitertools/filterfalse.hpp
+++ b/cppitertools/filterfalse.hpp
@@ -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/cppitertools/groupby.hpp b/cppitertools/groupby.hpp
index 6d7453f9..1c73f9f6 100644
--- a/cppitertools/groupby.hpp
+++ b/cppitertools/groupby.hpp
@@ -35,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;
@@ -177,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
@@ -191,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() {
@@ -203,7 +206,9 @@ class iter::impl::GroupProducer {
// move-constructible, non-copy-constructible, non-assignable
Group(Group&& other) noexcept
- : owner_(other.owner_), key_{other.key_}, completed{other.completed} {
+ : owner_(other.owner_),
+ key_{std::forward>(other.key_)},
+ completed{other.completed} {
other.completed = true;
}
diff --git a/cppitertools/imap.hpp b/cppitertools/imap.hpp
index 80b8cc3d..21ca2e81 100644
--- a/cppitertools/imap.hpp
+++ b/cppitertools/imap.hpp
@@ -16,7 +16,8 @@ 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();
};
diff --git a/cppitertools/internal/iteratoriterator.hpp b/cppitertools/internal/iteratoriterator.hpp
index 3993a4d0..0ab23bad 100644
--- a/cppitertools/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/cppitertools/internal/iterbase.hpp b/cppitertools/internal/iterbase.hpp
index 6f654828..e9627079 100644
--- a/cppitertools/internal/iterbase.hpp
+++ b/cppitertools/internal/iterbase.hpp
@@ -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 {};
@@ -214,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
@@ -270,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
@@ -282,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_;
@@ -353,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));
}
@@ -375,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));
}
};
@@ -400,10 +405,15 @@ namespace iter {
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 F{}(std::forward(container), std::move(stored_arg));
+ }
+
+ template
+ auto operator()(Container&& container) const& {
return F{}(std::forward(container), stored_arg);
}
};
@@ -433,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/cppitertools/takewhile.hpp b/cppitertools/takewhile.hpp
index 338e43aa..3a351d0c 100644
--- a/cppitertools/takewhile.hpp
+++ b/cppitertools/takewhile.hpp
@@ -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_justseen.hpp b/cppitertools/unique_justseen.hpp
index c4ca47f5..e86d20ba 100644
--- a/cppitertools/unique_justseen.hpp
+++ b/cppitertools/unique_justseen.hpp
@@ -9,9 +9,11 @@
namespace iter {
namespace impl {
- struct UniqueJustseenFn : PipeableAndBindOptionalSecond {
+ struct UniqueJustseenFn
+ : PipeableAndBindOptionalSecond {
public:
- using PipeableAndBindOptionalSecond::operator();
+ using PipeableAndBindOptionalSecond::
+ operator();
template
auto operator()(Container&& container, KeyFunc key_fn) const {
// decltype(auto) return type in lambda so reference types are preserved
@@ -19,7 +21,7 @@ namespace iter {
[](auto&& group) -> decltype(auto) {
return *get_begin(group.second);
},
- groupby(std::forward(container), key_fn));
+ groupby(std::forward(container), std::move(key_fn)));
}
};
}
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/helpers.hpp b/test/helpers.hpp
index 80167980..f94250f3 100644
--- a/test/helpers.hpp
+++ b/test/helpers.hpp
@@ -3,6 +3,7 @@
#include
#include
+#include
#include
#include
#include
@@ -414,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_dropwhile.cpp b/test/test_dropwhile.cpp
index 2a41a671..077bfd73 100644
--- a/test/test_dropwhile.cpp
+++ b/test/test_dropwhile.cpp
@@ -1,29 +1,58 @@
#include
-
-#include "helpers.hpp"
-
#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_filter.cpp b/test/test_filter.cpp
index f160785b..445476c8 100644
--- a/test/test_filter.cpp
+++ b/test/test_filter.cpp
@@ -1,56 +1,58 @@
#include
-
-#include "helpers.hpp"
-
#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 93d50d6a..ad79646a 100644
--- a/test/test_filterfalse.cpp
+++ b/test/test_filterfalse.cpp
@@ -1,63 +1,58 @@
#include
-
-#include "helpers.hpp"
-
#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 6ce9d479..02798f39 100644
--- a/test/test_groupby.cpp
+++ b/test/test_groupby.cpp
@@ -3,6 +3,7 @@
#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 40a9d3e3..cfda992b 100644
--- a/test/test_imap.cpp
+++ b/test/test_imap.cpp
@@ -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_starmap.cpp b/test/test_starmap.cpp
index e3958c84..c8758669 100644
--- a/test/test_starmap.cpp
+++ b/test/test_starmap.cpp
@@ -47,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));
}
@@ -64,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]") {
diff --git a/test/test_takewhile.cpp b/test/test_takewhile.cpp
index e3dade73..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_justseen.cpp b/test/test_unique_justseen.cpp
index 6147b477..530ab869 100644
--- a/test/test_unique_justseen.cpp
+++ b/test/test_unique_justseen.cpp
@@ -102,21 +102,45 @@ struct IntWrapperKey {
}
};
-TEST_CASE("unique_justseen: works with key function",
- "[unique_justseen]") {
+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("Normal call") {
- for (auto&& iw : unique_justseen(iwv, IntWrapperKey{})) {
- v.push_back(iw.n);
+ 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("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);
+ }
}
}