Difference between revisions of "cpp/ranges/to"
(→Example: add ctor example. case 1.a.4) is missing because it's too contrived for any boost/stdlib classes and writing a container is too much(?) boilerplate. Also missing is an filesystem::path error example) |
(Adjusted line break positions.) |
||
Line 4: | Line 4: | ||
{{dcl begin}} | {{dcl begin}} | ||
{{dcl header|ranges}} | {{dcl header|ranges}} | ||
− | {{dcla|num=1|since=c++23| | + | {{dcla|num=1|since=c++23| |
template< class C, ranges::input_range R, class... Args > | template< class C, ranges::input_range R, class... Args > | ||
requires (!ranges::view<C>) | requires (!ranges::view<C>) | ||
constexpr C to( R&& r, Args&&... args ); | constexpr C to( R&& r, Args&&... args ); | ||
}} | }} | ||
− | {{dcl|num=2|since=c++23| | + | {{dcl|num=2|since=c++23| |
− | template< template< class... > class C, ranges::input_range R, class... Args > | + | template< template< class... > class C, |
+ | ranges::input_range R, class... Args > | ||
constexpr auto to( R&& r, Args&&... args ); | constexpr auto to( R&& r, Args&&... args ); | ||
}} | }} | ||
− | {{dcla|num=3|since=c++23| | + | {{dcla|num=3|since=c++23| |
template< class C, class... Args > | template< class C, class... Args > | ||
requires (!ranges::view<C>) | requires (!ranges::view<C>) | ||
constexpr /*range adaptor closure*/ to( Args&&... args ); | constexpr /*range adaptor closure*/ to( Args&&... args ); | ||
}} | }} | ||
− | {{dcl|num=4|since=c++23| | + | {{dcl|num=4|since=c++23| |
template< template< class... > class C, class... Args > | template< template< class... > class C, class... Args > | ||
constexpr /*range adaptor closure*/ to( Args&&... args ); | constexpr /*range adaptor closure*/ to( Args&&... args ); | ||
Line 36: | Line 37: | ||
{{dcla|num=6|expos=yes|anchor=container-appendable|1= | {{dcla|num=6|expos=yes|anchor=container-appendable|1= | ||
template< class Container, class Reference > | template< class Container, class Reference > | ||
− | constexpr bool /*container-appendable*/ = | + | constexpr bool /*container-appendable*/ = |
requires (Container& c, Reference&& ref) | requires (Container& c, Reference&& ref) | ||
{ | { | ||
− | requires (requires { c.emplace_back(std::forward<Reference>(ref)); } | + | requires |
− | + | ( | |
− | + | requires { c.emplace_back(std::forward<Reference>(ref)); } {{!!}} | |
− | + | requires { 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)); } | ||
+ | ); | ||
}; | }; | ||
}} | }} | ||
Line 108: | Line 115: | ||
Let {{c|/*DEDUCE-EXPR*/}} be defined as follows: | Let {{c|/*DEDUCE-EXPR*/}} be defined as follows: | ||
* {{c|C(std::declval<R>(), std::declval<Args>()...)}}, if that expression is valid. | * {{c|C(std::declval<R>(), std::declval<Args>()...)}}, if that expression is valid. | ||
− | * Otherwise, {{c|C(std::from_range, std::declval<R>(), std::declval<Args>()...)}}, if that expression is valid. | + | * Otherwise, {{c multi|C(std::from_range, std::declval<R>(),| std::declval<Args>()...)}}, if that expression is valid. |
− | * Otherwise, {{c|C(std::declval</*input-iterator*/>(), std::declval</*input-iterator*/>(), std::declval<Args>()...)}}, if that expression is valid. | + | * Otherwise, {{c multi|C(std::declval</*input-iterator*/>(),| std::declval</*input-iterator*/>(),| std::declval<Args>()...)}}, if that expression is valid. |
* Otherwise, the program is ill-formed. | * Otherwise, the program is ill-formed. | ||
− | The call is equivalent to {{c|to<decltype(/*DEDUCE-EXPR*/)>(std::forward<R>(r), std::forward<Args>(args)...)}}. | + | The call is equivalent to {{c multi|to<decltype(/*DEDUCE-EXPR*/)>| (std::forward<R>(r), std::forward<Args>(args)...)}}. |
@3,4@ Returns a perfect forwarding call wrapper that is also a {{named req|RangeAdaptorClosureObject}}. | @3,4@ Returns a perfect forwarding call wrapper that is also a {{named req|RangeAdaptorClosureObject}}. | ||
@5@ Is {{c|true}} if it satisfies {{c/core|ranges::sized_range}} and is eligible to be reservable. | @5@ Is {{c|true}} if it satisfies {{c/core|ranges::sized_range}} and is eligible to be reservable. | ||
Line 146: | Line 153: | ||
{{member|{{small|ranges::to}} return type|2= | {{member|{{small|ranges::to}} return type|2= | ||
====Member objects==== | ====Member objects==== | ||
− | The returned object behaves as if it has no target object, and an {{lc|std::tuple}} object {{ | + | The returned object behaves as if it has no target object, and an {{lc|std::tuple}} object {{c|tup}} constructed with {{c|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==== | ====Constructors==== | ||
Line 171: | Line 178: | ||
{{source|1= | {{source|1= | ||
− | auto vec = r {{!}} std::ranges::to<std::vector>; // | + | auto vec = r {{!}} std::ranges::to<std::vector>; // Error |
auto vec = r {{!}} std::ranges::to<std::vector>(); // OK | auto vec = r {{!}} std::ranges::to<std::vector>(); // OK | ||
}} | }} | ||
Line 197: | Line 204: | ||
{{!}} std::views::transform([](int v){ return v * 2; }) | {{!}} std::views::transform([](int v){ return v * 2; }) | ||
{{!}} std::ranges::to<std::vector>(); | {{!}} std::ranges::to<std::vector>(); | ||
− | + | ||
static_assert(std::same_as<decltype(vec), std::vector<int>>); | static_assert(std::same_as<decltype(vec), std::vector<int>>); | ||
std::println("{}", vec); | std::println("{}", vec); | ||
− | + | ||
auto list = vec {{!}} std::views::take(3) {{!}} std::ranges::to<std::list<double>>(); | auto list = vec {{!}} std::views::take(3) {{!}} std::ranges::to<std::list<double>>(); | ||
std::println("{}", list); | std::println("{}", list); | ||
Line 210: | Line 217: | ||
{ | { | ||
char array[]{'a', 'b', '\0', 'c'}; | char array[]{'a', 'b', '\0', 'c'}; | ||
− | + | ||
// Argument type is convertible to result value type: | // Argument type is convertible to result value type: | ||
auto str_to = std::ranges::to<std::string>(array); | auto str_to = std::ranges::to<std::string>(array); | ||
// Equivalent to | // Equivalent to | ||
std::string str(array); | std::string str(array); | ||
− | + | ||
// Result type is not an input range: | // Result type is not an input range: | ||
auto re_to = std::ranges::to<std::regex>(array); | auto re_to = std::ranges::to<std::regex>(array); | ||
Line 221: | Line 228: | ||
std::regex re(array); | std::regex re(array); | ||
} | } | ||
− | + | ||
// 1.a.2) from_range ctor | // 1.a.2) from_range ctor | ||
{ | { | ||
auto list = {'a', 'b', '\0', 'c'}; | auto list = {'a', 'b', '\0', 'c'}; | ||
− | + | ||
// Argument type is convertible to result value type: | // Argument type is convertible to result value type: | ||
auto str_to = std::ranges::to<std::string>(list); | auto str_to = std::ranges::to<std::string>(list); | ||
// Equivalent to | // Equivalent to | ||
std::string str(std::from_range, list); | std::string str(std::from_range, list); | ||
− | + | ||
// Result type is not an input range: | // Result type is not an input range: | ||
auto pair_to = std::ranges::to<std::pair<std::from_range_t, bool>>(true); | auto pair_to = std::ranges::to<std::pair<std::from_range_t, bool>>(true); | ||
Line 236: | Line 243: | ||
std::pair<std::from_range_t, bool> pair(std::from_range, true); | std::pair<std::from_range_t, bool> pair(std::from_range, true); | ||
} | } | ||
− | + | ||
// 1.a.3) iterator pair ctor | // 1.a.3) iterator pair ctor | ||
{ | { | ||
auto list = {'a', 'b', '\0', 'c'}; | auto list = {'a', 'b', '\0', 'c'}; | ||
− | + | ||
// Argument type is convertible to result value type: | // Argument type is convertible to result value type: | ||
auto devector_to = std::ranges::to<boost::container::devector<char>>(list); | auto devector_to = std::ranges::to<boost::container::devector<char>>(list); | ||
// Equivalent to | // Equivalent to | ||
− | boost::container::devector<char> devector(std::ranges::begin(list), std::ranges::end(list)); | + | boost::container::devector<char> devector(std::ranges::begin(list), |
− | + | std::ranges::end(list)); | |
+ | |||
// Result type is not an input range: | // Result type is not an input range: | ||
std::regex re; | std::regex re; | ||
Line 260: | Line 268: | ||
===Defect reports=== | ===Defect reports=== | ||
{{dr list begin}} | {{dr list begin}} | ||
− | {{dr list item|wg=lwg|dr=3984|std=C++23|before=the nested construction branch of {{tt|ranges::to}}<br> | + | {{dr list item|wg=lwg|dr=3984|std=C++23|before=the nested construction branch of {{tt|ranges::to}} resulted to<br>program ill-formed if {{tt|R&}} does not model {{lconcept|viewable_range}}|after=made well-formed}} |
− | {{dr list item|wg=lwg|dr=4016|std=C++23|before=the container insertion branch of {{tt|ranges::to}} involved use of | + | {{dr list item|wg=lwg|dr=4016|std=C++23|before=the container insertion branch of<br>{{tt|ranges::to}} involved use of insert iterators|after=replaced with direct appending<br>of elements to container}} |
{{dr list end}} | {{dr list end}} | ||
Latest revision as of 18:18, 29 October 2024
Defined in header <ranges>
|
||
template< class C, ranges::input_range R, class... Args > requires (!ranges::view<C>) |
(1) | (since C++23) |
template< template< class... > class C, ranges::input_range R, class... Args > |
(2) | (since C++23) |
template< class C, class... Args > requires (!ranges::view<C>) |
(3) | (since C++23) |
template< template< class... > class C, class... Args > constexpr /*range adaptor closure*/ to( Args&&... args ); |
(4) | (since C++23) |
Helper templates |
||
template< class Container > constexpr bool /*reservable-container*/ = |
(5) | (exposition only*) |
template< class Container, class Reference > constexpr bool /*container-appendable*/ = |
(6) | (exposition only*) |
template< class Reference, class C > constexpr auto /*container-appender*/( C& c ); |
(7) | (exposition only*) |
template< class R, class T > concept /*container-compatible-range*/ = |
(8) | (exposition only*) |
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.
C
from the elements of r in the following:C
does not satisfy input_range
or std::convertible_to<ranges::range_reference_t<R>, ranges::range_value_t<C>> is true:C
from the source range std::forward<R>(r) and the rest of the functional arguments std::forward<Args>(args)... if std::constructible_from<C, R, Args...> is true.C
from additional disambiguation tag std::from_range, the source range std::forward<R>(r) and the rest of the functional arguments std::forward<Args>(args)... if std::constructible_from<C, std::from_range_t, R, Args...> is true.C
from the iterator-sentinel pair (ranges::begin(r) as an iterator and ranges::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 arguments std::forward<Args>(args)... if all of the conditions below are true:
- ranges::common_range<R>
- If std::iterator_traits<ranges::iterator_t<R>>::iterator_category is valid and denotes a type that satisfies std::derived_from<std::input_iterator_tag>
- std::constructible_from<C, ranges::iterator_t<R>, ranges::sentinel_t<R>, Args...>
C
from the rest of the function arguments std::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>>
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.
Let /*input-iterator*/ be an exposition only type that satisfies LegacyInputIterator:
struct /*input-iterator*/ { |
(exposition only*) | |
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.
(std::forward<R>(r), std::forward<Args>(args)...).
Reference
can be appended to Container
through a member function call emplace_back
, push_back
, emplace
or insert
.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 { c.push_back(std::declval<Reference>()); })
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));
};
R
whose range reference type must be convertible to T
.Contents |
[edit] Parameters
r | - | a source range object |
args | - | 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 | ||
-C must be cv-unqualified class type (1,3)
|
[edit] Return value
ranges::to 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.
Member function operator()
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)...), where
- r is a source range object that must satisfy
input_range
. - Ns is an integer pack 0, 1, ..., (sizeof...(Args) - 1).
- g is an lvalue in the call expression if it is an lvalue in the call expression, and is an rvalue otherwise. Thus std::move(g)(r) can move the bound arguments into the call, where g(r) would copy.
- The specified template argument is (3)
C
or (4) the deduced type from a class templateC
that must not satisfyview
.
- r is a source range object that must satisfy
The program is ill-formed if g has volatile-qualified type.
[edit] Exceptions
Only throws if construction of a non-view object throws.
[edit] 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.
auto vec = r | std::ranges::to<std::vector>; // Error auto vec = r | std::ranges::to<std::vector>(); // OK
Feature-test macro | Value | Std | Feature |
---|---|---|---|
__cpp_lib_ranges_to_container |
202202L | (C++23) | std::ranges::to
|
[edit] Example
A preview link: Compiler Explorer
#include <boost/container/devector.hpp> #include <concepts> #include <list> #include <initializer_list> #include <iostream> #include <print> #include <ranges> #include <regex> #include <string> #include <vector> int main() { auto vec = std::views::iota(1, 5) | std::views::transform([](int v){ return v * 2; }) | std::ranges::to<std::vector>(); static_assert(std::same_as<decltype(vec), std::vector<int>>); std::println("{}", vec); auto list = vec | std::views::take(3) | std::ranges::to<std::list<double>>(); std::println("{}", list); } void ctor_demos() { // 1.a.1) Direct init { char array[]{'a', 'b', '\0', 'c'}; // Argument type is convertible to result value type: auto str_to = std::ranges::to<std::string>(array); // Equivalent to std::string str(array); // Result type is not an input range: auto re_to = std::ranges::to<std::regex>(array); // Equivalent to std::regex re(array); } // 1.a.2) from_range ctor { auto list = {'a', 'b', '\0', 'c'}; // Argument type is convertible to result value type: auto str_to = std::ranges::to<std::string>(list); // Equivalent to std::string str(std::from_range, list); // Result type is not an input range: auto pair_to = std::ranges::to<std::pair<std::from_range_t, bool>>(true); // Equivalent to std::pair<std::from_range_t, bool> pair(std::from_range, true); } // 1.a.3) iterator pair ctor { auto list = {'a', 'b', '\0', 'c'}; // Argument type is convertible to result value type: auto devector_to = std::ranges::to<boost::container::devector<char>>(list); // Equivalent to boost::container::devector<char> devector(std::ranges::begin(list), std::ranges::end(list)); // Result type is not an input range: std::regex re; auto it_to = std::ranges::to<std::cregex_iterator>(list, re); // Equivalent to std::cregex_iterator it(std::ranges::begin(list), std::ranges::end(list), re); } }
Output:
[2, 4, 6, 8] [2, 4, 6]
[edit] Defect reports
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
LWG 3984 | C++23 | the nested construction branch of ranges::to resulted toprogram ill-formed if R& does not model viewable_range
|
made well-formed |
LWG 4016 | C++23 | the container insertion branch ofranges::to involved use of insert iterators
|
replaced with direct appending of elements to container |
[edit] References
- C++23 standard (ISO/IEC 14882:2024):
- 26.5.7 Range conversions [range.utility.conv]