[libc++] [ranges] Remove the static_assert from ranges::begin and ranges::end.

As discussed with ldionne. The problem with this static_assert
is that it makes ranges::begin a pitfall for anyone ever to use
inside a constraint or decltype. Many Ranges things, such as ranges::size,
are specified as "Does X if X is well-formed, or else Y if Y is well-formed,
or else `ranges::end(t) - ranges::begin(t)` if that is well-formed, or else..."
And if there's a static_assert hidden inside `ranges::begin(t)`, then you get
a hard error as soon as you ask the question -- even if the answer would have
been "no, that's not well-formed"!

Constraining on `requires { t + 0; }` or `requires { t + N; }` is verboten
because of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103700 . For ranges::begin,
we can just decay to a pointer even in the incomplete-type case. For ranges::end,
we can safely constrain on `sizeof(*t)`. Yes, this means that an array of incomplete
type has a `ranges::begin` but no `ranges::end`... just like an unbounded array of
complete type. This is a valid manifestation of IFNDR.

All of the new libcxx/test/std/ cases are mandatory behavior, as far as I'm aware.
Tests for the IFNDR cases in ranges::begin and ranges::end remain in `libcxx/test/libcxx/`.
The similar tests for ranges::empty and ranges::data were simply wrong, AFAIK.

Differential Revision: https://reviews.llvm.org/D115838
This commit is contained in:
Arthur O'Dwyer 2021-12-15 22:10:34 -05:00
parent 9075009d1f
commit 8ad364ad21
13 changed files with 162 additions and 279 deletions

View File

@ -27,15 +27,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if !defined(_LIBCPP_HAS_NO_RANGES)
// clang-format off
namespace ranges {
template <class _Tp>
concept __can_borrow =
is_lvalue_reference_v<_Tp> || enable_borrowed_range<remove_cvref_t<_Tp> >;
template<class _Tp>
concept __is_complete = requires { sizeof(_Tp); };
} // namespace ranges
// [range.access.begin]
@ -61,15 +56,10 @@ namespace ranges::__begin {
struct __fn {
template <class _Tp>
requires is_array_v<remove_cv_t<_Tp>>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp& __t) const noexcept {
constexpr bool __complete = __is_complete<iter_value_t<_Tp> >;
if constexpr (__complete) { // used to disable cryptic diagnostic
return __t + 0;
}
else {
static_assert(__complete, "`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type.");
}
requires is_array_v<remove_cv_t<_Tp>>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp& __t) const noexcept
{
return __t;
}
template <class _Tp>
@ -127,14 +117,10 @@ namespace ranges::__end {
class __fn {
public:
template <class _Tp, size_t _Np>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[_Np]) const noexcept {
constexpr bool __complete = __is_complete<remove_cv_t<_Tp> >;
if constexpr (__complete) { // used to disable cryptic diagnostic
return __t + _Np;
}
else {
static_assert(__complete, "`std::ranges::end` is SFINAE-unfriendly on arrays of an incomplete type.");
}
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[_Np]) const noexcept
requires (sizeof(*__t) != 0) // Disallow incomplete element types.
{
return __t + _Np;
}
template <class _Tp>
@ -209,8 +195,6 @@ namespace ranges::inline __cpo {
inline constexpr auto cend = __cend::__fn{};
} // namespace ranges::__cpo
// clang-format off
#endif // !defined(_LIBCPP_HAS_NO_RANGES)
_LIBCPP_END_NAMESPACE_STD

View File

@ -0,0 +1,74 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// RUN: %{cxx} %{flags} %{compile_flags} -c %s -o %t.tu1.o -DTU1
// RUN: %{cxx} %{flags} %{compile_flags} -c %s -o %t.tu2.o -DTU2
// RUN: %{cxx} %t.tu1.o %t.tu2.o %{flags} %{link_flags} -o %t.exe
// RUN: %{exec} %t.exe
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// Test the libc++-specific behavior that we handle the IFNDR case for ranges::begin
// by returning the beginning of the array-of-incomplete-type.
// Use two translation units so that `Incomplete` really is never completed
// at any point within TU2, but the array `bounded` is still given a definition
// (in TU1) to avoid an "undefined reference" error from the linker.
// All of the actually interesting stuff takes place within TU2.
#include <ranges>
#include <cassert>
#include "test_macros.h"
#if defined(TU1)
struct Incomplete {};
Incomplete bounded[10];
Incomplete unbounded[10];
#else // defined(TU1)
struct Incomplete;
constexpr bool test()
{
{
extern Incomplete bounded[10];
assert(std::ranges::begin(bounded) == bounded);
assert(std::ranges::cbegin(bounded) == bounded);
assert(std::ranges::begin(std::as_const(bounded)) == bounded);
assert(std::ranges::cbegin(std::as_const(bounded)) == bounded);
ASSERT_SAME_TYPE(decltype(std::ranges::begin(bounded)), Incomplete*);
ASSERT_SAME_TYPE(decltype(std::ranges::cbegin(bounded)), const Incomplete*);
ASSERT_SAME_TYPE(decltype(std::ranges::begin(std::as_const(bounded))), const Incomplete*);
ASSERT_SAME_TYPE(decltype(std::ranges::cbegin(std::as_const(bounded))), const Incomplete*);
}
{
extern Incomplete unbounded[];
assert(std::ranges::begin(unbounded) == unbounded);
assert(std::ranges::cbegin(unbounded) == unbounded);
assert(std::ranges::begin(std::as_const(unbounded)) == unbounded);
assert(std::ranges::cbegin(std::as_const(unbounded)) == unbounded);
ASSERT_SAME_TYPE(decltype(std::ranges::begin(unbounded)), Incomplete*);
ASSERT_SAME_TYPE(decltype(std::ranges::cbegin(unbounded)), const Incomplete*);
ASSERT_SAME_TYPE(decltype(std::ranges::begin(std::as_const(unbounded))), const Incomplete*);
ASSERT_SAME_TYPE(decltype(std::ranges::cbegin(std::as_const(unbounded))), const Incomplete*);
}
return true;
}
int main(int, char**)
{
test();
static_assert(test());
}
#endif // defined(TU1)

View File

@ -0,0 +1,46 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// Test the libc++-specific behavior that we handle the IFNDR case for ranges::end
// by being SFINAE-friendly.
#include <ranges>
#include <cassert>
#include <type_traits>
struct Incomplete;
constexpr bool test()
{
{
extern Incomplete bounded[10];
assert((!std::is_invocable_v<decltype(std::ranges::end), decltype((bounded))>));
assert((!std::is_invocable_v<decltype(std::ranges::cend), decltype((bounded))>));
assert((!std::is_invocable_v<decltype(std::ranges::end), decltype(std::as_const(bounded))>));
assert((!std::is_invocable_v<decltype(std::ranges::cend), decltype(std::as_const(bounded))>));
}
{
extern Incomplete unbounded[];
assert((!std::is_invocable_v<decltype(std::ranges::end), decltype((unbounded))>));
assert((!std::is_invocable_v<decltype(std::ranges::cend), decltype((unbounded))>));
assert((!std::is_invocable_v<decltype(std::ranges::end), decltype(std::as_const(unbounded))>));
assert((!std::is_invocable_v<decltype(std::ranges::cend), decltype(std::as_const(unbounded))>));
}
return true;
}
int main(int, char**)
{
test();
static_assert(test());
}

View File

@ -1,36 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// Test the libc++ specific behavior that we provide a better diagnostic when calling
// std::ranges::begin on an array of incomplete type.
#include <ranges>
#include <type_traits>
using begin_t = decltype(std::ranges::begin);
template <class T> void f() requires std::invocable<begin_t&, T> { }
template <class T> void f() { }
void test() {
struct incomplete;
f<incomplete(&)[]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
f<incomplete(&)[10]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
f<incomplete(&)[2][2]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
// This is okay because calling `std::ranges::begin` on any rvalue is ill-formed.
f<incomplete(&&)[10]>();
}

View File

@ -1,32 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// Test the libc++ specific behavior that we provide a better diagnostic when calling
// std::ranges::cbegin on an array of incomplete type.
#include <ranges>
#include <type_traits>
using cbegin_t = decltype(std::ranges::cbegin);
template <class T> void f() requires std::invocable<cbegin_t&, T> { }
template <class T> void f() { }
void test() {
struct incomplete;
f<incomplete(&)[10]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
// This is okay because calling `std::ranges::end` on any rvalue is ill-formed.
f<incomplete(&&)[10]>();
}

View File

@ -1,38 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// Test the libc++ specific behavior that we provide a better diagnostic when calling
// std::ranges::cend on an array of incomplete type.
#include <ranges>
#include <type_traits>
using cend_t = decltype(std::ranges::cend);
template <class T> void f() requires std::invocable<cend_t&, T> { }
template <class T> void f() { }
void test() {
struct incomplete;
f<incomplete(&)[]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
// expected-error@*:* {{"`std::ranges::end` is SFINAE-unfriendly on arrays of an incomplete type."}}
f<incomplete(&)[10]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
// expected-error@*:* {{"`std::ranges::end` is SFINAE-unfriendly on arrays of an incomplete type."}}
f<incomplete(&)[2][2]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
// This is okay because calling `std::ranges::end` on any rvalue is ill-formed.
f<incomplete(&&)[10]>();
}

View File

@ -1,38 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// Test the libc++ specific behavior that we provide a better diagnostic when calling
// std::ranges::end on an array of incomplete type.
#include <ranges>
#include <type_traits>
using end_t = decltype(std::ranges::end);
template <class T> void f() requires std::invocable<end_t&, T> { }
template <class T> void f() { }
void test() {
struct incomplete;
f<incomplete(&)[]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
// expected-error@*:* {{"`std::ranges::end` is SFINAE-unfriendly on arrays of an incomplete type."}}
f<incomplete(&)[10]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
// expected-error@*:* {{"`std::ranges::end` is SFINAE-unfriendly on arrays of an incomplete type."}}
f<incomplete(&)[2][2]>();
// expected-error@*:* {{"`std::ranges::begin` is SFINAE-unfriendly on arrays of an incomplete type."}}
// This is okay because calling `std::ranges::end` on any rvalue is ill-formed.
f<incomplete(&&)[10]>();
}

View File

@ -1,56 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// Test the libc++ specific behavior that we provide a better diagnostic when calling
// std::ranges::data on an array of incomplete type.
#include <ranges>
struct Incomplete;
void f(Incomplete arr[]) {
// expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
// expected-error@*:* {{no matching function for call}}
std::ranges::data(arr);
}
void f(Incomplete(&arr)[]) {
// expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
// expected-error@*:* {{no matching function for call}}
std::ranges::data(arr);
}
void f(Incomplete(&&arr)[]) {
// expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
// expected-error@*:* {{no matching function for call}}
std::ranges::data(arr);
}
void f2(Incomplete arr[2]) {
// expected-error@*:* {{no matching function for call}}
std::ranges::data(arr);
}
void f(Incomplete(&arr)[2]) {
// expected-error@*:* {{no matching function for call}}
std::ranges::data(arr);
}
void f(Incomplete(&&arr)[2]) {
// expected-error@*:* {{no matching function for call}}
std::ranges::data(arr);
}
void f(Incomplete(&arr)[2][2]) {
// expected-error@*:* {{no matching function for call}}
std::ranges::data(arr);
}

View File

@ -1,53 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// Test the libc++ specific behavior that we provide a better diagnostic when calling
// std::ranges::empty on an array of incomplete type.
#include <ranges>
struct Incomplete;
void f(Incomplete arr[]) {
// expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
// expected-error@*:* {{call to deleted function call operator in type}}
// expected-error@*:* {{attempt to use a deleted function}}
std::ranges::begin(arr);
}
void f(Incomplete(&arr)[]) {
// expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
std::ranges::begin(arr);
}
void f(Incomplete(&&arr)[]) {
// expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
std::ranges::begin(arr);
}
void f2(Incomplete arr[2]) {
// expected-error@*:* {{call to deleted function call operator in type}}
// expected-error@*:* {{attempt to use a deleted function}}
std::ranges::begin(arr);
}
void f(Incomplete(&arr)[2]) {
std::ranges::begin(arr);
}
void f(Incomplete(&&arr)[2]) {
std::ranges::begin(arr);
}
void f(Incomplete(&arr)[2][2]) {
std::ranges::begin(arr);
}

View File

@ -23,13 +23,17 @@ using RangeCBeginT = decltype(std::ranges::cbegin)&;
static int globalBuff[8];
struct Incomplete;
static_assert(!std::is_invocable_v<RangeBeginT, int (&&)[10]>);
static_assert( std::is_invocable_v<RangeBeginT, int (&)[10]>);
static_assert(!std::is_invocable_v<RangeBeginT, int (&&)[]>);
static_assert( std::is_invocable_v<RangeBeginT, int (&)[]>);
struct Incomplete;
static_assert(!std::is_invocable_v<RangeBeginT, Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeBeginT, Incomplete(&&)[42]>);
static_assert(!std::is_invocable_v<RangeCBeginT, Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeCBeginT, Incomplete(&&)[42]>);
struct BeginMember {
int x;
constexpr const int *begin() const { return &x; }

View File

@ -28,6 +28,12 @@ static_assert(!std::is_invocable_v<RangeEndT, int (&)[]>);
static_assert(!std::is_invocable_v<RangeEndT, int (&&)[10]>);
static_assert( std::is_invocable_v<RangeEndT, int (&)[10]>);
struct Incomplete;
static_assert(!std::is_invocable_v<RangeEndT, Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeEndT, Incomplete(&&)[42]>);
static_assert(!std::is_invocable_v<RangeCEndT, Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeCEndT, Incomplete(&&)[42]>);
struct EndMember {
int x;
constexpr const int *begin() const { return nullptr; }

View File

@ -30,6 +30,17 @@ static_assert( std::is_invocable_v<RangeEmptyT, int (&&)[1]>);
static_assert( std::is_invocable_v<RangeEmptyT, int (&)[1]>);
static_assert( std::is_invocable_v<RangeEmptyT, const int (&)[1]>);
struct Incomplete;
static_assert(!std::is_invocable_v<RangeEmptyT, Incomplete[]>);
static_assert(!std::is_invocable_v<RangeEmptyT, Incomplete(&)[]>);
static_assert(!std::is_invocable_v<RangeEmptyT, Incomplete(&&)[]>);
extern Incomplete array_of_incomplete[42];
static_assert(!std::ranges::empty(array_of_incomplete));
static_assert(!std::ranges::empty(std::move(array_of_incomplete)));
static_assert(!std::ranges::empty(std::as_const(array_of_incomplete)));
static_assert(!std::ranges::empty(static_cast<const Incomplete(&&)[42]>(array_of_incomplete)));
struct NonConstSizeAndEmpty {
int size();
bool empty();

View File

@ -25,6 +25,17 @@ static_assert( std::is_invocable_v<RangeSizeT, int[1]>);
static_assert( std::is_invocable_v<RangeSizeT, int (&&)[1]>);
static_assert( std::is_invocable_v<RangeSizeT, int (&)[1]>);
struct Incomplete;
static_assert(!std::is_invocable_v<RangeSizeT, Incomplete[]>);
static_assert(!std::is_invocable_v<RangeSizeT, Incomplete(&)[]>);
static_assert(!std::is_invocable_v<RangeSizeT, Incomplete(&&)[]>);
extern Incomplete array_of_incomplete[42];
static_assert(std::ranges::size(array_of_incomplete) == 42);
static_assert(std::ranges::size(std::move(array_of_incomplete)) == 42);
static_assert(std::ranges::size(std::as_const(array_of_incomplete)) == 42);
static_assert(std::ranges::size(static_cast<const Incomplete(&&)[42]>(array_of_incomplete)) == 42);
static_assert(std::semiregular<std::remove_cv_t<RangeSizeT>>);
struct SizeMember {