Here's the minimal example:
#include <vector>
#include <ranges>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/enumerate.hpp>
int main()
{
std::vector<int> const vec;
vec
| std::views::enumerate
| ranges::to_vector; // error
vec
| ranges::views::enumerate
| ranges::to_vector; // ok
}
which gives this error:
<source>: In function 'int main()':
<source>:12:5: error: no match for 'operator|' (operand types are 'std::ranges::enumerate_view<std::ranges::ref_view<const std::vector<int> > >' and 'ranges::detail::to_container_fn<ranges::detail::from_range<std::vector> >' {aka 'const ranges::detail::to_container::closure<ranges::detail::from_range<std::vector>, ranges::detail::to_container::fn<ranges::detail::from_range<std::vector> > >'})
10 | vec
| ~~~
11 | | std::views::enumerate
| ~~~~~~~~~~~~~~~~~~~~~~~
| |
| std::ranges::enumerate_view<std::ranges::ref_view<const std::vector<int> > >
12 | | ranges::to_vector; // error
| ^ ~~~~~~~~~~~~~~~~~
| |
| ranges::detail::to_container_fn<ranges::detail::from_range<std::vector> > {aka const ranges::detail::to_container::closure<ranges::detail::from_range<std::vector>, ranges::detail::to_container::fn<ranges::detail::from_range<std::vector> > >}
followed by a list of candidates that were considered.
As far as I understand, two candidate overloads of ranges::to_vector are attempted, and and both fail for the same reason, namely that the requirement of input_range<Rng> boils down to concepts::common_reference<std::tuple<long int, const int&>&&, std::tuple<long int, int>&> having a ::type member, which it doesn't. Now, the long int is just the index attached by enumerate, whereas the second entry of the tuple corresponds to the entry in v, and that the & in const int& is most likely the problem, but I don't quite get where Range-v3 vs C++23's enumerate differ, and, most importantly, whether one of the 2 is correct and the other is wrong.
Maybe related: Range-v3: Why is ranges::to_vector needed here?
The real solution here is just to use the utilities from the same library: std::ranges::to works just fine with std::views::enumerate.
The direct answer to the question has to do with common_reference. One of the difficult things about views::enumerate† is that you want to end up with a value_type of tuple<int, range_value_t<R>> and a reference of tuple<int, range_reference_t<R>>. And those need to share a common reference. In order for that to happen, you need to specialize basic_common_reference to handle tuple properly.
range-v3§ does this internally with its own tuple type (ranges::common_tuple<Ts...>). The standard library added support for this for std::tuple<Ts...> in P2321.
Notably, ranges::views::enumerate gives you a common_pair<I, T> (which ranges::common_reference handles properly) but std::views::enumerate gives you a std::tuple<I, T> (which std::common_reference handles properly). Internally, they are consistent.
But if you use ranges::to, it's going to check ranges::common_reference, which doesn't handle std::tuple properly. So the constraint check fails for std::views::enumerate.
If you use std::ranges::to, it just works. So just use the thing that works. It even works with ranges::views::enumerate, because the latter's value_type is actually std::pair (not ranges::common_pair) so std::common_reference even works correctly.
The only real benefit of the range-v3 solution is that it provides to_vector but that's easy enough to add yourself:
inline constexpr auto to_vector = std::ranges::to<std::vector>();
† This link is to my own blog post.
§ I am technically a range-v3 maintainer, although I have been doing very little on that front for some time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With