JH-Toolkit v1.4.1
An engineering-oriented C++20 toolkit with duck-typed concepts, static design, async coroutines, and semantic containers — header-only, RTTI-free, and concurrency-friendly.
Loading...
Searching...
No Matches
collect.h File Reference

Eager materialization adaptor — explicitly terminates a lazy range pipeline and realizes it into a concrete container C. More...

#include <ranges>
#include "jh/conceptual/collectable_container.h"
#include "jh/ranges/to.h"
#include "jh/metax/adl_apply.h"

Go to the source code of this file.

Classes

struct  jh::ranges::collect_fn< C >
 Function object implementing jh::ranges::collect. More...

Namespaces

namespace  jh::ranges
 Semantic pipeline namespace for JH range operations.

Functions

template<typename C, std::ranges::range R>
constexpr auto jh::ranges::collect_adaptor (R &&r)
 Core implementation for jh::ranges::collect adaptor.

Variables

template<typename C>
constexpr collect_fn< C > jh::ranges::collect {}
 Global instance of the collect adaptor.

Detailed Description

Eager materialization adaptor — explicitly terminates a lazy range pipeline and realizes it into a concrete container C.

Author
JeongHan-Bae mastr.nosp@m.opse.nosp@m.udo@g.nosp@m.mail.nosp@m..com

The jh::ranges::collect adaptor provides an explicit and controlled way to materialize a lazy range into a concrete container. It represents the eager half of std::ranges::to: while to performs direct construction of a closable container, collect enforces explicit evaluation of any lazy or proxy-based range and yields a stable, value-semantic container.

Behavior overview

  • If C and R satisfy closable_container_for<C, R>, it delegates directly to jh::ranges::to_adaptor<C>.
  • Otherwise, it performs element-wise insertion according to collectable_status deduction, supporting the four canonical insertion forms aligned with the proposed std::ranges::to:
    • emplace_back()
    • push_back()
    • emplace()
    • insert()
    These cover virtually all standard and third-party container families.
  • If C provides reserve() and R models sized_range, capacity is automatically preallocated.

Tuple unpacking and reconstruction

When none of the direct or implicit construction paths apply, and the range's element type is tuple-like, collect attempts a fallback reconstruction step: it unpacks the tuple-like element via jh::meta::adl_apply into emplace_back() or emplace() calls. This mechanism is unique to collect — the standard std::ranges::to does not perform such unpacking.

auto result = input_strings
| jh::ranges::to<std::pmr::unordered_map<size_t, std::string>>(
0,
std::hash<size_t>{},
std::equal_to<size_t>{},
alloc
);
constexpr detail::enumerate_fn enumerate
The user-facing enumerate adaptor.
Definition enumerate.h:149
constexpr to_fn< C > to
Global instance of the to adaptor.
Definition to.h:347
constexpr collect_fn< C > collect
Global instance of the collect adaptor.
Definition collect.h:375

Here, enumerate() (implemented via jh::ranges::zip_view and std::iota) yields a tuple-like proxy (zip_reference_proxy) combining the index and string reference; collect detects that it cannot insert the proxy directly, unpacks it, and reconstructs real std::pair<size_t, std::string> objects.

Semantic role

collect defines the explicit evaluation boundary within a lazy pipeline — it marks where deferred computations stop and data becomes concrete. This is crucial when interacting with std::views::transform or other lazy adaptors that convert a range into a transient, consumptive stream. By forcing evaluation, collect ensures that the data is materialized and safe from dangling or deferred access.

  • Explicit materialization: forces evaluation of lazy pipelines.
  • Type normalization: resolves proxy and reference wrappers into value objects.
  • Tuple fallback: reconstructs unpacked objects when direct construction is not viable.

Argument policy

collect does not accept additional constructor arguments. It performs data normalization only — all container-specific configuration (e.g. allocators, hashers, comparators) belongs to jh::ranges::to.

// Correct pipeline:
auto result = lazy_view
| jh::ranges::to<std::pmr::unordered_map<Key, Val>>(
0,
std::hash<Key>{},
std::equal_to<Key>{},
alloc
);

Do not use std::move() between collect and to — it provides no benefit. The two adaptors are designed to compose directly in a pipeline; move construction is handled automatically via RVO/NRVO. See the to adaptor documentation for detailed move semantics.

Direct completion

If your target container is a standard type that does not require extra constructor parameters (e.g. std::vector, std::set, std::unordered_map), you can use collect<C> directly as the final stage. Performance will be identical to to because, when possible, collect automatically dispatches to to_adaptor<C> internally.

Design rationale

  • Evaluation control: explicitly terminates lazy or consumptive pipelines.
  • Unified insertion model: supports four canonical insertion forms and extends them with tuple-based reconstruction.
  • Predictable semantics: forbids extra arguments, ensuring unambiguous materialization.
  • Composable design: integrates seamlessly with jh::ranges::to for final adaptation.

Relation to jh::ranges::to

collect focuses on materialization — forcing a lazy range into stable storage. to focuses on adaptation — constructing the final container, possibly with configuration parameters.

  1. collect<V>() — eagerly realize and normalize data.
  2. to<C>(...) — adapt and construct the final container.

Together they form a deterministic two-phase pipeline, separating lazy evaluation from container adaptation for clarity, safety, and composability.

Note
collect is more permissive than to: it accepts any range that supports minimal insertion semantics, and provides additional tuple-unpacking fallback paths. However, because it forbids extra arguments, configuration such as allocators or policies must be handled by to.
See also
jh::ranges::to
jh::concepts::collectable_container_for
Version
1.3.x
Date
2025