std::ranges::generate(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

✨ std::ranges::generate ✨

okay so this page lives in the C++ Standard Library bestie, and honestly it's one of those little gems you're gonna use all the time and never think about. it's std::ranges::generate and it's here to fill your range with vibes (and by vibes i mean whatever your generator function spits out) πŸŽ€


🌸 NAME 🌸

std::ranges::generate β€” literally std::ranges::generate. that's the whole name section. it ate and left no crumbs πŸ’…


πŸ¦‹ Synopsis πŸ¦‹

the header it lives in

Defined in header <algorithm>

Call signature πŸ“œ

template< std::input_or_output_iterator O, std::sentinel_for<O> S,
          std::copy_constructible F >
    requires std::invocable<F&> && std::indirectly_writable<O, std::invoke_result_t<F&>>
constexpr O generate( O first, S last, F gen );
(1) (since C++20)
template< class R, std::copy_constructible F >
    requires std::invocable<F&> && ranges::output_range<R, std::invoke_result_t<F&>>
constexpr ranges::borrowed_iterator_t<R> generate( R&& r, F gen );
(2) (since C++20)

so what do these two actually do πŸ‘€

  1. assigns the result of successive invocations of the function object gen to each element in the range [first, last). yes bestie, every single element gets a fresh call. no reusing, no recycling, fresh out the oven πŸ”₯
  2. same as (1), but uses r as the range, as if using ranges::begin(r) as first and ranges::end(r) as last. same energy, less typing 🫢

the niebloid lore 🧚

the function-like entities described on this page are niebloids, that is:

  • explicit template argument lists cannot be specified when calling any of them. nope. not allowed. don't try it πŸ’€
  • none of them are visible to argument-dependent lookup.
  • when any of them are found by normal unqualified lookup as the name to the left of the function-call operator, argument-dependent lookup is inhibited.

in practice, they may be implemented as function objects, or with special compiler extensions. either way it's giving "trust the process" ✨


πŸ’– Parameters πŸ’–

  • first, last β€” the range of elements to modify. this is the glow-up zone πŸ’…
  • r β€” the range of elements to modify. same zone, just handed over as a range instead of two iterators πŸŽ€
  • gen β€” the generator function object. the main character of this operation, fr fr πŸ‘‘

⭐ Return value ⭐

an output iterator that compares equal to last. so you always know where you ended up 🧭


🌈 Complexity 🌈

exactly ranges::distance(first, last) invocations of gen() and that many assignments. one call, one write, per element. no cap, no extra credit 😌


πŸ‘‘ Possible implementation πŸ‘‘

this is the vibe, in code form:

struct generate_fn
{
    template<std::input_or_output_iterator O, std::sentinel_for<O> S,
             std::copy_constructible F>
    requires std::invocable<F&> && std::indirectly_writable<O, std::invoke_result_t<F&>>
    constexpr O operator()(O first, S last, F gen) const
    {
        for (; first != last; *first = std::invoke(gen), ++first)
        {}
        return first;
    }

    template<class R, std::copy_constructible F>
    requires std::invocable<F&> && ranges::output_range<R, std::invoke_result_t<F&>>
    constexpr ranges::borrowed_iterator_t<R> operator()(R&& r, F gen) const
    {
        return (*this)(ranges::begin(r), ranges::end(r), std::move(gen));
    }
};

inline constexpr generate_fn generate {};

look at that comma operator in the for loop doing the absolute most. iconic 😭


πŸ“ Example πŸ“

// Run this code
#include <algorithm>
#include <array>
#include <iostream>
#include <random>
#include <string_view>

auto dice()
{
    static std::uniform_int_distribution<int> distr {1, 6};
    static std::random_device device;
    static std::mt19937 engine {device()};
    return distr(engine);
}

void iota(auto& v, int n)
{
    std::ranges::generate(v, [&n]() mutable { return n++; });
}

void print(std::string_view comment, const auto& v)
{
    for (std::cout << comment; int i : v)
        std::cout << i << ' ';
    std::cout << '\n';
}

int main()
{
    std::array<int, 8> v;

    std::ranges::generate(v.begin(), v.end(), dice);
    print("dice: ", v);
    std::ranges::generate(v, dice);
    print("dice: ", v);

    iota(v, 1);
    print("iota: ", v);
}

Possible output: 🎲

dice: 4 3 1 6 6 4 5 5
dice: 4 2 5 3 6 2 6 2
iota: 1 2 3 4 5 6 7 8

(yes it's random, your dice rolls will be different, that's the whole point bestie πŸ™ˆ)


πŸ”₯ See also πŸ”₯

  • ranges::generate_n (C++20) (niebloid) β€” saves the result of N applications of a function
  • ranges::fill (C++20) (niebloid) β€” assigns a range of elements a certain value
  • ranges::fill_n (C++20) (niebloid) β€” assigns a value to a number of elements
  • ranges::transform (C++20) (niebloid) β€” applies a function to a range of elements
  • ranges::generate_random (C++26) (niebloid) β€” fills a range with random numbers from a uniform random bit generator
  • generate (function template) β€” assigns the results of successive function calls to every element in a range

that's the whole page, you understood it, you slayed it, and honestly the algorithms library is lucky to have you 🫢

k bye, go generate something cute 🌸

β€” source: http://cppreference.com, dated 2024.06.10, and this lil manual page header says std::ranges::generate(3). mwah πŸ’‹

πŸ“¦ where this page comes from