linq for C++ and Qt

Fluent query composition with lazy evaluation

const auto products = std::vector{
    Product{ .name = "Keyboard", .price = 79.0, .in_stock = true },
    Product{ .name = "Mouse", .price = 29.0, .in_stock = false },
    Product{ .name = "Stand", .price = 99.0, .in_stock = true },
};

const auto visible_names = linq::from(&products)
    .where([](const auto& p) { return p.in_stock && p.price < 100.0; })
    .order_by([](const auto& p) { return p.price; })
    .select([](const auto& p) { return p.name; })
    .to_vector();  // or .to_qvector() with Qt
// { "Keyboard", "Stand" }
const auto grades = std::vector{
    'A', 'C', 'B',
    'A', 'B', 'A',
};

const auto totals = linq::from(&grades)
    .count_by([](char grade) { return grade; })
    .order_by([](const auto& entry) { return entry.first; })
    .to_vector();
// { {'A', 3}, {'B', 2}, {'C', 1} }
const auto customers = std::vector{
    Customer{ .id = 1, .name = "Alice" },
    Customer{ .id = 2, .name = "Bob" },
};
const auto orders = std::vector{
    Order{ .customer_id = 1, .total = 120.0 },
    Order{ .customer_id = 2, .total = 80.0 },
    Order{ .customer_id = 1, .total = 42.0 },
};

const auto rows = linq::from(&orders)
    .join(
        linq::from(&customers),
        [](const auto& o) { return o.customer_id; },
        [](const auto& c) { return c.id; },
        [](const auto& o, const auto& c) { return std::pair{ c.name, o.total }; })
    .order_by_descending([](const auto& row) { return row.second; })
    .take(2);
// { {"Alice", 120.0}, {"Bob", 80.0} }
const auto temperatures = std::vector{ 18.5, 21.0, 19.5, 24.0, 22.0 };
const auto readings = linq::from(&temperatures);

const auto total = readings.sum().value();                 // 105.0
const auto average = readings.average().value();           // 21.0
const auto count = readings.count();                       // 5
const auto [lowest, highest] = readings.minmax().value();  // 18.5, 24.0
Simpler alternative to C++20 <ranges>
Compile-time type resolution with no virtual dispatch
Lazy evaluation so query pipelines run only when iterated
Immutability-focused behavior with minimal side effects
Efficient pipelines that avoid copies and move data when possible
constexpr support for operation chains at compile time
First-class support for standard library and Qt containers

Generation

