This is an archive of the discontinued LLVM Phabricator instance.

[libc++] [ranges] Fix bugs in ranges::empty().
ClosedPublic

Authored by Quuxplusone on Dec 7 2021, 6:27 PM.

Details

Summary

It was missing the cast to bool in bool(__t.empty()).
It was wrongly using std::forward in some places.

Diff Detail

Event Timeline

Quuxplusone requested review of this revision.Dec 7 2021, 6:27 PM
Quuxplusone created this revision.
Herald added 1 blocking reviewer(s): Restricted Project. · View Herald TranscriptDec 7 2021, 6:27 PM
Quuxplusone added inline comments.
libcxx/include/__ranges/empty.h
46–52

Note the sneaky requires-clause hiding in among the "write it three times" business.
I'd be willing to pull this out into a helper concept, e.g.

template<class _Tp>
concept __forward_iterable =
  forward_iterator<decltype(ranges::begin(declval<_Tp&>()))>;

template <__forward_iterable _Tp>
_LIBCPP_HIDE_FROM_ABI
static constexpr auto __go(_Tp&& __t, __priority_tag<0>)
  noexcept(noexcept(bool(ranges::begin(__t) == ranges::end(__t))))
  -> decltype(      bool(ranges::begin(__t) == ranges::end(__t)))
  { return          bool(ranges::begin(__t) == ranges::end(__t)); }

but I'm ambivalent on whether that's better.
I'm also unclear on how to observe the difference between this constraint and a simple forward_range<_Tp&>. @tcanens perhaps?

I don't especially like the fact that we're moving away from requires clauses and back to straight SFINAE. Can you check what effect this has on diagnostics?

Quuxplusone added inline comments.Dec 8 2021, 7:18 AM
libcxx/include/__ranges/empty.h
61
@ldionne wrote:

I don't especially like the fact that we're moving away from requires clauses and back to straight SFINAE. Can you check what effect this has on diagnostics?

Old: 32 lines; new: 8 lines. I would call the new version "frustratingly opaque" but the old version "annoyingly verbose."
https://godbolt.org/z/GseTh84jx
I can't think of any way to improve this. I thought of constraining operator() on requires range<decay_t<_Tp>>, but (1) ranges::empty(e) doesn't require e to satisfy range (if it provides .empty()), and (2) that doesn't help because SFINAE kicks in prior to constraint checking, and (3) the constraint doesn't help the user anyway because the failure case 99% of the time will be that e is a range but can't be checked for emptiness for some other obscure reason.

ldionne requested changes to this revision.Dec 8 2021, 7:38 AM
ldionne added inline comments.
libcxx/include/__ranges/empty.h
61

Honestly, the output after is pretty terrible. It doesn't show anything at all, so it's basically impossible to know what's going wrong. I don't think we want to use this new technique in our ranges implementation. I would rather we revert to the more verbose and arguably not so great technique we had before, where we have to manually exclude other overloads.

This revision now requires changes to proceed.Dec 8 2021, 7:38 AM
Quuxplusone edited the summary of this revision. (Show Details)

I added a late-breaking value-category-related test case to the ranges::empty test; this fixes that too.

ldionne requested changes to this revision.Dec 9 2021, 12:58 PM

Thanks for fixing those bugs. We talked about it offline and I wasn't able to come up with a significantly better solution than the explicit mutual exclusion of concepts we had before, however I'm still not really comfortable moving to this 100% SFINAE based approach due to diagnostics concerns. If we do it here, one could argue that we should do it everywhere, cause it *is* simpler from a writing-the-code perspective. But I don't want to encourage that general direction.

So IMO we should fix the bugs you're fixing without changing the approach we had, i.e. the somewhat clunky helper concepts we used to define (and which is consistent with the approach taken in the rest of ranges).

This revision now requires changes to proceed.Dec 9 2021, 12:58 PM

Review comments.
Update with my proposed new-and-improved way of getting reasonable diagnostics when the SFINAE fails.

Guess what? We had a test/libcxx/ test that was supposed to test these diagnostics, but it was testing ranges::begin instead of ranges::empty.

New-and-improved CPO design coming to all the other ranges.access CPOs, in a PR depending on this one that I'll upload shortly.

"Patch application failed" in buildkite. Rebase and try again.

Harmonize with D115607; #if 0 out some tests that will work only after D115607 lands (because they depend on ranges::begin not to hard-error in silly ways).

Does removing the old parent revision make buildkite happier about applying the patch?

Can you please:

  1. Fix the bug(s) you are fixing without using a new CPO design
  2. Open a different review that proposes a new CPO design so we can discuss it separately
Quuxplusone retitled this revision from [libc++] [ranges] Simplify and fix a bug in ranges::empty. to [libc++] [ranges] Simplify and fix bugs in ranges::empty..
Quuxplusone edited the summary of this revision. (Show Details)

Remove an unused parameter name. NFC.

Quuxplusone planned changes to this revision.Dec 15 2021, 7:28 PM

To be rebased after D115686 and D115838.

Quuxplusone edited the summary of this revision. (Show Details)
Quuxplusone added reviewers: Mordante, philnik.

Scale back to just the bugfixes I'm aware of; isolate the refactoring into a separate (and still to-be-written) PR.

Quuxplusone retitled this revision from [libc++] [ranges] Simplify and fix bugs in ranges::empty. to [libc++] [ranges] Fix bugs in ranges::empty()..Dec 22 2021, 3:09 PM
philnik accepted this revision.Dec 23 2021, 10:41 AM
philnik added inline comments.
libcxx/test/std/ranges/range.access/empty.pass.cpp
157–164 ↗(On Diff #395960)

Why the == false and == true? Seems weird.

Quuxplusone accepted this revision.Dec 23 2021, 10:46 AM

Accepting-as-libc++ to land this. (@ldionne is out; but his "requested changes" applied to the previous version of this PR, which did a lot of rewriting in addition to the bugfixes. I believe scaling it back to just the bugfixes addresses those comments sufficiently.)

libcxx/test/std/ranges/range.access/empty.pass.cpp
157–164 ↗(On Diff #395960)

As mentioned in the other review, these tests don't really verify return types at all, so here I infer that the writer was trying to sort of hint at "...and empty(e) should return bool(true), not just anything that happens to be truthy." Of course the compiler doesn't care what you're hinting at, so this isn't very useful as a test. ;) I think I'll remove these == trues and change == false to !, but in a separate NFC patch.

This revision was not accepted when it landed; it landed in state Needs Review.Dec 23 2021, 11:56 AM
This revision was automatically updated to reflect the committed changes.

LGTM post-commit. Thanks.