Created
June 23, 2024 08:55
-
-
Save lephe/df76db492fe8a1c3afcdeb3a2b3b86c3 to your computer and use it in GitHub Desktop.
A C++20 range adaptor for generating the series of partial folds of range over a function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* http://silent-tower.net/projects/cpp-iterfold | |
g++ iterfold-gist.cc -o iterfold-gist -O2 -Wall -Wextra -std=c++23 | |
(generator and range adaptor closure require c++23, otherwise c++20 is fine) | |
(generator requires g++/libstdc++ 14 at time of writing) */ | |
#include <iterator> | |
#include <functional> | |
#include <ranges> | |
/*** iterfold implementation ***/ | |
/* An iterator adaptor that reads values from an input iterator of type I until | |
a sentinel of type S, and generates successive folded values of type T over | |
a function of type F. The adaptor produces an input iterator. */ | |
template<std::input_iterator I, typename S, typename T, typename F> | |
struct iterfold_iterator { | |
constexpr iterfold_iterator(I it, S sentinel, T init, F f): | |
it {it}, acc {init}, f {f}, sentinel {sentinel} { | |
if(it != sentinel) | |
acc = f(acc, *it); | |
} | |
constexpr iterfold_iterator &operator++() { | |
++it; | |
if(it != sentinel) | |
acc = f(acc, *it); | |
return *this; | |
} | |
constexpr iterfold_iterator operator++(int) { | |
iterfold_iterator tmp(*this); | |
operator++(); | |
return tmp; | |
} | |
T const &operator*() const { | |
return acc; | |
} | |
/* Comparison with sentinel for range logic */ | |
bool operator==(S const &other) const { | |
return it == other; | |
} | |
private: | |
I it; | |
T acc; | |
F f; | |
public: | |
S sentinel; | |
}; | |
template<std::input_iterator I, typename S, typename T, typename F> | |
struct std::iterator_traits<iterfold_iterator<I, S, T, F>> { | |
/* Distance is measured using the original iterator */ | |
using difference_type = std::iterator_traits<I>::difference_type; | |
/* But values are of type T */ | |
using value_type = T; | |
using pointer = T *; | |
using reference = T &; | |
/* This is exclusively an input iterator */ | |
using iterator_category = std::input_iterator_tag; | |
}; | |
/* Range object satisfying std::ranges:range by supplying begin() and end(). */ | |
template<std::input_iterator I, typename S, typename T, typename F> | |
struct iterfold_range: | |
public std::ranges::view_interface<iterfold_range<I, S, T, F>> { | |
iterfold_range(I it, S sentinel, T init, F f): | |
adaptor {it, sentinel, init, f} {} | |
auto begin() const { | |
return adaptor; | |
} | |
S end() const { | |
return adaptor.sentinel; | |
} | |
private: | |
iterfold_iterator<I, S, T, F> adaptor; | |
}; | |
/* "View" constructor taking a range instead of an it/sentinel pair. */ | |
auto iterfold_view(std::ranges::input_range auto r, auto init, auto f) { | |
return iterfold_range(std::ranges::begin(r), std::ranges::end(r), init, f); | |
} | |
/* Range adaptor closure for iterfold; this object stores all parameters except | |
the range, and is used for pipe notation: `r | iterfold(init, f)`. (This | |
mechanism is only available in C++23.) */ | |
template<typename T, typename F> | |
struct iterfold: std::ranges::range_adaptor_closure<iterfold<T, F>> { | |
iterfold(T init, F f): init {init}, f {f} {} | |
constexpr auto operator()(std::ranges::input_range auto &&r) const { | |
return iterfold_view(r, init, f); | |
} | |
private: | |
T init; | |
F f; | |
}; | |
/*** equivalent generator ***/ | |
#include <generator> | |
template<typename T> | |
std::generator<T> iterfold_generator(std::ranges::range auto r, T acc, auto f) | |
{ | |
for(auto value: r) | |
co_yield (acc = f(acc, value)); | |
} | |
__attribute__((noinline)) | |
void use(int i) { | |
printf("%d,", i); | |
} | |
void asm_adaptor(void) | |
{ | |
auto const v = {1, 2, 3, 4, 5}; | |
for(int i: v | iterfold(0, std::plus<int>())) | |
use(i); | |
printf("\n"); | |
} | |
void asm_generator(void) | |
{ | |
auto const v = {1, 2, 3, 4, 5}; | |
for(int i: iterfold_generator(v, 0, std::plus<int>())) | |
use(i); | |
printf("\n"); | |
} | |
/*** basic test code ***/ | |
using I = std::vector<int>::iterator; | |
using FI = iterfold_iterator<I, I, double, std::function<double(double,int)>>; | |
static_assert(std::input_iterator<FI>); | |
using FR = iterfold_range<I, I, double, std::function<double(double,int)>>; | |
static_assert(std::ranges::input_range<FR>); | |
static_assert(std::ranges::viewable_range<FR>); | |
int main(void) | |
{ | |
auto const v = {1, 2, 3, 4, 5}; | |
for(auto ad = iterfold_iterator(v.begin(), v.end(), 0, std::plus<int>()); ad != ad.sentinel; ++ad) | |
printf("%d,", *ad); | |
printf("\n"); | |
for(int n: iterfold_range(v.begin(), v.end(), 0, std::plus<int>())) | |
printf("%d,", n); | |
printf("\n"); | |
for(int n: iterfold_view(v, 0, std::plus<int>())) | |
printf("%d,", n); | |
printf("\n"); | |
for(int i: v | iterfold(0, std::plus<int>())) | |
printf("%d,", i); | |
printf("\n"); | |
for(int i: v | iterfold(0, std::plus<int>()) | |
| std::views::filter([](int n){ return n % 2 == 1; }) | |
| iterfold(0, std::plus<int>())) | |
printf("%d,", i); | |
printf("\n"); | |
asm_adaptor(); | |
asm_generator(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment