This is an archive of the discontinued LLVM Phabricator instance.

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

Authored by Quuxplusone on Dec 15 2021, 7:16 PM.

Details

Summary
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.

Diff Detail

Event Timeline

Quuxplusone requested review of this revision.Dec 15 2021, 7:16 PM
Quuxplusone created this revision.
Herald added 1 blocking reviewer(s): Restricted Project. · View Herald TranscriptDec 15 2021, 7:16 PM
libcxx/include/__ranges/access.h
58–59

This may be a bad comment. At least it's inconsistent between "t" and "__t".

120–124

I needed to add this requires (or, I could add -> decltype(__t + _Np) if you'd rather ;)) because otherwise this hard-errors when you try to call it on an array of incomplete type.
I actually suspect that what I've got here might hit the same https://gcc.gnu.org/bugzilla//show_bug.cgi?id=103700 that I worked around in ranges::begin. If so, then I'm not sure what we should do.

libcxx/test/libcxx/ranges/range.access/range.prim/data.incomplete.verify.cpp
23

This is calling ranges::data on a pointer variable. The expected-error here is surprising, but I didn't look into it.

35

If this were std::move(arr), then it would be required to SFINAE away, not hard-error. But it's not.

libcxx/test/libcxx/ranges/range.access/range.prim/empty.incomplete.verify.cpp
24

Every test in this file tests ranges::begin, when the test file is named empty.incomplete.verify.cpp.

Quuxplusone edited the summary of this revision. (Show Details)

Indeed it was as I feared, re that GCC bug with arrays of incomplete types. https://reviews.llvm.org/harbormaster/unit/view/1587680/

I think I have the workaround now, though.

ldionne requested changes to this revision.Dec 17 2021, 7:33 AM

I would have expected a test for SFINAE-friendliness of ranges::data on incomplete array types, but http://eel.is/c++draft/range.access#range.prim.data-2.2 says it's IFNDR. These inconsistencies in the spec are somewhat concerning.

libcxx/include/__ranges/access.h
60

Could we add another overload like this:

template <class _Tp>
      requires is_array_v<remove_cv_t<_Tp>> && !__is_complete<iter_value_t<_Tp>>
void operator()(_Tp&&) = delete;

of something of that sort? Otherwise, why don't we simply add the constraint __is_complete to this overload?

121

Why not use __is_complete? The name conveys more information than this expression IMO.

This revision now requires changes to proceed.Dec 17 2021, 7:33 AM
libcxx/include/__ranges/access.h
60

Here, I'm intentionally permitting ranges::begin(x) to return x when x is an array of incomplete type, because that seems strictly more useful than any other behavior (and it's IFNDR so we get to pick our behavior). Notice that if the type of *x becomes complete, later in the TU, we haven't violated the ODR yet. If we did what you suggest above, and made the behavior of this function dependent on the completeness of *x, then we would be violating the ODR.

See my comment on ranges::end also; that rationale also applies here. But in the ranges::end case, we don't have the option to choose "Don't violate the ODR"; there we must simply try to minimize the collateral damage.

121

I think it's valuable for us to stay away from any named entity that might be cached by the compiler — no type trait, no concept. The only thing we have to commit to (and maybe get cached) on this line is the well-formedness of ranges::end. We don't want this to have spooky effects on the well-formedness of other unrelated things as compilation of this TU proceeds (which is what would happen if we shared a dependency on __is_complete or whatever).

Remove unused helper concept __is_complete.

ldionne accepted this revision.EditedDec 20 2021, 8:36 AM

We seem to be missing a libc++ specific test for the extension that we return the start of the array when calling ranges::begin(incomplete).

LGTM except for that nit and my other comment.

libcxx/include/__ranges/access.h
120–124

I think I'd rather use -> decltype(__t + _Np) and not acknowledge explicitly that we're treating incomplete types any special way here.

This revision is now accepted and ready to land.Dec 20 2021, 8:36 AM
libcxx/include/__ranges/access.h
120–124

My above comment dates to an earlier version of the diff. IIRC, requires requires { __t + _Np; } did indeed hit https://gcc.gnu.org/bugzilla//show_bug.cgi?id=103700 , and I believe -> decltype(__t + _Np) would also hit it. I'll upload the diff again just to make sure I'm right about that (and also I'll add the new libcxx/test/libcxx/ test you asked for). If I'm right, then I'll have to continue using requires (sizeof(*__t) != 0) here. Because of https://gcc.gnu.org/bugzilla//show_bug.cgi?id=103700 , the constraint will have to be something that doesn't depend on pointer arithmetic. (Test case from 103700 demonstrating GCC 11's problem with pointer arithmetic: https://godbolt.org/z/G7e9hx5cb )

libcxx/test/libcxx/ tests.
Reintroduce decltype(__t + _Np) to prove it really doesn't work with GCC 11. Expect GCC CI to fail.

libcxx/include/__ranges/access.h
120–124

Indeed I was right about that. Using

template <class _Tp, size_t _Np>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr
auto operator()(_Tp (&__t)[_Np]) const noexcept
  -> decltype(__t + _Np)
{
  return __t + _Np;
}

we hit the GCC 11 bug: https://buildkite.com/llvm-project/libcxx-ci/builds/7339#e2494f74-3397-43d8-aa24-326e0275dd39
So I'm going to revert back to

template <class _Tp, size_t _Np>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr
auto operator()(_Tp (&__t)[_Np]) const noexcept
  requires (sizeof(*__t) != 0)
{
  return __t + _Np;
}

, wait to confirm that CI is green again, and land this, unless @ldionne interrupts that plan. :)

Revert to requires (sizeof(*__t) != 0). Will land if CI is happy with this (which I expect it will be).

Grr, it turns out that GCC (due to a lower optimization setting?) is unhappy with libcxx/test/libcxx/ranges/range.access/begin.incomplete.type.pass.cpp because we want to link and run the test but it also contains undefined references to extern arrays.

https://reviews.llvm.org/harbormaster/unit/view/1756161/

ldionne added inline comments.Dec 21 2021, 2:21 PM
libcxx/include/__ranges/access.h
120–124

At least, please add a comment like // make sure _Tp is complete

Quuxplusone marked 4 inline comments as done.Dec 21 2021, 4:36 PM
Quuxplusone added inline comments.
libcxx/include/__ranges/access.h
120–124

I feel like my super-verbose commit message should be enough... but OK, I've added a comment here.

Quuxplusone marked an inline comment as done.
Quuxplusone edited the summary of this revision. (Show Details)

Use the TU1/TU2 approach suggested by Louis for begin.incomplete.type.pass.cpp.
Add a missing test case for ranges::begin(unbounded-array-of-complete-type), which is not IFNDR; it just has to work.

Oops, rename .pass.cpp to .sh.cpp.

...And remove the new test cases for ranges::begin(static_cast<int(&)[]>(a)) and so on, again. These casts are new in C++20, so they're also new in Clang 14: Clang 12 and 13 fail the new test. So, remove them so I can land this, and then we can make a new test file for these few cases after that.

libcxx/test/libcxx/ranges/range.access/range.access.cend/incomplete.verify.cpp