C++ Logo

std-discussion

Advanced search

The C++ Ranges Library, P1206R7 and std::ranges::container concept.

From: Mateusz Zych <mte.zych_at_[hidden]>
Date: Wed, 30 Jul 2025 02:55:38 +0300
Hello everyone!

Recently I've noticed that the std::from_range_t constructor of std::vector,
when given an rvalue container, does not move its elements into the std
::vector,
but instead copies the elements:

   -

   auto vector = std::vector { std::from_range, std::array { noisy {
}, noisy { } } };


Compiler Explorer: click <https://godbolt.org/z/6T3cWqxa3>

I am aware that I can use std::views::as_rvalue, but applying it manually
should not be necessary,
that is, the std::from_range_t constructor of std::vector should
automatically detect
that a container-compatible-range is an actual container, which has value
semantics and owns its elements,
and in that case move elements from that container into the std::vector.

In my opinion, vast majority of C++ programmers will except that such an
optimization will be performed
and unknowingly cause huge performance issue. We are talking about gigantic
performance degradation!

The issue is the P1206R7
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1206r7.pdf> and
the C++ Ranges Library do not define the std::ranges::container concept,
which would allow us to determine reliably from which ranges elements could
be safely moved out,
when that range is about to be destroyed.

Also, I see that for the purpose of specifying the std::ranges::to facility
in the C++23 standard,
there are two exposition-only concepts defined, which attempt to define
some sort of container concept,
but I don't think they are sufficient to detect whether a range is a
container or not:

   - template< class Container >

   constexpr bool reservable-container =
       ranges::sized_range
   <https://en.cppreference.com/w/cpp/ranges/sized_range.html><Container> &&
       requires (Container& c, ranges::range_size_t
   <https://en.cppreference.com/w/cpp/ranges/range_size_t.html><Container> n
   )
       {
           c.reserve(n);
           { c.capacity() } -> std::same_as
   <https://en.cppreference.com/w/cpp/concepts/same_as.html><decltype(n)>;
           { c.max_size() } -> std::same_as
   <https://en.cppreference.com/w/cpp/concepts/same_as.html><decltype(n)>;
       };

   - template< class Container, class Reference >

   constexpr bool container-appendable =
       requires (Container& c, Reference&& ref)
       {
           requires
           (
               requires { c.emplace_back(std::forward
   <https://en.cppreference.com/w/cpp/utility/forward.html><Reference>(ref))
   ; } ||
               requires { c.push_back(std::forward
   <https://en.cppreference.com/w/cpp/utility/forward.html><Reference>(ref))
   ; } ||
               requires { c.emplace(c.end(), std::forward
   <https://en.cppreference.com/w/cpp/utility/forward.html><Reference>(ref))
   ; } ||
               requires { c.insert(c.end(), std::forward
   <https://en.cppreference.com/w/cpp/utility/forward.html><Reference>(ref))
   ; }
           );
       };

Moreover, none of the existing concepts from the C++ Ranges Library are
suitable either:

   - The std::ranges::view concept was redefined / relaxed multiple times,
   but unfortunately the final version does not help us here,
   since a range modelling the std::ranges::view concept
   might not have reference semantics and might own its elements,
   for example the std::ranges::owning_view and std::optional.

   This is surprising and confusing in my opinion, considering that
   views originally were supposed mean ranges referencing / pointing to
   elements,
   like std::span and std::string_view.

   [image: ranges.png]
   - The std::ranges::borrowed_range concept does not help us either,
   because it's opt-in, that is, it does not detect automatically
   whether a std::ranges::range owns its elements or just references them,
   and does not decide based on that information whether a function
   taking that range by-value can return an iterator obtained from that
   range safely,
   that is, without a danger of dangling.

   Consequently, there might exist a range, which does not own its elements,
   but does not opt-into the std::ranges::borrowed_range concept.
   A good example is a custom span-like type written in C++17.

The ideas of ownership and value semantics are fundamental in C++ and Rust,
thus, in my opinion, the C++ Ranges Library should provide the std::ranges::
container concept.
What do you think?

Thank you, Mateusz Zych

Received on 2025-07-29 23:55:54