https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120934
Bug ID: 120934 Summary: views::concat is ill-formed depending on argument order Product: gcc Version: 15.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: francois.hamonic at gmail dot com Target Milestone: --- The following example will fail to compile due to `std::views::concat(r2, r1)` iterators not satisfying `equality_comparable` while `std::views::concat(r1, r2)` is fine. The same behavior has been observed on all recent GCC implementations I tested (local GCC 15.1.0 and Godbolt GCC 15.1 and 16.0 trunk). The following error message is produced by https://godbolt.org/z/ac5jKe4Gr (GCC 16.0.0 20250702 (experimental) with ` -Wall -Wextra -std=c++26 -fconcepts-diagnostics-depth=5`). Example : #include <ranges> #include <vector> int main() { auto r1 = std::views::single(1); std::vector<int> vec = {2, 3}; auto r2 = std::views::join(std::views::transform(vec, std::views::single)); auto c1 = std::views::all(std::views::concat(r1, r2)); // OK auto c2 = std::views::all(std::views::concat(r2, r1)); // not OK } Error message : <source>: In function 'int main()': <source>:10:30: error: no match for call to '(const std::ranges::views::_All) (std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >)' 10 | auto c2 = std::views::all(std::views::concat(r2, r1)); // not OK | ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from <source>:1: /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:1429:12: note: there is 1 candidate 1429 | struct _All : __adaptor::_RangeAdaptorClosure<_All> | ^~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:1448:9: note: candidate 1: 'template<class _Range> requires (viewable_range<_Range>) && ((view<typename std::decay<_Tp>::type>) || (__can_ref_view<_Range>) || (__can_owning_view<_Range>)) constexpr auto std::ranges::views::_All::operator()(_Range&&) const' 1448 | operator() [[nodiscard]] (_Range&& __r) const | ^~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:1448:9: note: template argument deduction/substitution failed: /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:1448:9: note: constraints not satisfied In file included from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/string_view:58, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/basic_string.h:51, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/string:56, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/locale_classes.h:42, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ios_base.h:43, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/streambuf:45, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/streambuf_iterator.h:37, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/iterator:68, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:45: /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h: In substitution of 'template<class _Range> requires (viewable_range<_Range>) && ((view<typename std::decay<_Tp>::type>) || (__can_ref_view<_Range>) || (__can_owning_view<_Range>)) constexpr auto std::ranges::views::_All::operator()(_Range&&) const [with _Range = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >]': <source>:10:30: required from here 10 | auto c2 = std::views::all(std::views::concat(r2, r1)); // not OK | ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:514:13: required for the satisfaction of 'range<_Tp>' [with _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >] /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:831:13: required for the satisfaction of 'viewable_range<_Range>' [with _Range = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >] /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:514:21: in requirements with '_Tp& __t' [with _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >] /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:517:20: note: the required expression 'std::ranges::_Cpo::end(__t)' is invalid, because 517 | ranges::end(__t); | ~~~~~~~~~~~^~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:517:20: error: no match for call to '(const std::ranges::__access::_End) (std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >&)' /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:162:12: note: there is 1 candidate 162 | struct _End | ^~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:182:9: note: candidate 1: 'template<class _Tp> requires (__maybe_borrowed_range<_Tp>) && ((is_bounded_array_v<typename std::remove_reference<_Tp>::type>) || (__member_end<_Tp>) || (__adl_end<_Tp>)) constexpr auto std::ranges::__access::_End::operator()(_Tp&&) const' 182 | operator()[[nodiscard]](_Tp&& __t) const noexcept(_S_noexcept<_Tp&>()) | ^~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:182:9: note: template argument deduction/substitution failed: /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:182:9: note: constraints not satisfied /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h: In substitution of 'template<class _Tp> requires (__maybe_borrowed_range<_Tp>) && ((is_bounded_array_v<typename std::remove_reference<_Tp>::type>) || (__member_end<_Tp>) || (__adl_end<_Tp>)) constexpr auto std::ranges::__access::_End::operator()(_Tp&&) const [with _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >&]': /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:517:13: required by substitution of 'template<class _Range> requires (viewable_range<_Range>) && ((view<typename std::decay<_Tp>::type>) || (__can_ref_view<_Range>) || (__can_owning_view<_Range>)) constexpr auto std::ranges::views::_All::operator()(_Range&&) const [with _Range = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >]' 517 | ranges::end(__t); | ~~~~~~~~~~~^~~~~ <source>:10:30: required from here 10 | auto c2 = std::views::all(std::views::concat(r2, r1)); // not OK | ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:182:2: required by the constraints of 'template<class _Tp> requires (__maybe_borrowed_range<_Tp>) && ((is_bounded_array_v<typename std::remove_reference<_Tp>::type>) || (__member_end<_Tp>) || (__adl_end<_Tp>)) constexpr auto std::ranges::__access::_End::operator()(_Tp&&) const' /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:180:32: note: no operand of the disjunction is satisfied 179 | requires is_bounded_array_v<remove_reference_t<_Tp>> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 180 | || __member_end<_Tp> || __adl_end<_Tp> | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:179:18: note: the operand 'is_bounded_array_v<typename std::remove_reference<_Tp>::type>' is unsatisfied because 179 | requires is_bounded_array_v<remove_reference_t<_Tp>> | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 180 | || __member_end<_Tp> || __adl_end<_Tp> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:182:2: required by the constraints of 'template<class _Tp> requires (__maybe_borrowed_range<_Tp>) && ((is_bounded_array_v<typename std::remove_reference<_Tp>::type>) || (__member_end<_Tp>) || (__adl_end<_Tp>)) constexpr auto std::ranges::__access::_End::operator()(_Tp&&) const' /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:179:18: note: the expression 'is_bounded_array_v<typename std::remove_reference<_Tp>::type> [with _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >&]' evaluated to 'false' 179 | requires is_bounded_array_v<remove_reference_t<_Tp>> | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:180:14: note: the operand '__member_end<_Tp>' is unsatisfied because 179 | requires is_bounded_array_v<remove_reference_t<_Tp>> | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 180 | || __member_end<_Tp> || __adl_end<_Tp> | ~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/version.h:51, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/concepts:38, from /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:39: /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:147:15: required for the satisfaction of '__member_end<_Tp>' [with _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >&] /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:147:30: in requirements with '_Tp& __t' [with _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >&] /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:149:13: note: '(auto)(__t.end())' does not satisfy return-type-requirement, because 149 | { _GLIBCXX_AUTO_CAST(__t.end()) } -> sentinel_for<__range_iter_t<_Tp>>; | ^~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:149:13: error: deduced expression type does not satisfy placeholder constraints /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:149:13: note: constraints not satisfied /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/concepts:304:15: required for the satisfaction of '__weakly_eq_cmp_with<_Sent, _Iter>' [with _Sent = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false>; _Iter = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false>] /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/iterator_concepts.h:676:13: required for the satisfaction of 'sentinel_for<decltype(auto) [requires std::sentinel_for<<placeholder>, decltype(std::ranges::__access::__begin((declval<_Container&>)()))>], decltype (std::ranges::__access::__begin(declval<_Container&>()))>' [with decltype(auto) [requires std::sentinel_for<<placeholder>, decltype(std::ranges::__access::__begin((declval<_Container&>)()))>] = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false>; _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >&] /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/concepts:305:4: in requirements with 'std::remove_reference_t<_Tp>& __t', 'std::remove_reference_t<_Rhs>& __u' [with _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false>; _Up = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false>] /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/concepts:306:17: note: the required expression '(__t == __u)' is invalid, because 306 | { __t == __u } -> __boolean_testable; | ~~~~^~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/concepts:306:17: error: no match for 'operator==' (operand types are 'std::remove_reference_t<std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false> >' {aka 'const std::remove_reference<std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false> >::type'} and 'std::remove_reference_t<std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false> >' {aka 'const std::remove_reference<std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false> >::type'}) /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/concepts:306:17: note: there are 2 candidates /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:10052:5: note: candidate 1: 'constexpr bool std::ranges::operator==(const concat_view<join_view<transform_view<ref_view<std::vector<int, std::allocator<int> > >, views::_Single> >, single_view<int> >::_Iterator<false>&, std::default_sentinel_t)' (reversed) 10052 | operator==(const _Iterator& __it, default_sentinel_t) | ^~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:10052:39: note: no known conversion for argument 2 from 'std::remove_reference_t<std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false> >' {aka 'const std::remove_reference<std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >::_Iterator<false> >::type'} to 'std::default_sentinel_t' 10052 | operator==(const _Iterator& __it, default_sentinel_t) | ^~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:10043:5: note: candidate 2: 'constexpr bool std::ranges::operator==(const concat_view<join_view<transform_view<ref_view<std::vector<int, std::allocator<int> > >, views::_Single> >, single_view<int> >::_Iterator<false>&, const concat_view<join_view<transform_view<ref_view<std::vector<int, std::allocator<int> > >, views::_Single> >, single_view<int> >::_Iterator<false>&) requires (equality_comparable<decltype(std::ranges::__access::__begin((declval<typename std::__conditional<_Const>::type<const _Vs, _Vs>&>)()))> && ...)' 10043 | operator==(const _Iterator& __x, const _Iterator& __y) | ^~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:10043:5: note: constraints not satisfied /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges: In instantiation of 'constexpr bool std::ranges::operator==(const concat_view<join_view<transform_view<ref_view<std::vector<int, std::allocator<int> > >, views::_Single> >, single_view<int> >::_Iterator<false>&, const concat_view<join_view<transform_view<ref_view<std::vector<int, std::allocator<int> > >, views::_Single> >, single_view<int> >::_Iterator<false>&) requires (equality_comparable<decltype(std::ranges::__access::__begin((declval<typename std::__conditional<_Const>::type<const _Vs, _Vs>&>)()))> && ...)': /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/concepts:306:10: required by substitution of 'template<class _Tp> requires (__maybe_borrowed_range<_Tp>) && ((is_bounded_array_v<typename std::remove_reference<_Tp>::type>) || (__member_end<_Tp>) || (__adl_end<_Tp>)) constexpr auto std::ranges::__access::_End::operator()(_Tp&&) const [with _Tp = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >&]' 306 | { __t == __u } -> __boolean_testable; | ~~~~^~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/bits/ranges_base.h:517:13: required by substitution of 'template<class _Range> requires (viewable_range<_Range>) && ((view<typename std::decay<_Tp>::type>) || (__can_ref_view<_Range>) || (__can_owning_view<_Range>)) constexpr auto std::ranges::views::_All::operator()(_Range&&) const [with _Range = std::ranges::concat_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int> >]' 517 | ranges::end(__t); | ~~~~~~~~~~~^~~~~ <source>:10:30: required from here 10 | auto c2 = std::views::all(std::views::concat(r2, r1)); // not OK | ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:10043:5: required by the constraints of 'template<class ... _Vs> template<bool _Const> constexpr bool std::ranges::operator==(const concat_view<_Vs>::_Iterator<_Const>&, const concat_view<_Vs>::_Iterator<_Const>&) requires (equality_comparable<decltype(std::ranges::__access::__begin((declval<typename std::__conditional<_Const>::type<const _Vs, _Vs>&>)()))> && ...)' /opt/compiler-explorer/gcc-trunk-20250702/include/c++/16.0.0/ranges:10044:79: note: the expression '(equality_comparable<decltype(std::ranges::__access::__begin((declval<typename std::__conditional<_Const>::type<const _Vs, _Vs>&>)()))> && ...) [with _Const = false; _Vs = {std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, std::ranges::views::_Single> >, std::ranges::single_view<int>}]' evaluated to 'false' 10044 | requires (equality_comparable<iterator_t<__maybe_const_t<_Const, _Vs>>> && ...) | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~ (cropped since the messages repeat from there)