from
19 overloads
template <typename K, typename T>
[[nodiscard]]
static constexpr auto from(const QMap<K, T>* container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from(const QHash<K, T>* container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from(const QMultiMap<K, T>* container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from(const QMultiHash<K, T>* container);

template <template <typename, typename> typename C, typename T, typename A>
[[nodiscard]]
static constexpr auto from(const C<T, A>* container);

template <template <typename, typename, typename> typename C, typename T, typename S, typename U>
[[nodiscard]]
static constexpr auto from(const C<T, S, U>* container);

template <template <class, size_t> class C, typename T, size_t L>
[[nodiscard]]
static constexpr auto from(const C<T, L>* container);

template <template <class, class, class, class> class C, typename K, typename T, typename S, typename U>
[[nodiscard]]
static constexpr auto from(const C<K, T, S, U>* container);

template <template <typename> typename C, class T>
[[nodiscard]]
static constexpr auto from(const C<T>* container);

template <typename T>
[[nodiscard]]
static constexpr auto from(QList<T> container);

template <typename T>
[[nodiscard]]
static constexpr auto from(QVector<T> container);

template <typename T>
[[nodiscard]]
static constexpr auto from(QSet<T> container);

template <typename T>
[[nodiscard]]
static constexpr auto from(QQueue<T> container);

template <typename T>
[[nodiscard]]
static constexpr auto from(QStack<T> container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from(QMap<K, T> container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from(QHash<K, T> container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from(QMultiMap<K, T> container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from(QMultiHash<K, T> container);

template <typename T>
[[nodiscard]]
static constexpr auto from(std::initializer_list<T> list);

Creates a range over an existing container. For STL containers, from() is primarily a non-owning, read-only adapter from a container pointer or a std::initializer_list.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto q1 = linq::from(&nums);             // from container pointer
const auto q2 = linq::from({1, 2, 3, 4, 5});  // from initializer list
// both iterate: 1, 2, 3, 4, 5
from_copy
5 overloads
template <typename K, typename T>
[[nodiscard]]
static constexpr auto from_copy(const QMap<K, T>& container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from_copy(const QHash<K, T>& container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from_copy(const QMultiMap<K, T>& container);

template <typename K, typename T>
[[nodiscard]]
static constexpr auto from_copy(const QMultiHash<K, T>& container);

template <typename TContainer>
[[nodiscard]]
static constexpr auto from_copy(const TContainer& container);

Creates a range that owns a copy of the container. The resulting query is independent of the original container's lifetime and later modifications.

const auto original = std::vector{1, 2, 3};
const auto query = linq::from_copy(original);
// iterating: 1, 2, 3
from_mutable
5 overloads
template <template <typename, typename> typename C, typename T, typename A>
[[nodiscard]]
static constexpr auto from_mutable(C<T, A>* container);

template <template <typename, typename, typename> typename C, typename T, typename S, typename U>
[[nodiscard]]
static constexpr auto from_mutable(C<T, S, U>* container);

template <template <class, size_t> class C, typename T, size_t L>
[[nodiscard]]
static constexpr auto from_mutable(C<T, L>* container);

template <template <class, class, class, class> class C, typename K, typename T, typename S, typename U>
[[nodiscard]]
static constexpr auto from_mutable(C<K, T, S, U>* container);

template <template <typename> typename C, class T>
[[nodiscard]]
static constexpr auto from_mutable(C<T>* container);

Creates a range that yields mutable references to container elements. Useful for in-place modification through a query chain.

auto nums = std::vector{1, 2, 3, 4, 5};
linq::from_mutable(&nums)
        .for_each([](auto& n) { n *= 2; });
// nums is now: {2, 4, 6, 8, 10}
from_to
Signature
template <typename T>
requires(details::addable<T>)
    [[nodiscard]] static constexpr auto from_to(T&& start, T&& end, T&& step = T(1));

Generates a numeric range from start to end (inclusive) with an optional step. Works with integral and floating-point types.

const auto range1 = linq::from_to(0, 5);           // 0, 1, 2, 3, 4, 5
const auto range2 = linq::from_to(0.0, 1.5, 0.5); // 0, 0.5, 1.0, 1.5
empty
Signature
template <typename T>
[[nodiscard]]
static constexpr auto empty();

Creates an empty range of the specified type. Useful as a placeholder or default.

const auto nothing = linq::empty<int>();
// iterating: (nothing)
repeat_value
Signature
template <typename T>
[[nodiscard]]
static constexpr auto repeat_value(T value, size_t count);

Generates a range that yields a single value repeated count times.

const auto zeros = linq::repeat_value(0, 5);
// iterating: 0, 0, 0, 0, 0
generate
Signature
template <typename TGenerator>
[[nodiscard]]
static constexpr auto generate(TGenerator&& generator) -> details::generator_range<TGenerator>;

Creates a range from a generator function. The function receives an index and must return generate_return(value) to yield a value or generate_finish<T>() to stop.

const auto evens = linq::generate([](size_t i) {
    if (i < 5)
        return linq::generate_return(i * 2);
    return linq::generate_finish<int>();
});
// iterating: 0, 2, 4, 6, 8
generate_return
Signature
template <typename T>
[[nodiscard]]
static constexpr auto generate_return(T&& value) -> details::generator_return_value<T>;

Wraps a value to be yielded by a generate() range. Used inside generator functions.

return linq::generate_return(42);         // yields 42
generate_finish
Signature
template <typename T>
[[nodiscard]]
static constexpr auto generate_finish() -> details::generator_return_value<T>;

Sentinel value that signals a generate() range to stop iteration.

return linq::generate_finish<int>();      // stops generation
unfold
Signature
template <typename TState, typename TFunc>
[[nodiscard]]
static constexpr auto unfold(TState&& initial, TFunc&& func);

State-based generator. Calls func(state) which returns std::optional<std::pair<output, next_state>>. Yields values until the function returns std::nullopt.

const auto fibonacci = linq::unfold(
    std::pair{0, 1},
    [](auto state) -> std::optional<std::pair<int, std::pair<int, int>>> {
        const auto [a, b] = state;
        if (a > 50) return std::nullopt;
        return std::pair{a, std::pair{b, a + b}};
    }
);
// iterating: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

Filtering

where
Signature
template <typename TPredicate>
[[nodiscard]]
constexpr auto where(TPredicate&& predicate) const;

Filters elements, keeping only those where the predicate returns true. Lazy evaluation.

const auto nums = std::vector{1, 2, 3, 4, 5, 6};
const auto evens = linq::from(&nums)
        .where([](int n) { return n % 2 == 0; })
        .to_vector();
// = {2, 4, 6}
distinct
Signature
[[nodiscard]]
constexpr auto distinct() const;

Removes duplicate elements, keeping only the first occurrence of each value. Uses an internal hash set.

const auto nums = std::vector{1, 2, 3, 3, 5, 4, 5, 6};
const auto unique = linq::from(&nums)
        .distinct()
        .to_vector();
// = {1, 2, 3, 5, 4, 6}
distinct_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto distinct_by(TKeySelector&& key_selector) const;

Removes duplicates based on a key selector. Keeps the first element for each unique key.

const auto words = std::vector{"apple"s, "avocado"s, "banana"s, "apricot"s};
const auto result = linq::from(&words)
        .distinct_by([](const auto& w) { return w[0]; })
        .to_vector();
// = {"apple", "banana"}
choose
Signature
template <typename TTransform>
[[nodiscard]]
constexpr auto choose(TTransform&& transform) const;

Maps each element through a function that returns std::optional. Filters out std::nullopt and unwraps the remaining values. Combines select and where in one step.

const auto nums = std::vector{1, 2, 3, 4, 5, 6};
const auto result = linq::from(&nums)
        .choose([](int n) -> std::optional<int> {
            return n % 2 == 0 ? std::optional(n * 10) : std::nullopt;
        })
        .to_vector();
// = {20, 40, 60}

Projection

select
Signature
template <typename TTransform>
[[nodiscard]]
constexpr auto select(TTransform&& transform) const;

Projects each element into a new form using a transform function. The classic map operation.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto doubled = linq::from(&nums)
        .select([](int n) { return n * 2; })
        .to_vector();
// = {2, 4, 6, 8, 10}
select_to_string

Converts each element to a std::string representation. Uses std::to_string by default.

const auto nums = std::vector{1, 2, 3};
const auto strs = linq::from(&nums)
        .select_to_string()
        .to_vector();
// = {"1", "2", "3"}
select_many
Signature
template <typename TTransform>
[[nodiscard]]
constexpr auto select_many(TTransform&& transform) const;

Projects each element to a sub-range and flattens all sub-ranges into a single sequence. The classic flat-map operation.

const auto groups = std::vector{
    std::vector{1, 2},
    std::vector{3, 4, 5},
    std::vector{6},
};
const auto flat = linq::from(&groups)
        .select_many([](const auto& g) { return linq::from(&g); })
        .to_vector();
// = {1, 2, 3, 4, 5, 6}
flatten
Signature
[[nodiscard]]
constexpr auto flatten() const;

Flattens a range of ranges into a single range. Shorthand for select_many with the identity function.

const auto groups = std::vector{
    std::vector{1, 2},
    std::vector{3},
    std::vector{4, 5},
};
const auto flat = linq::from(&groups)
        .flatten()
        .to_vector();
// = {1, 2, 3, 4, 5}
cast
Signature
template <typename TDest>
[[nodiscard]]
constexpr auto cast() const;

Reinterprets each element as another type via static_cast.

const auto doubles = std::vector{1.1, 2.9, 3.5};
const auto ints = linq::from(&doubles)
        .cast<int>()
        .to_vector();
// = {1, 2, 3}
enumerate
Signature
[[nodiscard]]
constexpr auto enumerate() const;

Pairs each element with its zero-based index. Yields std::pair<size_t, T>.

const auto words = std::vector{"hello"s, "world"s};
const auto indexed = linq::from(&words)
        .enumerate()
        .to_vector();
// = {(0, "hello"), (1, "world")}
keys
Signature
[[nodiscard]]
constexpr auto keys() const;

Extracts the first element (.first) from each std::pair in the range.

const auto pairs = std::vector{std::pair{"a"s, 1}, std::pair{"b"s, 2}};
const auto k = linq::from(&pairs)
        .keys()
        .to_vector();
// = {"a", "b"}
values
Signature
[[nodiscard]]
constexpr auto values() const;

Extracts the second element (.second) from each std::pair in the range.

const auto pairs = std::vector{std::pair{"a"s, 1}, std::pair{"b"s, 2}};
const auto v = linq::from(&pairs)
        .values()
        .to_vector();
// = {1, 2}
elements
Signature
template <size_t I>
[[nodiscard]]
constexpr auto elements() const;

Extracts the I-th element from each tuple-like element via std::get<I>.

const auto tuples = std::vector{
    std::tuple{1, "a"s, 1.0},
    std::tuple{2, "b"s, 2.0},
};
const auto names = linq::from(&tuples)
        .elements<1>()
        .to_vector();
// = {"a", "b"}
scan
Signature
template <typename TSeed, typename TFunc>
[[nodiscard]]
constexpr auto scan(TSeed seed, TFunc&& func) const;

Running accumulation that yields each intermediate value. Starts from a seed and applies the accumulator function at each step.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto running = linq::from(&nums)
        .scan(0, [](int acc, int n) { return acc + n; })
        .to_vector();
// = {1, 3, 6, 10, 15}

Partitioning

take
Signature
[[nodiscard]]
constexpr auto take(size_t count) const;

Takes the first count elements from the range.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto first_three = linq::from(&nums)
        .take(3)
        .to_vector();
// = {1, 2, 3}
take_while
Signature
template <typename TPredicate>
[[nodiscard]]
constexpr auto take_while(TPredicate&& predicate) const;

Takes elements from the beginning while the predicate is satisfied. Stops at the first element that fails.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto result = linq::from(&nums)
        .take_while([](int n) { return n < 4; })
        .to_vector();
// = {1, 2, 3}
take_last
Signature
[[nodiscard]]
constexpr auto take_last(size_t count) const;

Takes only the last count elements.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto last_two = linq::from(&nums)
        .take_last(2)
        .to_vector();
// = {4, 5}
skip
Signature
[[nodiscard]]
constexpr auto skip(size_t count) const;

Skips the first count elements and yields the rest.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto result = linq::from(&nums)
        .skip(3)
        .to_vector();
// = {4, 5}
skip_while
Signature
template <typename TPredicate>
[[nodiscard]]
constexpr auto skip_while(TPredicate&& predicate) const;

Skips elements while the predicate is satisfied. Yields from the first element that fails onward.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto result = linq::from(&nums)
        .skip_while([](int n) { return n < 3; })
        .to_vector();
// = {3, 4, 5}
skip_last
Signature
[[nodiscard]]
constexpr auto skip_last(size_t count) const;

Skips the last count elements and yields everything before them.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto result = linq::from(&nums)
        .skip_last(2)
        .to_vector();
// = {1, 2, 3}
chunk
Signature
[[nodiscard]]
constexpr auto chunk(size_t chunk_size) const;

Splits the range into consecutive chunks of up to chunk_size elements. Yields sub-ranges.

const auto nums = std::vector{1, 2, 3, 4, 5, 6, 7};
const auto chunks = linq::from(&nums)
        .chunk(3)
        .to_vector();
// = {{1,2,3}, {4,5,6}, {7}}

Ordering

order_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto order_by(TKeySelector&& key_selector, sort_direction sort_dir) const;

Sorts elements using a key selector and an explicit direction. Sorting happens on first iteration and the result is cached.

const auto words = std::vector{"hello"s, "world"s, "are"s, "some"s};
const auto sorted = linq::from(&words)
        .order_by(
            [](const auto& w) { return w.size(); },
            linq::sort_direction::ascending
        )
        .to_vector();
// = {"are", "some", "hello", "world"}
order_by_ascending
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto order_by_ascending(TKeySelector&& key_selector) const;

Sorts elements in ascending order by key selector.

const auto nums = std::vector{5, 3, 1, 4, 2};
const auto sorted = linq::from(&nums)
        .order_by_ascending([](int n) { return n; })
        .to_vector();
// = {1, 2, 3, 4, 5}
order_by_descending
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto order_by_descending(TKeySelector&& key_selector) const;

Sorts elements in descending order by key selector.

const auto nums = std::vector{5, 3, 1, 4, 2};
const auto sorted = linq::from(&nums)
        .order_by_descending([](int n) { return n; })
        .to_vector();
// = {5, 4, 3, 2, 1}
then_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto then_by(TKeySelector&& key_selector, sort_direction sort_dir) const;

Applies a secondary sort criterion after order_by. Uses explicit direction.

const auto words = std::vector{"hello"s, "world"s, "here"s, "are"s, "some"s, "sorted"s, "words"s};
const auto sorted = linq::from(&words)
        .order_by_ascending([](const auto& w) { return w.size(); })
        .then_by(
            [](const auto& w) { return w; },
            linq::sort_direction::ascending
        )
        .to_vector();
// = {"are", "here", "some", "hello", "sorted", "words", "world"}
then_by_ascending
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto then_by_ascending(TKeySelector&& key_selector) const;

Secondary ascending sort after order_by.

const auto words = std::vector{"ccc"s, "aaa"s, "bbb"s, "aa"s};
const auto sorted = linq::from(&words)
        .order_by_ascending([](const auto& w) { return w.size(); })
        .then_by_ascending([](const auto& w) { return w; })
        .to_vector();
// = {"aa", "aaa", "bbb", "ccc"}
then_by_descending
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto then_by_descending(TKeySelector&& key_selector) const;

Secondary descending sort after order_by.

const auto words = std::vector{"aaa"s, "bbb"s, "ccc"s, "aa"s};
const auto sorted = linq::from(&words)
        .order_by_ascending([](const auto& w) { return w.size(); })
        .then_by_descending([](const auto& w) { return w; })
        .to_vector();
// = {"aa", "ccc", "bbb", "aaa"}
order
Signature
[[nodiscard]]
constexpr auto order() const;

Sorts by natural ordering (ascending). Shorthand for order_by(self, ascending).

const auto nums = std::vector{5, 2, 8, 1, 9};
const auto sorted = linq::from(&nums)
        .order()
        .to_vector();
// = {1, 2, 5, 8, 9}
order_descending
Signature
[[nodiscard]]
constexpr auto order_descending() const;

Sorts by natural ordering in descending order.

const auto nums = std::vector{5, 2, 8, 1, 9};
const auto sorted = linq::from(&nums)
        .order_descending()
        .to_vector();
// = {9, 8, 5, 2, 1}
reverse
Signature
[[nodiscard]]
constexpr auto reverse() const;

Reverses the order of elements.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto rev = linq::from(&nums)
        .reverse()
        .to_vector();
// = {5, 4, 3, 2, 1}
shuffle
2 overloads
[[nodiscard]]
auto shuffle() const;

template <typename TEngine>
[[nodiscard]]
auto shuffle(TEngine&& engine) const;

Randomly shuffles elements using std::random_device for seeding. Uses Fisher-Yates algorithm.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto shuffled = linq::from(&nums)
        .shuffle()
        .to_vector();
// = random permutation, e.g. {3, 1, 5, 2, 4}

Joining

concat
Signature
template <typename TOtherRange>
[[nodiscard]]
constexpr auto concat(const TOtherRange& other_range) const;

Concatenates another range after this one. Both ranges must have the same element type.

const auto a = std::vector{1, 2, 3};
const auto b = std::vector{4, 5, 6};
const auto result = linq::from(&a)
        .concat(linq::from(&b))
        .to_vector();
// = {1, 2, 3, 4, 5, 6}
append
Signature
template <typename TValue>
[[nodiscard]]
constexpr auto append(TValue&& value) const;

Appends a single value to the end of the range.

const auto nums = std::vector{1, 2, 3};
const auto result = linq::from(&nums)
        .append(4)
        .to_vector();
// = {1, 2, 3, 4}
prepend
Signature
template <typename TValue>
[[nodiscard]]
constexpr auto prepend(TValue&& value) const;

Prepends a single value to the beginning of the range.

const auto nums = std::vector{2, 3, 4};
const auto result = linq::from(&nums)
        .prepend(1)
        .to_vector();
// = {1, 2, 3, 4}
zip
Signature
template <typename TOtherRange, typename TTransform>
[[nodiscard]]
constexpr auto zip(TOtherRange&& other, TTransform&& transform) const;

Combines two ranges pairwise using a transform function. Stops when the shorter range is exhausted.

const auto names = std::vector{"Alice"s, "Bob"s};
const auto ages = std::vector{30, 25};
const auto combined = linq::from(&names)
        .zip(
            linq::from(&ages),
            [](const auto& name, int age) { return name + " (" + std::to_string(age) + ")"; }
        )
        .to_vector();
// = {"Alice (30)", "Bob (25)"}
interleave
Signature
template <typename TOtherRange>
[[nodiscard]]
constexpr auto interleave(TOtherRange&& other) const;

Alternates elements from this range and another. Stops when the shorter range is exhausted.

const auto a = std::vector{1, 3, 5};
const auto b = std::vector{2, 4, 6};
const auto result = linq::from(&a)
        .interleave(linq::from(&b))
        .to_vector();
// = {1, 2, 3, 4, 5, 6}
intersperse
Signature
template <typename TValue>
[[nodiscard]]
constexpr auto intersperse(TValue&& value) const;

Inserts a separator value between each element of the range.

const auto words = std::vector{"hello"s, "world"s, "!"s};
const auto result = linq::from(&words)
        .intersperse(" "s)
        .to_vector();
// = {"hello", " ", "world", " ", "!"}
join
Signature
template <typename TOtherRange, typename TKeySelectorA, typename TKeySelectorB, typename TTransform>
[[nodiscard]]
constexpr auto join(
    const TOtherRange& other_range,
    TKeySelectorA&&    key_selector_a,
    TKeySelectorB&&    key_selector_b,
    TTransform&&       transform) const;

Hash join in O(n+m). Correlates elements from two ranges where their keys match. The transform function receives each matching pair.

struct Person { std::string name; int dept_id; };
struct Dept { int id; std::string name; };

const auto people = std::vector{Person{"Alice", 1}, Person{"Bob", 2}};
const auto depts = std::vector{Dept{1, "Engineering"}, Dept{2, "Marketing"}};

const auto result = linq::from(&people)
        .join(
            linq::from(&depts),
            [](const auto& p) { return p.dept_id; },
            [](const auto& d) { return d.id; },
            [](const auto& p, const auto& d) { return p.name + " - " + d.name; }
        )
        .to_vector();
// = {"Alice - Engineering", "Bob - Marketing"}
group_join
Signature
template <typename TOtherRange, typename TKeySelectorA, typename TKeySelectorB, typename TTransform>
[[nodiscard]]
constexpr auto group_join(
    TOtherRange&&   other,
    TKeySelectorA&& key_selector_a,
    TKeySelectorB&& key_selector_b,
    TTransform&&    transform) const;

Like join, but groups all matching inner elements per outer element. The transform function receives the outer element and a std::vector of matching inner elements.

struct Order { int id; int customer_id; };
struct Customer { int id; std::string name; };

const auto orders = std::vector{Order{1, 1}, Order{2, 1}, Order{3, 2}};
const auto customers = std::vector{Customer{1, "Alice"}, Customer{2, "Bob"}};

const auto result = linq::from(&customers)
        .group_join(
            linq::from(&orders),
            [](const auto& c) { return c.id; },
            [](const auto& o) { return o.customer_id; },
            [](const auto& c, auto orders) {
                return c.name + ": " + std::to_string(orders.size());
            }
        )
        .to_vector();
// = {"Alice: 2", "Bob: 1"}
left_join
Signature
template <typename TOtherRange, typename TKeySelectorA, typename TKeySelectorB, typename TTransform>
[[nodiscard]]
constexpr auto left_join(
    TOtherRange&&   other,
    TKeySelectorA&& key_selector_a,
    TKeySelectorB&& key_selector_b,
    TTransform&&    transform) const;

Like join, but includes unmatched left elements with an empty std::vector of matches.

struct Employee { std::string name; int dept_id; };
struct Dept { int id; std::string name; };

const auto employees = std::vector{Employee{"Alice", 1}, Employee{"Bob", 99}};
const auto depts = std::vector{Dept{1, "Engineering"}};

const auto result = linq::from(&employees)
        .left_join(
            linq::from(&depts),
            [](const auto& e) { return e.dept_id; },
            [](const auto& d) { return d.id; },
            [](const auto& e, const auto& depts) {
                return e.name + " -> "
                    + (depts.empty() ? "(none)" : depts.front().name);
            }
        )
        .to_vector();
// = {"Alice -> Engineering", "Bob -> (none)"}
cartesian_product
Signature
template <typename TOtherRange>
[[nodiscard]]
constexpr auto cartesian_product(TOtherRange&& other) const;

Returns all std::pairs (a, b) where a is from this range and b is from the other range.

const auto suits = std::vector{"Hearts"s, "Spades"s};
const auto ranks = std::vector{"A"s, "K"s, "Q"s};
const auto deck = linq::from(&suits)
        .cartesian_product(linq::from(&ranks))
        .to_vector();
// = {(Hearts,A), (Hearts,K), (Hearts,Q), (Spades,A), (Spades,K), (Spades,Q)}
repeat
Signature
[[nodiscard]]
constexpr auto repeat(size_t count) const;

Repeats the entire range count additional times. The total number of iterations is the original range times (count + 1).

const auto nums = linq::from_to(0, 2);
const auto repeated = nums
        .repeat(2)
        .to_vector();
// = {0, 1, 2, 0, 1, 2, 0, 1, 2}

Grouping

group_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto group_by(TKeySelector&& key_selector) const;

Groups elements by a key selector. Yields grouping objects, each with a .key() method and iterable sub-range.

const auto nums = std::vector{1, 2, 3, 4, 5, 6, 7, 8};
for (const auto& group : linq::from(&nums)
         .group_by([](int n) { return n % 2; })) {
    // group.key() == 0 → {2, 4, 6, 8}
    // group.key() == 1 → {1, 3, 5, 7}
}
chunk_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto chunk_by(TKeySelector&& key_selector) const;

Groups consecutive elements that share the same key. Unlike group_by, preserves original order and only groups adjacent equal keys.

const auto nums = std::vector{1, 1, 2, 2, 2, 1, 1};
for (const auto& chunk : linq::from(&nums)
         .chunk_by([](int n) { return n; })) {
    // first chunk:  {1, 1}
    // second chunk: {2, 2, 2}
    // third chunk:  {1, 1}
}
count_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto count_by(TKeySelector&& key_selector) const;

Groups elements by key and counts the elements in each group. Yields std::pair<key, count>.

const auto words = std::vector{"apple"s, "avocado"s, "banana"s, "blueberry"s};
const auto counts = linq::from(&words)
        .count_by([](const auto& w) { return w[0]; })
        .to_vector();
// = {('a', 2), ('b', 2)}

Set

union_with
Signature
template <typename TOtherRange>
[[nodiscard]]
constexpr auto union_with(TOtherRange&& other) const;

Returns the set union: unique elements from both ranges combined.

const auto a = std::vector{1, 2, 3, 4};
const auto b = std::vector{3, 4, 5, 6};
const auto result = linq::from(&a)
        .union_with(linq::from(&b))
        .to_vector();
// = {1, 2, 3, 4, 5, 6}
intersect
Signature
template <typename TOtherRange>
[[nodiscard]]
constexpr auto intersect(TOtherRange&& other) const;

Returns the set intersection: elements present in both ranges.

const auto a = std::vector{1, 2, 3, 4};
const auto b = std::vector{3, 4, 5, 6};
const auto result = linq::from(&a)
        .intersect(linq::from(&b))
        .to_vector();
// = {3, 4}
except
Signature
template <typename TOtherRange>
[[nodiscard]]
constexpr auto except(TOtherRange&& other) const;

Returns the set difference: elements in this range that are not in the other range.

const auto a = std::vector{1, 2, 3, 4};
const auto b = std::vector{3, 4, 5, 6};
const auto result = linq::from(&a)
        .except(linq::from(&b))
        .to_vector();
// = {1, 2}

Windowing

windowed
Signature
[[nodiscard]]
constexpr auto windowed(size_t window_size) const;

Sliding window of the given size over the range. Yields sub-ranges, each containing window_size consecutive elements.

const auto nums = std::vector{1, 2, 3, 4, 5};
for (const auto& w : linq::from(&nums)
         .windowed(3)) {
    // first:  {1, 2, 3}
    // second: {2, 3, 4}
    // third:  {3, 4, 5}
}
adjacent
Signature
template <size_t N>
[[nodiscard]]
constexpr auto adjacent() const;

Groups consecutive N-tuples in non-overlapping fashion. The template parameter N specifies the tuple size.

const auto nums = std::vector{1, 2, 3, 4, 5, 6};
for (const auto& t : linq::from(&nums)
         .adjacent<3>()) {
    // first:  {1, 2, 3}
    // second: {4, 5, 6}
}
pairwise
Signature
[[nodiscard]]
constexpr auto pairwise() const;

Groups consecutive elements into overlapping pairs. Yields std::pair<T, T> for each adjacent pair.

const auto nums = std::vector{1, 2, 3, 4};
const auto pairs = linq::from(&nums)
        .pairwise()
        .to_vector();
// = {(1,2), (2,3), (3,4)}
split
Signature
template <typename TValue>
[[nodiscard]]
constexpr auto split(TValue&& delimiter) const;

Splits the range at each occurrence of a delimiter value. Yields sub-ranges between delimiters.

const auto nums = std::vector{1, 0, 2, 0, 0, 3};
for (const auto& part : linq::from(&nums)
         .split(0)) {
    // first:  {1}
    // second: {2}
    // third:  {3}
}
step_by
Signature
[[nodiscard]]
constexpr auto step_by(size_t step) const;

Takes every step-th element from the range, starting at the first element.

const auto nums = std::vector{1, 2, 3, 4, 5, 6, 7, 8};
const auto result = linq::from(&nums)
        .step_by(3)
        .to_vector();
// = {1, 4, 7}

Deduplication

dedup
Signature
[[nodiscard]]
constexpr auto dedup() const;

Removes consecutive duplicate elements using operator==. Only adjacent duplicates are removed; non-adjacent duplicates are kept.

const auto nums = std::vector{1, 1, 2, 2, 2, 3, 1, 1};
const auto result = linq::from(&nums)
    .dedup()
    .to_vector();
// = {1, 2, 3, 1}
dedup_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto dedup_by(TKeySelector&& key_selector) const;

Removes consecutive duplicates based on a key selector. Adjacent elements with the same key are collapsed.

const auto words = std::vector{"Apple"s, "apricot"s, "Banana"s, "blueberry"s};
const auto result = linq::from(&words)
    .dedup_by([](const auto& w) { return std::tolower(w[0]); })
    .to_vector();
// = {"Apple", "Banana"}

Aggregation

sum
Signature
[[nodiscard]]
constexpr auto sum() const;

Sums all elements using operator+=. Returns std::optional; empty ranges produce std::nullopt.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto total = linq::from(&nums).sum();
// = optional(15)
product
Signature
[[nodiscard]]
constexpr auto product() const;

Computes the product of all elements using operator*=. Returns std::optional; empty ranges produce std::nullopt.

const auto nums = std::vector{1, 2, 3, 4};
const auto result = linq::from(&nums).product();
// = optional(24)
min
Signature
[[nodiscard]]
constexpr auto min() const;

Returns the minimum element using operator<. Returns std::optional; empty ranges produce std::nullopt.

const auto nums = std::vector{5, 2, 8, 1, 9};
const auto result = linq::from(&nums).min();
// = optional(1)
max
Signature
[[nodiscard]]
constexpr auto max() const;

Returns the maximum element using operator<. Returns std::optional; empty ranges produce std::nullopt.

const auto nums = std::vector{5, 2, 8, 1, 9};
const auto result = linq::from(&nums).max();
// = optional(9)
minmax
Signature
[[nodiscard]]
constexpr auto minmax() const -> std::optional<std::pair<output_t, output_t>>;

Returns both the minimum and maximum element in a single pass. Returns std::optional<std::pair<T, T>>; empty ranges produce std::nullopt.

const auto nums = std::vector{5, 2, 8, 1, 9};
const auto result = linq::from(&nums).minmax();
// = optional(pair(1, 9))
min_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto min_by(TKeySelector&& key_selector) const;

Returns the element with the smallest key value. Returns std::optional.

struct Person { std::string name; int age; };
const auto people = std::vector{Person{"Alice", 30}, Person{"Bob", 25}, Person{"Carol", 35}};
const auto youngest = linq::from(&people)
        .min_by([](const auto& p) { return p.age; });
// = optional(Person{"Bob", 25})
max_by
Signature
template <typename TKeySelector>
[[nodiscard]]
constexpr auto max_by(TKeySelector&& key_selector) const;

Returns the element with the largest key value. Returns std::optional.

struct Person { std::string name; int age; };
const auto people = std::vector{Person{"Alice", 30}, Person{"Bob", 25}, Person{"Carol", 35}};
const auto oldest = linq::from(&people)
        .max_by([](const auto& p) { return p.age; });
// = optional(Person{"Carol", 35})
sum_and_count
2 overloads
if (const auto maybe_sum_and_count = op.sum_and_count());

[[nodiscard]]
constexpr auto sum_and_count() const;

Computes both the sum and element count in a single pass. Returns std::optional<std::pair<T, size_t>>.

const auto nums = std::vector{10, 20, 30};
const auto result = linq::from(&nums).sum_and_count();
// = optional(pair(60, 3))
average
Signature
[[nodiscard]]
constexpr auto average() const
    requires(averageable<output_t>);

Computes the average of all elements. Returns std::optional. For numeric types, returns longdouble.

const auto nums = std::vector{10, 20, 30};
const auto avg = linq::from(&nums).average();
// = optional(20.0L)
aggregate
Signature
template <typename TSeed, std::invocable<TSeed&&, const TOutput&> TAccumFunc>
[[nodiscard]]
constexpr auto aggregate(TSeed seed, TAccumFunc&& func) const;

Applies an accumulator function with an explicit seed. Always returns a value. For empty ranges, the seed is returned, so the result is not std::optional.

const auto nums = std::vector{1, 2, 3, 4};
const auto result = linq::from(&nums)
        .aggregate(0, [](int acc, int n) {
            return acc + n * n;
        });
// = 30  (0 + 1 + 4 + 9 + 16)
reduce
Signature
template <std::invocable<TOutput&&, const TOutput&> TAccumFunc>
[[nodiscard]]
constexpr auto reduce(const TAccumFunc& func) const;

Applies an accumulator with a default-constructed seed. Unlike aggregate, no explicit seed is needed.

const auto nums = std::vector{1, 2, 3, 4};
const auto result = linq::from(&nums)
        .reduce([](int acc, int n) {
            return acc + n;
        });
// = 10
count
2 overloads
[[nodiscard]]
constexpr auto count() const -> size_t;

template <typename TPredicate>
[[nodiscard]]
constexpr auto count(const TPredicate& predicate) const -> size_t;

Returns the number of elements. With no arguments, uses size() if available (O(1)), otherwise iterates (O(n)). With a predicate, counts elements satisfying the condition (always O(n)).

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto total = linq::from(&nums)
        .count();                           // = 5
const auto evens = linq::from(&nums)
        .count([](int n) { return n % 2 == 0; }); // = 2

Element Access

first
2 overloads
[[nodiscard]]
constexpr auto first() const -> std::optional<output_t>;

template <typename TPredicate>
[[nodiscard]]
constexpr auto first(const TPredicate& predicate) const -> std::optional<output_t>;

Returns the first element, or the first element satisfying a predicate. Returns std::optional.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto first = linq::from(&nums).first();          // = optional(1)
const auto first_even = linq::from(&nums)
        .first([](int n) { return n % 2 == 0; });     // = optional(2)
last
2 overloads
[[nodiscard]]
constexpr auto last() const -> std::optional<output_t>;

template <typename TPredicate>
[[nodiscard]]
constexpr auto last(const TPredicate& predicate) const -> std::optional<output_t>;

Returns the last element, or the last element satisfying a predicate. Returns std::optional.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto last = linq::from(&nums).last();            // = optional(5)
const auto last_even = linq::from(&nums)
        .last([](int n) { return n % 2 == 0; });      // = optional(4)
single
2 overloads
[[nodiscard]]
constexpr auto single() const -> std::optional<output_t>;

template <typename TPredicate>
[[nodiscard]]
constexpr auto single(TPredicate&& predicate) const -> std::optional<output_t>;

Returns the only element in the range. Returns std::nullopt if the range is empty or contains more than one element.

const auto one = std::vector{42};
const auto empty = std::vector<int>{};
const auto many = std::vector{1, 2, 3};

const auto a = linq::from(&one).single();    // = optional(42)
const auto b = linq::from(&empty).single();  // = nullopt
const auto c = linq::from(&many).single();   // = nullopt (asserts in debug)
element_at
Signature
[[nodiscard]]
constexpr auto element_at(size_t index) const -> std::optional<output_t>;

Returns the element at a zero-based index. Returns std::optional if the index is out of bounds.

const auto nums = std::vector{10, 20, 30};
const auto at_0 = linq::from(&nums).element_at(0);  // = optional(10)
const auto at_5 = linq::from(&nums).element_at(5);  // = nullopt
find
7 overloads
template <typename TPredicate>
[[nodiscard]]
constexpr auto find(const TPredicate& predicate) const -> std::optional<output_t>;

auto* found = m_parent->m_inner_map.find(key);

auto* existing = m_inner_map.find(key);

auto* existing = map.find(key);

auto*          found = m_parent->m_inner_map.find(key);

auto*            found   = m_parent->m_inner_map.find(a_key);

auto* existing = indices.find(key);

Returns the first element matching a predicate. Alias for first(pred). Returns std::optional.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto result = linq::from(&nums)
        .find([](int n) { return n > 3; });
// = optional(4)
find_index
2 overloads
const auto idx = find_index(key);

template <typename TPredicate>
[[nodiscard]]
constexpr auto find_index(const TPredicate& predicate) const -> std::optional<size_t>;

Returns the zero-based index of the first element matching a predicate. Returns std::optional<size_t>.

const auto nums = std::vector{10, 20, 30, 40};
const auto idx = linq::from(&nums)
        .find_index([](int n) { return n > 25; });
// = optional(2)
first_index_of
Signature
[[nodiscard]]
constexpr auto first_index_of(const output_t& value) const -> std::optional<size_t>;

Returns the zero-based index of the first occurrence of a specific value. Returns std::optional<size_t>.

const auto nums = std::vector{10, 20, 30, 20};
const auto idx = linq::from(&nums)
        .first_index_of(20);
// = optional(1)

Quantifier

any
2 overloads
template <typename TPredicate>
[[nodiscard]]
constexpr auto any(const TPredicate& predicate) const -> bool;

[[nodiscard]]
constexpr auto any() const -> bool;

Returns true if the range has at least one element, or if any element satisfies a predicate.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto has_any = linq::from(&nums).any();         // = true
const auto has_even = linq::from(&nums)
        .any([](int n) { return n % 2 == 0; });      // = true
const auto has_neg = linq::from(&nums)
        .any([](int n) { return n < 0; });           // = false
all
Signature
template <typename TPredicate>
[[nodiscard]]
constexpr auto all(const TPredicate& predicate) const -> bool;

Returns true if all elements satisfy the predicate.

const auto nums = std::vector{2, 4, 6, 8};
const auto all_even = linq::from(&nums)
        .all([](int n) { return n % 2 == 0; });      // = true
const auto all_big = linq::from(&nums)
        .all([](int n) { return n > 5; });           // = false
none
Signature
template <typename TPredicate>
[[nodiscard]]
constexpr auto none(const TPredicate& predicate) const -> bool;

Returns true if no element satisfies the predicate.

const auto nums = std::vector{2, 4, 6, 8};
const auto no_odds = linq::from(&nums)
        .none([](int n) { return n % 2 != 0; });     // = true
contains
2 overloads
[[nodiscard]]
constexpr auto contains(const output_t& value) const -> bool;

template <typename TEqual>
[[nodiscard]]
constexpr auto contains(const output_t& value, TEqual&& equal) const -> bool;

Returns true if the range contains a specific value. Supports an optional custom equality comparator.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto has_three = linq::from(&nums).contains(3);  // = true
const auto has_ten = linq::from(&nums).contains(10);   // = false
equals
2 overloads
template <typename U>
constexpr auto equals(const range<U, TOutput>& other_range) const -> bool;

constexpr auto equals(const std::initializer_list<output_t>& list) const -> bool;

Returns true if two ranges are element-wise equal. Compares against another range or a std::initializer_list.

const auto a = std::vector{1, 2, 3};
const auto b = std::vector{1, 2, 3};
const auto same = linq::from(&a)
        .equals(linq::from(&b));         // = true
const auto also = linq::from(&a)
        .equals({1, 2, 3});              // = true
is_empty

Returns true if the range has no elements.

const auto empty = std::vector<int>{};
const auto nums = std::vector{1, 2, 3};
const auto a = linq::from(&empty).is_empty();  // = true
const auto b = linq::from(&nums).is_empty();   // = false
starts_with

Returns true if the range begins with the elements of the given prefix range.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto prefix = std::vector{1, 2, 3};
const auto ok = linq::from(&nums)
        .starts_with(linq::from(&prefix));    // = true
ends_with

Returns true if the range ends with the elements of the given suffix range.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto suffix = std::vector{4, 5};
const auto ok = linq::from(&nums)
        .ends_with(linq::from(&suffix));      // = true
is_sorted

Returns true if the range is sorted in ascending order according to operator<.

const auto sorted = std::vector{1, 2, 3, 4, 5};
const auto unsorted = std::vector{3, 1, 4, 2};
const auto a = linq::from(&sorted).is_sorted();    // = true
const auto b = linq::from(&unsorted).is_sorted();  // = false

Conversion

to_vector
5 overloads
[[nodiscard]]
auto to_vector() const -> std::vector<output_t>;

[[nodiscard]]
auto to_vector() const -> std::vector<value_t>;

[[nodiscard]]
auto to_vector() const -> std::vector<std::decay_t<typename TPrevRange::iterator::output_t>>;

[[nodiscard]]
auto to_vector() const -> std::vector<container_element_t>;

[[nodiscard]]
auto to_vector() const -> std::vector<typename TContainer::value_type>;

Materializes the range into a std::vector.

const auto nums = std::vector{3, 1, 4, 1, 5};
const auto result = linq::from(&nums)
        .where([](int n) { return n > 2; })
        .to_vector();
// = {3, 4, 5}
to_list
Signature
[[nodiscard]]
auto to_list() const -> std::list<output_t>;

Materializes the range into a std::list.

const auto nums = std::vector{1, 2, 3};
const auto result = linq::from(&nums).to_list();
// std::list<int>{1, 2, 3}
to_set
Signature
[[nodiscard]]
auto to_set() const -> std::set<output_t>;

Materializes the range into a std::set (sorted, unique elements).

const auto nums = std::vector{3, 1, 4, 1, 5};
const auto result = linq::from(&nums).to_set();
// std::set<int>{1, 3, 4, 5}
to_hash_set
Signature
[[nodiscard]]
auto to_hash_set() const -> details::hash_set_backend_t<output_t>;

Materializes the range into a std::unordered_set (unique elements, no ordering guarantee).

const auto nums = std::vector{3, 1, 4, 1, 5};
const auto result = linq::from(&nums).to_hash_set();
// std::unordered_set<int>{1, 3, 4, 5} (order may vary)
to_map
Signature
[[nodiscard]]
auto to_map() const
    requires(has_first_and_second_type<output_t>);

Materializes a range of std::pairs into a std::map. Elements must have first_type and second_type.

const auto pairs = std::vector{std::pair{"b"s, 2}, std::pair{"a"s, 1}};
const auto result = linq::from(&pairs).to_map();
// std::map<std::string, int>{{"a", 1}, {"b", 2}}
to_unordered_map
Signature
[[nodiscard]]
auto to_unordered_map() const
    requires(has_first_and_second_type<output_t>);

Materializes a range of std::pairs into a std::unordered_map.

const auto pairs = std::vector{std::pair{"b"s, 2}, std::pair{"a"s, 1}};
const auto result = linq::from(&pairs).to_unordered_map();
// std::unordered_map<std::string, int>{{"a", 1}, {"b", 2}}
to_qvector
Signature
[[nodiscard]]
auto to_qvector() const -> QVector<output_t>;

Materializes the range into a QVector, preserving the iteration order of the source range.

const auto nums = QVector{1, 2, 3, 4};
const auto result = linq::from(nums)
        .where([](int n) { return n % 2 == 0; })
        .to_qvector();
// QVector<int>{2, 4}
to_qlist
Signature
[[nodiscard]]
auto to_qlist() const -> QList<output_t>;

Materializes the range into a QList, preserving the iteration order of the source range.

to_qset
Signature
[[nodiscard]]
auto to_qset() const -> QSet<output_t>;

Materializes the range into a QSet. Equivalent values are coalesced according to QSet semantics.

to_qmap
Signature
[[nodiscard]]
auto to_qmap() const
    requires(has_first_and_second_type<output_t>);

Materializes a range of pairs into a QMap. Elements must expose first_type and second_type, such as std::pair<K, V>.

to_qhash
Signature
[[nodiscard]]
auto to_qhash() const
    requires(has_first_and_second_type<output_t>);

Materializes a range of pairs into a QHash. Elements must expose first_type and second_type, such as std::pair<K, V>.

to_qmultimap
Signature
[[nodiscard]]
auto to_qmultimap() const
    requires(has_first_and_second_type<output_t>);

Materializes a range of pairs into a QMultiMap. Every pair produced by the range is inserted.

to_qmultihash
Signature
[[nodiscard]]
auto to_qmultihash() const
    requires(has_first_and_second_type<output_t>);

Materializes a range of pairs into a QMultiHash. Every pair produced by the range is inserted.

unzip
Signature
[[nodiscard]]
auto unzip() const
    requires(has_first_and_second_type<output_t>);

Splits a range of std::pairs into two separate std::vectors. Returns std::pair<std::vector<First>, std::vector<Second>>.

const auto pairs = std::vector{
    std::pair{"a"s, 1},
    std::pair{"b"s, 2},
    std::pair{"c"s, 3},
};
const auto [keys, vals] = linq::from(&pairs)
        .unzip();
// keys = {"a", "b", "c"}
// vals = {1, 2, 3}
partition
Signature
template <typename TPredicate>
[[nodiscard]]
auto partition(TPredicate&& predicate) const
    -> std::pair<details::sequence_buffer_t<output_t>, details::sequence_buffer_t<output_t>>;

Splits the range into two std::vectors: one for elements that satisfy the predicate and one for those that do not.

const auto nums = std::vector{1, 2, 3, 4, 5, 6};
const auto [evens, odds] = linq::from(&nums)
        .partition([](int n) { return n % 2 == 0; });
// evens = {2, 4, 6}
// odds = {1, 3, 5}

Side Effects

for_each
Signature
template <typename TFunc>
constexpr void for_each(TFunc&& func) const;

Applies a function to each element for side effects. This is a terminal operation and evaluates the entire range.

const auto nums = std::vector{1, 2, 3};
linq::from(&nums)
        .for_each([](int n) {
            std::cout << n << '\n';
        });
// prints: 1, 2, 3
for_each_while
Signature
template <typename TFunc>
constexpr auto for_each_while(TFunc&& func) const -> size_t;

Applies a function to each element. Stops when the function returns false. Returns the number of elements processed.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto count = linq::from(&nums)
        .for_each_while([](int n) {
            std::cout << n << '\n';
            return n < 3; // stop after 3
        });
// prints: 1, 2, 3
// count = 3
inspect
Signature
template <typename TFunc>
[[nodiscard]]
constexpr auto inspect(TFunc&& func) const;

Calls a function on each element without modifying the range. This is a lazy side effect, so the function is called during iteration. It is useful for debugging or logging within a chain.

const auto nums = std::vector{1, 2, 3, 4, 5};
const auto result = linq::from(&nums)
        .inspect([](int n) { std::cout << "before: " << n << '\n'; })
        .where([](int n) { return n > 2; })
        .to_vector();
// prints "before: 1" through "before: 5"
// result = {3, 4, 5}

Misc

default_if_empty
2 overloads
[[nodiscard]]
constexpr auto default_if_empty() const;

template <typename TValue>
[[nodiscard]]
constexpr auto default_if_empty(TValue&& default_value) const;

Returns the range unchanged if it has elements. If empty, returns a range containing a single default-constructed value or a provided default.

const auto empty = std::vector<int>{};
const auto nums = std::vector{1, 2, 3};

const auto a = linq::from(&empty)
        .default_if_empty()
        .to_vector();                     // = {0}
const auto b = linq::from(&empty)
        .default_if_empty(42)
        .to_vector();                     // = {42}
const auto c = linq::from(&nums)
        .default_if_empty(42)
        .to_vector();                     // = {1, 2, 3}
cycle
Signature
[[nodiscard]]
constexpr auto cycle() const;

Repeats the range infinitely. Each iteration re-traverses the original range.

const auto nums = std::vector{1, 2, 3};
const auto result = linq::from(&nums)
        .cycle()
        .take(7)
        .to_vector();
// = {1, 2, 3, 1, 2, 3, 1}
sample
Signature
template <typename TEngine>
[[nodiscard]]
auto sample(size_t count, TEngine&& engine) const;

Takes count random elements from the range using reservoir sampling. Single-pass O(n) algorithm.

const auto nums = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const auto result = linq::from(&nums)
        .sample(3, std::mt19937{std::random_device{}()})
        .to_vector();
// = random 3 elements, e.g. {2, 7, 9}