std::ranges::to(3)
π NAME π
std::ranges::to β std::ranges::to
okay bestie, welcome to the std::ranges::to(3) page β this is part 1 of 2, so buckle up π«Ά. This is the glow-up that lets you convert a range into a container (or into another range-y thing) without writing a whole loop like a Victorian child. no cap π
.
β¨ Synopsis β¨
Defined in header <ranges> πΈ
template< class C, ranges::input_range R, class... Args >
requires (!ranges::view<C>) (1)
(since C++23)
constexpr C to( R&& r, Args&&... args );
template< template< class... > class C, ranges::input_range R,
class... Args > (2)
(since C++23)
constexpr auto to( R&& r, Args&&... args );
template< class C, class... Args >
requires (!ranges::view<C>) (3)
(since C++23)
constexpr /*range adaptor closure*/ to( Args&&... args );
template< template< class... > class C, class... Args > (4)
(since C++23)
constexpr /*range adaptor closure*/ to( Args&&... args );
Helper templates π§
template< class Container >
constexpr bool /*reservable-container*/ =
ranges::sized_range<Container> &&
requires (Container& c, ranges::range_size_t<Container> n)
(exposition
{ (5)
only*)
c.reserve(n);
{ c.capacity() } -> std::same_as<decltype(n)>;
{ c.max_size() } -> std::same_as<decltype(n)>;
};
template< class Container, class Reference >
constexpr bool /*container-appendable*/ = requires (Container& c,
Reference&& ref)
{
requires (requires {
c.emplace_back(std::forward<Reference>(ref)); } ||
(exposition
requires { (6)
only*)
c.push_back(std::forward<Reference>(ref)); } ||
requires { c.emplace(c.end(),
std::forward<Reference>(ref); } ||
requires { c.insert(c.end(),
std::forward<Reference>(ref)); });
};
template< class Reference, class C >
constexpr auto /*container-appender*/( C& c )
{
return [&c]<class Reference>(Reference&& ref)
{
if constexpr (requires {
c.emplace_back(std::declval<Reference>()); })
c.emplace_back(std::forward<Reference>(ref));
else if constexpr (requires {
(exposition
c.push_back(std::declval<Reference>()); }) (7)
only*)
c.push_back(std::forward<Reference>(ref));
else if constexpr (requires { c.emplace(c.end(),
std::declval<Reference>()); })
c.emplace(c.end(), std::forward<Reference>(ref));
else
c.insert(c.end(), std::forward<Reference>(ref));
};
}
template< class R, class T >
concept /*container-compatible-range*/ = (8)
(exposition
ranges::input_range<R> &&
only*)
std::convertible_to<ranges::range_reference_t<R>, T>;
π the actual vibe check
The overloads of the range conversion function construct a new non-view object from a source range as its first argument by calling a constructor taking a range, a std::from_range_t tagged ranged constructor, a constructor taking an iterator-sentinel pair, or by back inserting each element of the source range into the arguments-constructed object. iconic behavior, honestly π₯.
1) Constructs an object of type C from the elements of r in the following:
a) If C does not satisfy input_range or std::convertible_to<ranges::range_reference_t<R>, ranges::range_value_t<C>> is true:
- Constructing a non-view object as if direct-initializing (but not direct-list-initializing) an object of type
Cfrom the source rangestd::forward<R>(r)and the rest of the functional argumentsstd::forward<Args>(args)...ifstd::constructible_from<C, R, Args...>is true. - Otherwise, constructing a non-view object as if direct-initializing (but not direct-list-initializing) an object of type
Cfrom additional disambiguation tagstd::from_range, the source rangestd::forward<R>(r)and the rest of the functional argumentsstd::forward<Args>(args)...ifstd::constructible_from<C, std::from_range_t, R, Args...>is true. - Otherwise, constructing a non-view object as if direct-initializing (but not direct-list-initializing) an object of type
Cfrom the iterator-sentinel pair (ranges::begin(r)as an iterator andranges::end(r)as sentinel, where iterator and sentinel have the same type. In other words, the source range must be a common range), and the rest of function argumentsstd::forward<Args>(args)...if all of the conditions below are true:ranges::common_range<R>- If
std::iterator_traits<ranges::iterator_t<R>>::iterator_categoryis valid and denotes a type that satisfiesstd::derived_from<std::input_iterator_tag> std::constructible_from<C, ranges::iterator_t<R>, ranges::sentinel_t<R>, Args...>
- Otherwise, constructing a non-view range object as if direct-initializing (but not direct-list-initializing) an object of type
Cfrom the rest of the function argumentsstd::forward<Args>(args)...with the following equivalent call below after the construction:
if constexpr (ranges::sized_range<R> && /*reservable-container*/<C>)
c.reserve(static_cast<ranges::range_size_t<C>>(ranges::size(r)));
ranges::for_each(r, /*container-appender*/(c));
If the R satisfies sized_range and C satisfies /*reservable-container*/, the constructed object c of type C is able to reserve storage with the initial storage size ranges::size(r) to prevent additional allocations during inserting new elements. Each element of r is appended to c. The operations above are valid if both of the conditions below are true:
std::constructible_from<C, Args...>/*container-appendable*/<C, ranges::range_reference_t<R>>
b) Otherwise, the return expression is equivalent to:
to<C>(ranges::ref_view(r) | views::transform([](auto&& elem)
{
return to<ranges::range_value_t<C>>(std::forward<decltype(elem)>(elem));
}), std::forward<Args>(args)...)
Which allows nested range constructions within the range if ranges::input_range<ranges::range_reference_t<C>> is true. π¦
Otherwise, the program is ill-formed. (yeah, she said what she said π)
2) Constructs an object of deduced type from the elements of r.
Let /*input-iterator*/ be an exposition only type that satisfies LegacyInputIterator:
struct /*input-iterator*/
{
using iterator_category = std::input_iterator_tag;
using value_type = ranges::range_value_t<R>;
using difference_type = std::ptrdiff_t;
using pointer = std::add_pointer_t<ranges::range_reference_t<R>>;
using reference = ranges::range_reference_t<R>;
reference operator*() const; // not
defined
(exposition only*)
pointer operator->() const; // not
defined
/*input-iterator*/& operator++(); // not
defined
/*input-iterator*/ operator++(int); // not
defined
bool operator==(const /*input-iterator*/&) const; // not
defined
};
Let /*DEDUCE-EXPR*/ be defined as follows:
C(std::declval<R>(), std::declval<Args>()...), if that expression is valid.- Otherwise,
C(std::from_range, std::declval<R>(), std::declval<Args>()...), if that expression is valid. - Otherwise,
C(std::declval</*input-iterator*/>(), std::declval</*input-iterator*/>(), std::declval<Args>()...), if that expression is valid. - Otherwise, the program is ill-formed.
The call is equivalent to to<decltype(/*DEDUCE-EXPR*/)>(std::forward<R>(r), std::forward<Args>(args)...).
3,4) Returns a perfect forwarding call wrapper that is also a RangeAdaptorClosureObject.
5) The exposition-only variable template /*reservable-container*/<Container> is true if it satisfies ranges::sized_range and is eligible to be reservable.
6) The exposition-only variable template /*container-appendable*/<Container, Reference> is true if one element of type Reference can be appended to Container through a member function call emplace_back, push_back, emplace or insert.
7) The exposition-only function template /*container-appender*/ returns a function object where a call to the returned function object is expression-equivalent to appending one element to a container.
8) The exposition-only concept /*container-compatible-range*/ is used in the definition of containers in constructing an input range R whose range reference type must be convertible to T.
πΈ Parameters πΈ
rβ a source range objectargsβ list of the arguments to (1,2) construct a range or (3,4) bind to the last parameters of range adaptor closure object
π Type requirements π
Cmust be cv-unqualified class type (1,3)
β Return value β
1,2) A constructed non-view object.
3,4) A range adaptor closure object of unspecified type, with the following properties:
π ranges::to return type
The return type is derived from ranges::range_adaptor_closure</*return-type*/>.
π Member objects π
The returned object behaves as if it has no target object, and an std::tuple object tup constructed with std::tuple<std::decay_t<Args>...>(std::forward<Args>(args)...), except that the returned object's assignment behavior is unspecified and the names are for exposition only.
π§ Constructors
The return type of ranges::to (3-4) behaves as if its copy/move constructors perform a memberwise copy/move. It is CopyConstructible if all of its member objects (specified above) are CopyConstructible, and is MoveConstructible otherwise. periodt π
π Member function operator() π
Okay bestie, so given an object G obtained from an earlier call to range::to</* see below */>(args...), when a glvalue g designating G is invoked in a function call expression g(r), an invocation of the stored object takes place, as if by:
ranges::to</* see below */>(r, std::get<Ns>(g.tup)...), whereris a source range object that must satisfyinput_range.Nsis an integer pack0, 1, ..., (sizeof...(Args) - 1).gis an lvalue in the call expression if it is an lvalue in the call expression, and is an rvalue otherwise. Thusstd::move(g)(r)can move the bound arguments into the call, whereg(r)would copy.- The specified template argument is
(3) Cor(4)the deduced type from a class templateCthat must not satisfyview.
π¨ The program is ill-formed if g has volatile-qualified type.
πΈ Exceptions πΈ
Only throws if construction of a non-view object throws. Thatβs the whole vibe check, no cap.
β¨ Notes β¨
The insertion of elements into the container may involve copy which can be less efficient than move because lvalue references are produced during the indirection call. Users can opt-in to use views::as_rvalue to adapt the range in order for their elements to always produce an rvalue reference during the indirection call which implies move.
The parentheses are mandatory when using the pipe syntax. Like, literally mandatory β donβt skip them:
auto vec = r | std::ranges::to<std::vector>; // error
auto vec = r | std::ranges::to<std::vector>(); // OK
Feature-test macro cuteness:
__cpp_lib_ranges_to_containerβ value202202L, standard(C++23), featurestd::ranges::to.
π Example π
A preview link: Compiler Explorer
// Run this code
#include <concepts>
#include <list>
#include <print>
#include <ranges>
#include <vector>
int main()
{
auto vec = std::views::iota(1, 5)
| std::views::transform([](auto const v){ return v * 2; })
| std::ranges::to<std::vector>();
static_assert(std::same_as<decltype(vec), std::vector<int>>);
std::println("{}", vec);
auto lst = vec | std::views::take(3) |
std::ranges::to<std::list<double>>();
std::println("{}", lst);
}
Output:
[2, 4, 6, 8]
[2, 4, 6]
π¦ References π¦
- C++23 standard (ISO/IEC 14882:2023):
- 26.5.7 Range conversions [range.utility.conv]
π Defect reports π
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
- DR
LWG 3984Β· applied toC++23- behavior as published: the nested construction branch of
ranges::toresulted to program ill-formed ifR&does not modelviewable_range - correct behavior: made well-formed
- behavior as published: the nested construction branch of
- DR
LWG 4016Β· applied toC++23- behavior as published: the container insertion branch of
ranges::toinvolved use of insert iterators - correct behavior: replaced with direct appending of elements to container
- behavior as published: the container insertion branch of
π Thatβs the whole page, bestie! Source: cppreference.com Β· version/date: 2024.06.10 Β· std::ranges::to(3) β periodt. π
π¦ where this page comes from
- nixpkgs unstable: stdman 2024.07.05
- opensuse-leap 16.0: stdman 2024.07.05-bp160.1.9 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms