[libc++] No longer support ranges::begin(x) when x is an array of incomplete type.

var-const points out that `ranges::begin` is (non-normatively
but explicitly) always supposed to return a `std::input_or_output_iterator`,
and `Incomplete*` is not a `std::input_or_output_iterator` because it
has no `operator++`. Therefore, we should never return `Incomplete*`
from `ranges::begin(x)`, even when `x` is `Incomplete(&)[]`. Instead,
just SFINAE away.

Differential Revision: https://reviews.llvm.org/D118963
This commit is contained in:
Arthur O'Dwyer 2022-02-03 21:41:30 -05:00
parent c67c9cfe3f
commit cc1d02ba2d
3 changed files with 30 additions and 81 deletions

View file

@ -59,10 +59,17 @@ namespace __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
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[]) const noexcept
requires (sizeof(_Tp) != 0) // Disallow incomplete element types.
{
return __t;
return __t + 0;
}
template <class _Tp, size_t _Np>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[_Np]) const noexcept
requires (sizeof(_Tp) != 0) // Disallow incomplete element types.
{
return __t + 0;
}
template <class _Tp>
@ -127,7 +134,7 @@ namespace __end {
public:
template <class _Tp, size_t _Np>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp (&__t)[_Np]) const noexcept
requires (sizeof(*__t) != 0) // Disallow incomplete element types.
requires (sizeof(_Tp) != 0) // Disallow incomplete element types.
{
return __t + _Np;
}

View file

@ -1,75 +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
//
//===----------------------------------------------------------------------===//
// 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());
return 0;
}
#endif // defined(TU1)

View file

@ -31,9 +31,26 @@ 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<RangeBeginT, const Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeCBeginT, Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeCBeginT, Incomplete(&&)[42]>);
static_assert(!std::is_invocable_v<RangeCBeginT, const Incomplete(&&)[]>);
static_assert(!std::is_invocable_v<RangeBeginT, Incomplete(&&)[10]>);
static_assert(!std::is_invocable_v<RangeBeginT, const Incomplete(&&)[10]>);
static_assert(!std::is_invocable_v<RangeCBeginT, Incomplete(&&)[10]>);
static_assert(!std::is_invocable_v<RangeCBeginT, const Incomplete(&&)[10]>);
// This case is IFNDR; we handle it SFINAE-friendly.
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeBeginT, Incomplete(&)[]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeBeginT, const Incomplete(&)[]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCBeginT, Incomplete(&)[]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCBeginT, const Incomplete(&)[]>);
// This case is IFNDR; we handle it SFINAE-friendly.
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeBeginT, Incomplete(&)[10]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeBeginT, const Incomplete(&)[10]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCBeginT, Incomplete(&)[10]>);
LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCBeginT, const Incomplete(&)[10]>);
struct BeginMember {
int x;