Why is it done in std::ranges that I can't split the range that I got from join with transform
Why doesn't this code compile and how can I fix it while still using the declarative approach with std::ranges and not creating intermediate not lazy containers as std::vector after transform ?
auto Foo(std::vector<std::string_view> viewsVec) -> void {
std::println("{}",
viewsVec | std::views::transform([](std::string_view str) -> std::string_view {
return str.substr(1);
}) | std::views::join |
std::views::split('\n'));
}
I get the following compilation error: clang++ -std=c++23 split.cpp
split.cpp:10:38: error: invalid operands to binary expression ('invoke_result_t<const __fn &, transform_view<ref_view<vector<string_view,
allocator<string_view>>>, (lambda at split.cpp:8:49)>>' (aka
'std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::string_view>>, (lambda at split.cpp:8:49)>>') and
'__range_adaptor_closure_t<__bind_back_t<__fn, tuple<char>>>' (aka
'std::ranges::__range_adaptor_closure_t<std::__bind_back_t<std::ranges::views::__split_view::__fn, std::tuple<char>>>'))
8 | viewsVec | std::views::transform([](std::string_view str) -> std::string_view {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9 | return str.substr(1);
| ~~~~~~~~~~~~~~~~~~~~~
10 | }) | std::views::join |
| ~~~~~~~~~~~~~~~~~~~~~ ^
11 | std::views::split('\n'));
| ~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/cstddef:73:45: note: candidate function not viable: no known conversion from 'invoke_result_t<const __fn &,
transform_view<ref_view<vector<string_view, allocator<string_view>>>, (lambda at split.cpp:8:49)>>' (aka
'std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::string_view>>, (lambda at split.cpp:8:49)>>') to 'byte' for 1st
argument
73 | _LIBCPP_HIDE_FROM_ABI inline constexpr byte operator|(byte __lhs, byte __rhs) noexcept {
| ^ ~~~~~~~~~~
/usr/include/c++/v1/__charconv/chars_format.h:34:53: note: candidate function not viable: no known conversion from 'invoke_result_t<const __fn &,
transform_view<ref_view<vector<string_view, allocator<string_view>>>, (lambda at split.cpp:8:49)>>' (aka
'std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::string_view>>, (lambda at split.cpp:8:49)>>') to 'chars_format' for
1st argument
34 | inline _LIBCPP_HIDE_FROM_ABI constexpr chars_format operator|(chars_format __x, chars_format __y) {
| ^ ~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__ranges/range_adaptor.h:71:1: note: candidate template ignored: constraints not satisfied [with _Range =
invoke_result_t<const __fn &, transform_view<ref_view<vector<string_view, allocator<string_view>>>, (lambda at split.cpp:8:49)>>, _Closure =
__range_adaptor_closure_t<__bind_back_t<__fn, tuple<char>>>]
71 | operator|(_Range&& __range, _Closure&& __closure) noexcept(is_nothrow_invocable_v<_Closure, _Range>) {
| ^
/usr/include/c++/v1/__ranges/range_adaptor.h:69:12: note: because
'invocable<std::ranges::__range_adaptor_closure_t<std::__bind_back_t<std::ranges::views::__split_view::__fn, std::tuple<char> > >,
std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::string_view> >, (lambda at split.cpp:8:49)> > >' evaluated to false
69 | requires invocable<_Closure, _Range>
| ^
/usr/include/c++/v1/__concepts/invocable.h:28:3: note: because 'std::invoke(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...)' would be
invalid: no matching function for call to 'invoke'
28 | std::invoke(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...); // not required to be equality preserving
| ^
/usr/include/c++/v1/__ranges/range_adaptor.h:77:52: note: candidate template ignored: constraints not satisfied [with _Closure =
invoke_result_t<const __fn &, transform_view<ref_view<vector<string_view, allocator<string_view>>>, (lambda at split.cpp:8:49)>>, _OtherClosure =
__range_adaptor_closure_t<__bind_back_t<__fn, tuple<char>>>]
77 | [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator|(_Closure&& __c1, _OtherClosure&& __c2) noexcept(
| ^
/usr/include/c++/v1/__ranges/range_adaptor.h:75:11: note: because
'std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::string_view>>, (lambda at split.cpp:8:49)>>' does not satisfy
'_RangeAdaptorClosure'
75 | template <_RangeAdaptorClosure _Closure, _RangeAdaptorClosure _OtherClosure>
| ^
/usr/include/c++/v1/__ranges/range_adaptor.h:62:32: note: because
'!ranges::range<remove_cvref_t<join_view<transform_view<ref_view<vector<string_view, allocator<string_view> > >, (lambda at split.cpp:8:49)> > > >' evaluated to
false
62 | concept _RangeAdaptorClosure = !ranges::range<remove_cvref_t<_Tp>> && requires {
| ^
1 error generated.
clang --version:
clang version 19.1.0+libcxx
Target: x86_64-pc-linux-musl
Thread model: posix
InstalledDir: /usr/lib/llvm/19/bin
Configuration file: /etc/clang/x86_64-pc-linux-musl-clang.cfg
Reformatting your code a bit and dropping the println
which isn't relevant:
auto Foo(std::vector<std::string_view> viewsVec) -> void {
auto f = viewsVec // (1)
| std::views::transform([](std::string_view str) -> std::string_view {
return str.substr(1);
}) // (2)
| std::views::join // (3)
| std::views::split('\n'); // (4)
}
We can annotate our steps:
viewsVec
is a contiguous range of string_view&
transform
is a random access range of string_view
join
is an input range of char const&
views::split
requires forward or better.join
ing a range of prvalue ranges has to be an input
range because each range has to be stored somewhere — which has to be within the join_view
itself. And once you're caching you can't be anything but input.
And then split
requires forward in order to give convenient behavior and be a range of subrange
— which is only possible if you can actually have multiple iterators into your range.
For this, you need views::lazy_split
— which is a less convenient range adapter, but works on input ranges.