This is an archive of the discontinued LLVM Phabricator instance.

[libc++] [P0879] constexpr std::nth_element, and rewrite its tests.
ClosedPublic

Authored by Quuxplusone on Dec 18 2020, 10:53 AM.

Details

Summary

This patch is more than just adding the constexpr keyword, because the old code relied on goto, and goto is not constexpr-friendly. Refactor to eliminate goto, and then mark it as constexpr in C++20.

I freely admit that the name __nth_element_partloop is bad; I couldn't find any better name because I don't really know what this loop is doing, conceptually. Vice versa, I think __nth_element_find_guard has a decent name.

Now the only one we're still missing from P0879 is sort.

Diff Detail

Event Timeline

Quuxplusone requested review of this revision.Dec 18 2020, 10:53 AM
Quuxplusone created this revision.
Herald added a reviewer: Restricted Project. · View Herald TranscriptDec 18 2020, 10:53 AM
ldionne requested changes to this revision.Jan 25 2021, 9:14 AM

Can you please ping me once CI passes?

libcxx/include/algorithm
5281

This comment is anchored to something that doesn't exist anymore, so I would remove it.

This revision now requires changes to proceed.Jan 25 2021, 9:14 AM
libcxx/include/algorithm
5281

You mean you'd remove // i.e., goto __restart? I prefer to leave it, for the same reason we leave the existing comments like // _VSTD::__nth_element<_Compare>(__i, __nth, __last, __comp) — they show what we would put on that line if it weren't troublesome for mechanical reasons (don't want recursion, can't use goto). I left a comment above: // __restart: -- this is the target of a "continue" below

I'm very much not a fan of using continue for non-trivial control flow, anyway. But I'll take another look at this code and see if I can maybe figure out a way to eliminate the continue. For example, we could definitely just indent lines 5284–5362 one more level and stuff them into an else block.

Add "simple" tests per @ldionne's preferences. :)
I looked at ways to remove that continue; and realized that the thing I was thinking of wouldn't work. (The section I was saying I could put in an else block, would actually have to go in a separate helper function so that it could be reached along two different codepaths.) So I'd say let's ship this as-is, and let someone else refactor it if they want to.

Rebase and poke buildkite.

ldionne accepted this revision.Jan 28 2021, 7:44 AM

LGTM. CI is passing due to the extern-templates.sh.cpp test, which I broke. Go ahead.

libcxx/include/algorithm
5284

Would it make sense to replace this by a variant of std::find_if? I'd do any other refactoring in a separate patch, though, to keep this one purely mechanical.

This revision is now accepted and ready to land.Jan 28 2021, 7:44 AM
This revision was landed with ongoing or failed builds.Jan 28 2021, 8:59 AM
This revision was automatically updated to reflect the committed changes.

It looks like this is generating incorrect results. I uploaded D96074 which shows the failing test case.

Mind if I revert first, and the fix forward (with the added test case, plus probably some more) can land later?

@rupprecht: Uh-oh. :( Your suggestion to revert D93557 SGTM. It looks like it should revert cleanly.

Reverted, thanks!

When I was reducing a test case, one thing I noticed was the problem only happens for certain pivot values, e.g.:

int main() {
  std::vector<int> v = {
      0, 1, 2, 3, 4, 5, 7, 6,
  };

  for (int i = 0; i < v.size(); ++i) {
    std::vector<int> v2 = v;

    auto pivot = v2.begin() + i;
    std::nth_element(v2.begin(), pivot, v2.end());

    for (auto it = v2.begin(); it != pivot; ++it)
      if (*it > *pivot)
        std::cerr << "Bad result with pivot " << i << "\n";
    for (auto it = std::next(pivot); it != v2.end(); ++it)
      if (*pivot > *it)
        std::cerr << "Bad result with pivot " << i << "\n";
  }
}

$ clang++ /tmp/nth.cpp -stdlib=libc++ && ./a.out
Bad result with pivot 6
Bad result with pivot 7

The existing test case operates on {3,1,4,1,5, 9,2,6,5,3, 5,8,9,7,9}, which has multiple 1s and multiple 9s, so if the bug is something about incorrectly handling a pivot point at the end or right next to the end (or vice versa for the beginning), then the bug would be masked because the value would be the same in those instances. (e.g. std::nth_element of the last element is 9, but so is std::nth_element of the last - 1 element). So maybe it would be good to also include a test case that has only unique values to avoid that case. (Including a case with equal values is good too, so don't remove that test case).

Quuxplusone added inline comments.
libcxx/include/algorithm
5466

This was the bug. while (++__j != __last) should have changed to while (true), if I was going to repeat the condition here.
However, I've got a better patch now anyway, which I'm posting as D96084.