Skip to content

Instantly share code, notes, and snippets.

@lephe
Created June 23, 2024 08:55
Show Gist options
  • Save lephe/df76db492fe8a1c3afcdeb3a2b3b86c3 to your computer and use it in GitHub Desktop.
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.
/* 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