libstdc++: Implement LWG 4027 change to possibly-const-range [PR118083]
LWG 4027 effectively makes the const range access CPOs ranges::cfoo behave more consistently across C++23 and C++20 (pre-P2278R4) and also more consistently with the std::cfoo range accessors, as the below testcase adjustments demonstrate (which mostly consist of reverting workarounds added by r14-3771-gf12e26f3496275 and r13-7186-g0d94c6df183375). In passing fix PR118083 which reports that the input_range constraint on possibly-const-range is missing in our implementation. A consequence of this is that the const range access CPOs now consistently reject a non-range argument, and so in some our of tests we need to introduce otherwise unused begin/end members. PR libstdc++/118083 libstdc++-v3/ChangeLog: * include/bits/ranges_base.h (ranges::__access::__possibly_const_range): Adjust logic as per LWG 4027. Add missing input_range constraint. * testsuite/std/ranges/access/cbegin.cc (test05): Verify LWG 4027 testcases. * testsuite/std/ranges/access/cdata.cc: Adjust, simplify and consolidate some tests after the above. * testsuite/std/ranges/access/cend.cc: Likewise. * testsuite/std/ranges/access/crbegin.cc: Likewise. * testsuite/std/ranges/access/crend.cc: Likewise. * testsuite/std/ranges/adaptors/join.cc: Likewise. * testsuite/std/ranges/adaptors/take_while.cc: Likewise. * testsuite/std/ranges/adaptors/transform.cc: Likewise. Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
This commit is contained in:
parent
fffe14f045
commit
1b9e4fe2ff
9 changed files with 59 additions and 90 deletions
|
@ -642,11 +642,11 @@ namespace ranges
|
|||
namespace __access
|
||||
{
|
||||
#if __glibcxx_ranges_as_const // >= C++23
|
||||
template<typename _Range>
|
||||
template<input_range _Range>
|
||||
constexpr auto&
|
||||
__possibly_const_range(_Range& __r) noexcept
|
||||
{
|
||||
if constexpr (constant_range<const _Range> && !constant_range<_Range>)
|
||||
if constexpr (input_range<const _Range>)
|
||||
return const_cast<const _Range&>(__r);
|
||||
else
|
||||
return __r;
|
||||
|
|
|
@ -116,10 +116,27 @@ test04()
|
|||
VERIFY(std::ranges::cbegin(std::move(c)) == std::ranges::begin(c));
|
||||
}
|
||||
|
||||
void
|
||||
test05()
|
||||
{
|
||||
// LWG 4027 - possibly-const-range should prefer returning const R&
|
||||
auto r = std::views::single(0)
|
||||
| std::views::transform([](int) { return 0; });
|
||||
using C1 = decltype(std::ranges::cbegin(r));
|
||||
using C1 = decltype(std::cbegin(r));
|
||||
|
||||
[] (auto x) {
|
||||
auto r = std::views::single(x) | std::views::lazy_split(0);
|
||||
static_assert(!requires { (*std::ranges::cbegin(r)).front() = 42; });
|
||||
static_assert(!requires { (*std::cbegin(r)).front() = 42; });
|
||||
}(0);
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
test01();
|
||||
test03();
|
||||
test04();
|
||||
test05();
|
||||
}
|
||||
|
|
|
@ -34,20 +34,21 @@ test01()
|
|||
{
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
|
||||
#if __cpp_lib_ranges_as_const
|
||||
// These overloads mean that range<R> and range<const R> are satisfied.
|
||||
const int* begin() const { throw; }
|
||||
const int* end() const { throw; }
|
||||
#endif
|
||||
|
||||
int* data() { return &j; }
|
||||
const R* data() const noexcept { return nullptr; }
|
||||
};
|
||||
static_assert( has_cdata<R&> );
|
||||
static_assert( has_cdata<const R&> );
|
||||
R r;
|
||||
#if ! __cpp_lib_ranges_as_const
|
||||
VERIFY( std::ranges::cdata(r) == (R*)nullptr );
|
||||
static_assert( noexcept(std::ranges::cdata(r)) );
|
||||
#else
|
||||
// constant_range<const R> is not satisfied, so cdata(r) == data(r).
|
||||
VERIFY( std::ranges::cdata(r) == &r.j );
|
||||
static_assert( ! noexcept(std::ranges::cdata(r)) );
|
||||
#endif
|
||||
const R& c = r;
|
||||
VERIFY( std::ranges::cdata(c) == (R*)nullptr );
|
||||
static_assert( noexcept(std::ranges::cdata(c)) );
|
||||
|
@ -58,11 +59,11 @@ test01()
|
|||
|
||||
struct R2
|
||||
{
|
||||
#if __cpp_lib_ranges_as_const
|
||||
// These overloads mean that range<R2> and range<const R2> are satisfied.
|
||||
int* begin();
|
||||
int* end();
|
||||
const int* begin() const;
|
||||
const int* end() const;
|
||||
const int* begin() const { throw; }
|
||||
const int* end() const { throw; }
|
||||
#endif
|
||||
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
|
|
|
@ -52,15 +52,6 @@ struct R
|
|||
friend const int* end(const R&& r) noexcept { return r.a + 3; }
|
||||
};
|
||||
|
||||
#if __cpp_lib_ranges_as_const
|
||||
struct R2 : R
|
||||
{
|
||||
// This overload means constant_range<const R2> will be satisfied:
|
||||
friend const int* begin(const R2&) noexcept;
|
||||
friend const int* end(const R2& r2) noexcept { return r2.a + 2; }
|
||||
};
|
||||
#endif
|
||||
|
||||
struct RV // view on an R
|
||||
{
|
||||
R& r;
|
||||
|
@ -79,26 +70,11 @@ test03()
|
|||
{
|
||||
R r;
|
||||
const R& c = r;
|
||||
#if ! __cpp_lib_ranges_as_const
|
||||
VERIFY( std::ranges::cend(r) == std::ranges::end(c) );
|
||||
#else
|
||||
// constant_range<const R> is not satisfied, so cend(r) == end(r) instead.
|
||||
VERIFY( std::ranges::cend(r) == std::ranges::end(r) );
|
||||
R2 r2;
|
||||
const R& c2 = r2;
|
||||
// But constant_range<const R2> is satisfied, so cend(r2) == end(c2).
|
||||
VERIFY( std::ranges::cend(r2) == std::ranges::end(c2) );
|
||||
VERIFY( std::ranges::cend(r2) == std::ranges::end((const R&)c2) );
|
||||
#endif
|
||||
VERIFY( std::ranges::cend(c) == std::ranges::end(c) );
|
||||
|
||||
RV v{r};
|
||||
#if ! __cpp_lib_ranges_as_const
|
||||
VERIFY( std::ranges::cend(std::move(v)) == std::ranges::end(c) );
|
||||
#else
|
||||
// constant_range<RV> is already satisfied, so cend(v) == end(r) instead.
|
||||
VERIFY( std::ranges::cend(std::move(v)) == std::ranges::end(r) );
|
||||
#endif
|
||||
|
||||
const RV cv{r};
|
||||
VERIFY( std::ranges::cend(std::move(cv)) == std::ranges::end(c) );
|
||||
|
@ -107,7 +83,7 @@ test03()
|
|||
struct RR
|
||||
{
|
||||
short s = 0;
|
||||
long l = 0;
|
||||
short l = 0;
|
||||
int a[4] = { 0, 1, 2, 3 };
|
||||
|
||||
const void* begin() const; // return type not an iterator
|
||||
|
@ -115,8 +91,8 @@ struct RR
|
|||
friend int* end(RR&) { throw 1; }
|
||||
short* end() noexcept { return &s; }
|
||||
|
||||
friend const long* begin(const RR&) noexcept;
|
||||
const long* end() const { return &l; }
|
||||
friend const short* begin(const RR&) noexcept;
|
||||
const short* end() const { return &l; }
|
||||
|
||||
friend int* begin(RR&&) noexcept;
|
||||
friend int* end(RR&& r) { return r.a + 1; }
|
||||
|
|
|
@ -28,6 +28,11 @@ struct R1
|
|||
int i = 0;
|
||||
int j = 0;
|
||||
|
||||
#if __cpp_lib_ranges_as_const
|
||||
const int *begin() const;
|
||||
const int *end() const;
|
||||
#endif
|
||||
|
||||
const int* rbegin() const { return &i; }
|
||||
friend const int* rbegin(const R1&& r) { return &r.j; }
|
||||
};
|
||||
|
@ -36,6 +41,11 @@ struct R1V // view on an R1
|
|||
{
|
||||
R1& r;
|
||||
|
||||
#if __cpp_lib_ranges_as_const
|
||||
const int *begin() const;
|
||||
const int *end() const;
|
||||
#endif
|
||||
|
||||
friend const long* rbegin(R1V&) { return nullptr; }
|
||||
friend const int* rbegin(const R1V& rv) noexcept { return rv.r.rbegin(); }
|
||||
};
|
||||
|
@ -43,26 +53,6 @@ struct R1V // view on an R1
|
|||
// Allow ranges::end to work with R1V&&
|
||||
template<> constexpr bool std::ranges::enable_borrowed_range<R1V> = true;
|
||||
|
||||
#if __cpp_lib_ranges_as_const
|
||||
struct R1VC // view on an R1
|
||||
{
|
||||
R1& r;
|
||||
|
||||
friend const long* rbegin(R1VC&); // this is not defined
|
||||
friend const int* rbegin(const R1VC& rv) noexcept { return rv.r.rbegin(); }
|
||||
|
||||
// The following ensure that the following are satisfied:
|
||||
// constant_range<const R1VC> && ! constant_range<R1VC>
|
||||
friend int* begin(R1VC&);
|
||||
friend int* end(R1VC&);
|
||||
friend const int* begin(const R1VC&);
|
||||
friend const int* end(const R1VC&);
|
||||
};
|
||||
|
||||
// Allow ranges::end to work with R1VC&&
|
||||
template<> constexpr bool std::ranges::enable_borrowed_range<R1VC> = true;
|
||||
#endif
|
||||
|
||||
void
|
||||
test01()
|
||||
{
|
||||
|
@ -72,21 +62,8 @@ test01()
|
|||
VERIFY( std::ranges::crbegin(c) == std::ranges::rbegin(c) );
|
||||
|
||||
R1V v{r};
|
||||
#if ! __cpp_lib_ranges_as_const
|
||||
VERIFY( std::ranges::crbegin(v) == std::ranges::rbegin(c) );
|
||||
VERIFY( std::ranges::crbegin(std::move(v)) == std::ranges::rbegin(c) );
|
||||
#else
|
||||
// constant_range<const R1V> is not satisfied, so crbegin(v) == rbegin(v).
|
||||
VERIFY( std::ranges::crbegin(v) == (long*)nullptr );
|
||||
VERIFY( std::ranges::crbegin(std::move(v)) == (long*)nullptr );
|
||||
R1VC v2{r};
|
||||
// But constant_range<const R1VC> is satisfied:
|
||||
VERIFY( std::ranges::crbegin(v2) == std::ranges::rbegin(c) );
|
||||
VERIFY( std::ranges::crbegin(std::move(v2)) == std::ranges::rbegin(c) );
|
||||
const R1VC cv2{r};
|
||||
VERIFY( std::ranges::crbegin(cv2) == std::ranges::rbegin(c) );
|
||||
VERIFY( std::ranges::crbegin(std::move(cv2)) == std::ranges::rbegin(c) );
|
||||
#endif
|
||||
|
||||
const R1V cv{r};
|
||||
VERIFY( std::ranges::crbegin(cv) == std::ranges::rbegin(c) );
|
||||
|
|
|
@ -28,6 +28,11 @@ struct R1
|
|||
int i = 0;
|
||||
int j = 0;
|
||||
|
||||
#if __cpp_lib_ranges_as_const
|
||||
const int *begin() const;
|
||||
const int *end() const;
|
||||
#endif
|
||||
|
||||
constexpr const int* rbegin() const { return &i; }
|
||||
constexpr const int* rend() const { return &i + 1; }
|
||||
friend constexpr const int* rbegin(const R1&& r) { return &r.j; }
|
||||
|
@ -78,6 +83,11 @@ struct R3
|
|||
{
|
||||
int i = 0;
|
||||
|
||||
#if __cpp_lib_ranges_as_const
|
||||
const int *begin() const;
|
||||
const int *end() const;
|
||||
#endif
|
||||
|
||||
const int* rbegin() const noexcept { return &i + 1; }
|
||||
const long* rend() const noexcept { return nullptr; } // not a sentinel for rbegin()
|
||||
|
||||
|
@ -89,9 +99,11 @@ struct R4
|
|||
{
|
||||
int i = 0;
|
||||
|
||||
#if __cpp_lib_ranges_as_const
|
||||
// These members mean that range<R4> and range<const R4> are satisfied.
|
||||
const short* begin() const { return 0; }
|
||||
const short* end() const { return 0; }
|
||||
#endif
|
||||
|
||||
const int* rbegin() const noexcept { return &i + 1; }
|
||||
const long* rend() const noexcept { return nullptr; } // not a sentinel for rbegin()
|
||||
|
@ -105,16 +117,8 @@ test03()
|
|||
{
|
||||
R3 r;
|
||||
const R3& c = r;
|
||||
#if ! __cpp_lib_ranges_as_const
|
||||
VERIFY( std::ranges::crend(r) == std::ranges::rend(c) );
|
||||
static_assert( !noexcept(std::ranges::crend(r)) );
|
||||
#else
|
||||
// constant_range<const R3> is not satisfied, so crend(r) is equivalent
|
||||
// to const_sentinel{rend(r)}, which is ill-formed because range<R3>
|
||||
// is not satisfied.
|
||||
static_assert( not std::ranges::range<R3> );
|
||||
static_assert( not std::ranges::range<const R3> );
|
||||
#endif
|
||||
VERIFY( std::ranges::crend(c) == std::ranges::rend(c) );
|
||||
static_assert( !noexcept(std::ranges::crend(c)) );
|
||||
|
||||
|
|
|
@ -113,15 +113,15 @@ test06()
|
|||
|
||||
// Verify that _Iterator<false> is implicitly convertible to _Iterator<true>.
|
||||
static_assert(!std::same_as<decltype(ranges::begin(v)),
|
||||
decltype(std::as_const(v).begin())>);
|
||||
auto a = std::as_const(v).begin();
|
||||
decltype(ranges::cbegin(v))>);
|
||||
auto a = std::cbegin(v);
|
||||
a = ranges::begin(v);
|
||||
|
||||
// Verify that _Sentinel<false> is implicitly convertible to _Sentinel<true>.
|
||||
static_assert(!ranges::common_range<decltype(v)>);
|
||||
static_assert(!std::same_as<decltype(ranges::end(v)),
|
||||
decltype(std::as_const(v).end())>);
|
||||
auto b = std::as_const(v).end();
|
||||
decltype(ranges::cend(v))>);
|
||||
auto b = ranges::cend(v);
|
||||
b = ranges::end(v);
|
||||
}
|
||||
|
||||
|
|
|
@ -63,10 +63,8 @@ test03()
|
|||
|
||||
// Verify that _Sentinel<false> is implicitly convertible to _Sentinel<true>.
|
||||
static_assert(!ranges::common_range<decltype(v)>);
|
||||
#if ! __cpp_lib_ranges_as_const
|
||||
static_assert(!std::same_as<decltype(ranges::end(v)),
|
||||
decltype(ranges::cend(v))>);
|
||||
#endif
|
||||
auto b = ranges::cend(v);
|
||||
b = ranges::end(v);
|
||||
}
|
||||
|
|
|
@ -107,20 +107,16 @@ test05()
|
|||
auto r = ranges::subrange{i, std::default_sentinel};
|
||||
auto v = r | views::transform(std::negate{});
|
||||
|
||||
#if ! __cpp_lib_ranges_as_const
|
||||
// Verify that _Iterator<false> is implicitly convertible to _Iterator<true>.
|
||||
static_assert(!std::same_as<decltype(ranges::begin(v)),
|
||||
decltype(ranges::cbegin(v))>);
|
||||
#endif
|
||||
auto a = ranges::cbegin(v);
|
||||
a = ranges::begin(v);
|
||||
|
||||
#if ! __cpp_lib_ranges_as_const
|
||||
// Verify that _Sentinel<false> is implicitly convertible to _Sentinel<true>.
|
||||
static_assert(!ranges::common_range<decltype(v)>);
|
||||
static_assert(!std::same_as<decltype(ranges::end(v)),
|
||||
decltype(ranges::cend(v))>);
|
||||
#endif
|
||||
auto b = ranges::cend(v);
|
||||
b = ranges::end(v);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue