Skip to content

Commit ee9548b

Browse files
committed
libstdc++: Fix value categories used by ranges access CPOs [PR 100824]
The implementation of P2091R0 was incomplete, so that some range access CPOs used perfect forwarding where they should not. This fixes it by consistently operating on lvalues. Some additional changes that are not necessary to fix the bug: Modify the __as_const helper to simplify its usage. Instead of deducing the value category from its argument, and requiring callers to forward the argument as the correct category, add a non-deduced template parameter which is used for the value category and accept the argument as an lvalue. This means callers say __as_const<T>(t) instead of __as_const(std::forward<T>(t)). Always use an lvalue reference type as the template argument for the _S_noexcept helpers, so that we only instantiate one specialization for lvalues and rvalues of the same type. Move some helper concepts and functions from namespace std::__detail to ranges::__cust_access, to be consistent with the ranges::begin CPO. This ensures that the __adl_begin concept and the _Begin::operator() function are in the same namespace, so unqualified lookup is consistent and the poison pills for begin are visible to both. Simplified static assertions for arrays, because the expression a+0 is already ill-formed for an array of incomplete type. Signed-off-by: Jonathan Wakely <[email protected]> libstdc++-v3/ChangeLog: PR libstdc++/100824 * include/bits/iterator_concepts.h (__detail::__decay_copy) (__detail::__member_begin, __detail::__adl_begin): Move to namespace ranges::__cust_access. (__detail::__ranges_begin): Likewise, and rename to __begin. Remove redundant static assertion. * include/bits/ranges_base.h (_Begin, _End, _RBegin, _REnd): Use lvalue in noexcept specifier. (__as_const): Add non-deduced parameter for value category. (_CBegin, _CEnd, _CRBegin, _CREnd, _CData): Adjust uses of __as_const. (__member_size, __adl_size, __member_empty, __size0_empty): (__eq_iter_empty, __adl_data): Use lvalue objects in requirements. (__sentinel_size): Likewise. Add check for conversion to unsigned-like. (__member_data): Allow non-lvalue types to satisfy the concept, but use lvalue object in requirements. (_Size, _SSize): Remove forwarding to always use an lvalue. (_Data): Likewise. Add static assertion for arrays. * testsuite/std/ranges/access/cdata.cc: Adjust expected behaviour for rvalues. Add negative tests for ill-formed expressions. * testsuite/std/ranges/access/data.cc: Likewise. * testsuite/std/ranges/access/empty.cc: Adjust expected behaviour for rvalues. * testsuite/std/ranges/access/size.cc: Likewise.
1 parent f6bb145 commit ee9548b

File tree

6 files changed

+188
-125
lines changed

6 files changed

+188
-125
lines changed

libstdc++-v3/include/bits/iterator_concepts.h

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -925,8 +925,11 @@ namespace ranges
925925
struct default_sentinel_t { };
926926
inline constexpr default_sentinel_t default_sentinel{};
927927

928-
namespace __detail
928+
// This is the namespace for [range.access] CPOs.
929+
namespace ranges::__cust_access
929930
{
931+
using std::__detail::__class_or_enum;
932+
930933
template<typename _Tp>
931934
constexpr decay_t<_Tp>
932935
__decay_copy(_Tp&& __t)
@@ -936,42 +939,44 @@ namespace ranges
936939
template<typename _Tp>
937940
concept __member_begin = requires(_Tp& __t)
938941
{
939-
{ __detail::__decay_copy(__t.begin()) } -> input_or_output_iterator;
942+
{ __cust_access::__decay_copy(__t.begin()) }
943+
-> input_or_output_iterator;
940944
};
941945

946+
// Poison pills so that unqualified lookup doesn't find std::begin.
942947
void begin(auto&) = delete;
943948
void begin(const auto&) = delete;
944949

945950
template<typename _Tp>
946951
concept __adl_begin = __class_or_enum<remove_reference_t<_Tp>>
947952
&& requires(_Tp& __t)
948953
{
949-
{ __detail::__decay_copy(begin(__t)) } -> input_or_output_iterator;
954+
{ __cust_access::__decay_copy(begin(__t)) }
955+
-> input_or_output_iterator;
950956
};
951957

952958
// Simplified version of std::ranges::begin that only supports lvalues,
953959
// for use by __range_iter_t below.
954960
template<typename _Tp>
955961
requires is_array_v<_Tp> || __member_begin<_Tp&> || __adl_begin<_Tp&>
956962
auto
957-
__ranges_begin(_Tp& __t)
963+
__begin(_Tp& __t)
958964
{
959965
if constexpr (is_array_v<_Tp>)
960-
{
961-
static_assert(sizeof(remove_all_extents_t<_Tp>) != 0,
962-
"not array of incomplete type");
963-
return __t + 0;
964-
}
966+
return __t + 0;
965967
else if constexpr (__member_begin<_Tp&>)
966968
return __t.begin();
967969
else
968970
return begin(__t);
969971
}
972+
} // namespace ranges::__cust_access
970973

974+
namespace __detail
975+
{
971976
// Implementation of std::ranges::iterator_t, without using ranges::begin.
972977
template<typename _Tp>
973978
using __range_iter_t
974-
= decltype(__detail::__ranges_begin(std::declval<_Tp&>()));
979+
= decltype(ranges::__cust_access::__begin(std::declval<_Tp&>()));
975980

976981
} // namespace __detail
977982

0 commit comments

Comments
 (0)