C++ Logo

std-proposals

Advanced search

[std-proposals] ranges::to valarray (from_range_t for valarray constructor)

From: Liu Yihua <yihuajack_at_[hidden]>
Date: Sun, 7 Jan 2024 04:30:25 +0000
Hi all,

Currently, we can write
```cpp
std::valarray<double> va = {1, 2};
auto new_vec = va | std::ranges::views::take(2) | std::ranges::to<std::vector<double>>();
```
but not
```cpp
auto new_va = va | std::ranges::views::take(2) | std::ranges::to<std::valarray<double>>();
```
Using C = std::valarray<double> for valarray and C = std::vector<double> for vector,
R = std::ranges::take_view<std::ranges::ref_view<std::valarray<double>>> (can also be std::range:transform_view<std::ranges::ref_view<std::vector<double, std::allocator<double>>>>, etc.).
According to N4950 Page 1145 Chapter 26.5.7.2 Range conversions ranges::to [range.utility.conv.to]
> The range conversion functions construct an object (usually a container) from a range, by using a constructor
taking a range, a from_range_t tagged constructor, or a constructor taking a pair of iterators, or by inserting
each element of the range into the default-constructed object.

1. Take clang libc++ as an example,
(2.1) First see if the non-recursive case applies -- the conversion target is either:
- a range with a convertible value type;
- a non-range type which might support being created from the input argument(s) (e.g. an `optional`).
C does satisfy std::ranges::input_range, but
std::convertible_to<std::ranges::range_reference_t<R>, std::ranges::range_value_t<C>> is true.
i.e. !std::ranges::input_range<Container> || std::convertible_to<std::ranges::range_reference_t<Range>, std::ranges::range_value_t<Container>> is false.
(2.1.1) Case 1 -- construct directly from the given range.
C(std::forward<R>(r), std::forward<Args>(args)...)
std::constructible_from<C, R, Args...> is false.
If we write va | std::ranges::to<std::valarray<double>>() then it is true.
(2.1.2) Case 2 -- construct using the `from_range_t` tagged constructor.
C(from_range, std::forward<R>(r), std::forward<Args>(args)...)
std::constructible_from<C, std::from_range_t, R, Args...> is true for vector, false for valarray, because
std::vector<T, Alloator> constructor etc. use std::from_range_t but std::valarray<T> constructor does not.
See https://en.cppreference.com/w/cpp/ranges/from_range.
(2.1.3) Case 3 -- construct from a begin-end iterator pair.
C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...)
template <class Container, class Range, class... Args>
concept constructible_from_iter_pair =
std::ranges::common_range<Range> && // (2.1.3.1)
requires { typename std::iterator_traits<std::ranges::iterator_t<Range>>::iterator_category; } &&
std::derived_from<typename std::iterator_traits<std::ranges::iterator_t<Range>>::iterator_category, std::input_iterator_tag> && // (2.1.3.2)
std::constructible_from<Container, std::ranges::iterator_t<Range>, std::ranges::sentinel_t<Range>, Args...>; // (2.1.3.3)
std::constructible_from<C, from_range_t, R, _Args...> is true for vector, false for valarray.
(2.1.4) Case 4 -- default-construct (or construct from the extra arguments) and insert, reserving the size if possible.
C c(std::forward<Args>(args)...);
if constexpr (sized_range<R> && reservable-container <C>)
c.reserve(static_cast<range_size_t<C>>(ranges::size(r)));
ranges::copy(r, container-inserter <range_reference_t<R>>(c));
template <class Container, class Ref>
constexpr bool container_insertable = requires(Container& c, Ref&& ref) {
    requires(
    requires { c.push_back(std::forward<Ref>(ref)); } ||
    requires { c.insert(c.end(), std::forward<Ref>(ref)); });
};
std::constructible_from<C, Args> && container_insertable<C, std::ranges::range_reference_t<R>> is true for vector, false for valarray.
(2.2) Try the recursive case.
std::ranges::input_range<std::ranges::range_reference_t<R> is false.
2. Take range-v3 as an example, in <range/v3/range/conversion.hpp>,
ranges::detail::convertible_to_cont<R, C> is true for vector, false for valarray.
- ranges::detail::range_and_not_view<C> is true
- std::move_constructible<C> is true (move_constructible = constructible_from<T, T> && convertible_to<T, T>)
- ranges::detail::convertible_to_cont_impl_concept<R, C> is true for vector, false for valarray.
-- std::constructible_from<std::ranges::range_value_t<R>, std::ranges::range_reference_t<C>>
-- std::constructible_from<C, ranges::detail::range_cpp17_iterator_t<R>, ranges::detail::range_cpp17_iterator_t<R>> is true for vector, false for valarray.

In summary, ranges::to valarray is not possible because valarray does not use std::from_range_t in its constructor. Yet, the standard does not restrict ranges::to to container libraries, but "an object (usually a container)", including basic_string from strings library. Is it possible or realistic to add std::from_range_t for valarry constructor from numeric library. so that ranges::to valarray can work? Thanks.

Yihua Liu

Received on 2024-01-07 04:30:30