template<typename T>
class array_ptr : public inline_sequence_base<array_ptr<T>>

A type with “fat” pointer semantics, implemented as a (pointer, length) pair. It can be used across API boundaries as a “type erased” contiguous sequence. It is the Flux-native equivalent of std::span.

All array_ptr s are trivially movable. If T is const then array_ptr is additionally trivially copyable, otherwise it is move-only.

For the purposes of documentation below, the exposition-only concept non_slicing_ptr_convertible is defined as:

template <typename From, typename To>
concept non_slicing_ptr_convertible = std::convertible_to<From (*)[], To (*)[]>;

array_ptr() = default;

Default initializes an empty array_ptr


data() == nullptr

size() == 0

template<contiguous_sequence Seq>
requires see_below
explicit array_ptr(Seq &seq);

Constructs an array_ptr from the contiguous sequence seq.


data() == flux::data(seq)

size() == flux::size(seq)

template<typename U>
requires (!std::same_as<U, T> && non_slicing_ptr_convertible<U, T>)
array_ptr(array_ptr<U> const &other) noexcept;

Implicit conversion constructor from a compatible array_ptr.


data() == other.data()

size() == other.size()

Friend functions:

friend auto operator==(array_ptr lhs, array_ptr rhs) -> bool;

Equivalent to:

lhs.data() == rhs.data() && lhs.size() == rhs.size()

but ensures that the pointer comparison is always well defined.


array_ptr has pointer semantics, and so equality comparison tests the addresses of the pointed-to objects.

If you want to check whether the elements of two array_ptr s compare equal, you can use flux::equal().


template<typename T>
requires std::is_object_v<T>
contiguous_sequence auto empty;

A variable template that names a contiguous sequence of zero T s. Any attempt to read from an empty will result in a runtime error.

is_empty(empty) is vacuously true.


template<std::default_initializable T, typename CharT, typename Traits>
auto from_istream(std::basic_istream<CharT, Traits> &is) -> sequence auto;

Returns a single-pass, read-only sequence which yields successive T s extracted from is using operator>>(). The element type of the returned sequence is T const&.


template<typename CharT, typename Traits>
auto from_istreambuf(std::basic_streambuf<CharT, Traits> *buf) -> sequence auto;
template<typename CharT, typename Traits>
auto from_istreambuf(std::basic_istream<CharT, Traits> &is) -> sequence auto;

Returns a single-pass, read-only sequence which yields successive characters from the given streambuf using std::basic_streambuf::sgetc(). Iteration is complete when the streambuf reaches EOF.

The second overload is equivalent to:



template<std::ranges::viewable_range R>
requires std::ranges::input_range<R>
auto from_range(R &&rng) -> sequence auto;
template<typename R, typename C = std::remove_reference<R> const&>
requires std::viewable_range<C> && std::input_range<C>
auto from_crange(R &&rng) -> sequence auto;


template<typename ElemT>
class generator


template<typename CharT, typename Traits>
auto getlines(std::basic_istream<CharT, Traits> &istream, CharT delim) -> sequence auto;
template<typename CharT, typename Traits>
auto getlines(std::basic_istream<CharT, Traits> &istream) -> sequence auto;


auto ints() -> random_access_sequence auto;
auto ints(distance_t from) -> random_access_sequence auto;
auto ints(distance_t from, distance_t to) -> random_access_sequence auto;


template<typename T>
requires see_below
auto iota(T from) -> multipass_sequence auto;
template<typename T>
requires see_below
auto iota(T from, T to) -> multipass_sequence auto;


template<typename T>
requires std::movable<std::decay_t<T>>
auto repeat(T &&obj) -> infinite_sequence auto;
template<typename T>
requires std::movable<std::decay_t<T>>
auto repeat(T &&obj, std::integral auto count) -> random_access_sequence auto;

Returns a sequence which yields a const reference to obj endlessly (for the first overload) or exactly count times (for the second overload).

For both overloads, the returned sequence is always a random_access_sequence. For the second overload it additionally models sized_sequence and bounded_sequence.


In order to provide random-access functionality, cursors for repeat sequences keep a size_t count of how many times they have been incremented. For very long-running programs using the infinite version of repeat() it may be possible to overflow this counter. (Assuming 1000 iterations per second, this would take approximately 49 days on a machine with a 32-bit size_t, or around 500 million years on a 64-bit machine.)

While this won’t cause undefined behaviour, calling distance() with cursors that have rolled over may give incorrect results, and may result in a runtime error in debug mode if the result cannot be represented as a distance_t.

  • obj – A movable object which will be stored in the returned sequence object

  • count – If provided, a non-negative value to indicate the size of the returned sequence. If not provided, the returned sequence will be infinite.


A sequence which repeatedly yields a const reference to obj


// flux::repeat(val) is a random-access sequence which endlessly repeats
// the given value
auto seq = flux::repeat(3);

auto cursor = flux::first(seq);
assert(flux::read_at(seq, cursor) == 3);
// fast-forward the cursor a lot...
cursor = flux::next(seq, cursor, 1'000'000);
assert(flux::read_at(seq, cursor) == 3); // still returning 3!

// We could use the take adaptor to make a repeat sequence finite...
auto taken = flux::take(seq, 5);
assert(flux::equal(taken, std::array{3, 3, 3, 3, 3}));

// ...but it's easier to use repeat(val, count) instead
auto police = flux::repeat("hello"sv, 3);
assert(flux::equal(police, std::array{"hello", "hello", "hello"}));
See also:


template<typename T>
requires std::move_constructible<T>
auto single(T &&obj) -> contiguous_sequence auto;


template<typename Func, typename Seed>
requires see_below
auto unfold(Func func, Seed &&seed) -> infinite_sequence auto;

Generates an infinite single-pass sequence by repeatedly invoking the unary function func, starting with the given seed.

Whereas fold() takes a sequence and a function and produces a single value, unfold() does the opposite: it takes a function and a single value an produces a sequence.

Let R be std::decay_t<std::invoke_result_t<Func&, Seed>>. The sequence object contains variable state of type R, which is initialised from seed. At every call to inc(), the internal state is updated as if by:

state = std::invoke(func, std::move(state));

A call to read_at() returns a read-only reference to the internal state, with type R const&.


As the provided function can potentially be called again and again “forever”, it’s important to make sure that this can’t cause undefined behaviour, for example by signed integer overflow – perhaps by using unsigned ints instead, or by ensuring that iteration is terminated before this occurs.


Let R be std::decay_t<std::invoke_result_t<Func&, Seed>>. Then the expression in the requires clause is equivalent to:

std::constructible_from<R, Seed> &&
std::invocable<Func&, R> &&
std::assignable_from<R&, std::invoke_result_t<Func&, R>>
  • func – A unary callable with a signature compatible with R(R)

  • seed – The initial seed value. Must be convertible to the result type of func.


An infinite single-pass sequence generated by repeated invocations of func, starting with the seed value.


// We can use unfold() with the identity function to do the equivalent of
// flux::repeat():
auto repeated = flux::unfold(std::identity{}, "hello"sv).take(3);

assert(flux::equal(repeated, std::array{"hello"sv, "hello"sv, "hello"sv}));

// We can combine unfold() with a mutable lambda to do more sophisticated
// things, like generating the Fibonacci sequence:
auto fibs = flux::unfold([next = 1u](unsigned cur) mutable {
    return std::exchange(next, cur + next);
}, 0u);

                   std::array<unsigned, 10>{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}));
See also: