This patch implements P0809 to add support for comparing unordered containers. It also fixes issues with the current implementations.
Details
- Reviewers
mclow.lists EricWF zoecarver ldionne - Group Reviewers
Restricted Project - Commits
- rGbd095b5c4d85: [libc++] Add tests for P0809 (Comparing Unordered Containers)
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Maybe I should add some fail tests for pre-c++20?
include/unordered_map | ||
---|---|---|
1641 ↗ | (On Diff #198960) | Was it ever necessary for the two maps to use the same allocator type (and is it now)? Furthermore, is it even necessary for them to have the same key/value type (I don't see anything in the standard that explicitly says they need to be)? |
1690 ↗ | (On Diff #198960) | I can add some tests for this. Wanted to make sure it is correct first. |
include/unordered_map | ||
---|---|---|
1641 ↗ | (On Diff #198960) | All the container comparisons in the standard are defined in terms of comparing two containers of the same type. For allocator-aware containers, this includes the allocators. There is a proposal to relax that in flight (https://wg21.link/P0805), but I don't think that will be part of C++20. My suggestion is to not support heterogenous comparisons at this time. |
test/std/containers/unord/unord.map/comp_different_hash.pass.cpp | ||
34 ↗ | (On Diff #198960) | I don't think that this is what P0809 meant. "Hashes with different behavior", yes. |
include/unordered_map | ||
---|---|---|
1690 ↗ | (On Diff #198960) | I like this implementation. It's straightforward, easy to read, and looks correct. |
include/unordered_map | ||
---|---|---|
1656 ↗ | (On Diff #198960) | This can be simplified for unordered_map, because unordered_map requires unique keys. You can just use __y.find instead of equal_range and a single (Still need equal_range etc for unordered_multimap, though) |
include/unordered_set | ||
968 ↗ | (On Diff #198960) | Like the unordered_map code, this can be made much simpler because there are unique keys. |
1006 ↗ | (On Diff #198960) | No need for is_permutation, again. |
include/unordered_map | ||
---|---|---|
1641 ↗ | (On Diff #198960) | I will keep an eye on that paper. As per your below comment, I will get rid of this. |
1656 ↗ | (On Diff #198960) | Good idea! I will change this. |
include/unordered_set | ||
968 ↗ | (On Diff #198960) | Good catch. Will update (same with plain unordered_set). |
1006 ↗ | (On Diff #198960) | The standard specifically says to use is_permutation (for both set and multiset, etc.). |
test/std/containers/unord/unord.map/comp_different_hash.pass.cpp | ||
34 ↗ | (On Diff #198960) | While the paper doesn't explain it specifically, I think you are right. Because of the part that is removed which defines the behavior and not type. I can update it :P |
include/unordered_set | ||
---|---|---|
1006 ↗ | (On Diff #198960) | Yeah, I see that. I'm reviewing that and the original papers. I'll get back to you. |
I (now) realize that I'm giving you inconsistent advice. (re is_permutation and the non-multi containers).
Sorry about that; I'll get back to you on that after some research.
include/unordered_map | ||
---|---|---|
2274 ↗ | (On Diff #199147) | From my tests, simply using is_permutation here is quite a bit faster than the previous method of comparison. I think that both bits of code are functionally equivalent (but let me know if that is not the case). |
- remove all modification to unordered_{set,map}
- add tests
Thank you @mclow.lists for writing most of the tests below.
I know that Louis and Eric disagree, but I'd rather have four tests, one for each of the unordered containers, rather than just one.
(unless someone wants to step up and re-do all the container tests, and I'm not hearing any enthusiasm for that).
test/std/containers/unord/comp_different_hash.pass.cpp | ||
---|---|---|
49 ↗ | (On Diff #199353) | I don't think that this does what what you want anymore. |
71 ↗ | (On Diff #199353) | The first time through the loop you are dereferencing p2, which is equal to end. |
It would be cool to land it. I know that Arthur marked it as "Nothing To Do" already, but to the best of my knowledge there are no tests for that. And your tests seem exhaustive.
Nit: in the summary, the phrase "It also fixes issues with the current implementations." seems outdated.
libcxx/test/std/containers/unord/unord.set/comp_different_hash.pass.cpp | ||
---|---|---|
106 ↗ | (On Diff #215799) | For completeness, you should test operator!= as well (here and in other tests). |
libcxx/test/std/containers/unord/unord.set/comp_different_hash.pass.cpp | ||
---|---|---|
64 ↗ | (On Diff #215799) | FWIW, I'd rather see something simpler here. As simple as providing hash_mod_two { return val % 2; } and hash_mod_three { return val % 3; }. I don't think it buys anything to have 5 different hashes as opposed to 2, nor to have partial specializations for T* of which the only one that's used is T=char. In fact I think the whole void test() could be just [WARNING: UNTESTED CODE] using Hash = int(*)(T); std::unordered_set<int, Hash> c1({1, 2, 3, 4, 6, 7, 8}, 0, +[](int x) { return x % 2; }); std::unordered_set<int, Hash> c2({1, 2, 3, 4, 6, 7, 8}, 0, +[](int x) { return x % 3; }); std::unordered_set<int, Hash> c3({1, 2, 3, 5, 6, 7, 8}, 0, +[](int x) { return x % 3; }); assert(!std::equal(c1.begin(), c1.end(), c2.begin(), c2.end())); assert(std::is_permutation(c1.begin(), c1.end(), c2.begin(), c2.end())); assert(c1 == c2); assert(!(c1 != c2)); assert(!std::is_permutation(c1.begin(), c1.end(), c3.begin(), c3.end())); assert(c1 != c3); assert(!(c1 == c3)); followed by a test for non-default equivalence predicate: struct EqualMod6 { bool operator(int x, int y) const { return x % 6 == y % 6; } }; using Hash = int(*)(T); std::unordered_set<int, Hash, EqualMod6> c1({1, 2, 3, 4, 6}, 0, +[](int x) { return x % 2; }); std::unordered_set<int, Hash, EqualMod6> c2({1, 8, 9, 4, 6}, 0, +[](int x) { return x % 3; }); assert(!std::equal(c1.begin(), c1.end(), c2.begin(), c2.end())); assert(!std::is_permutation(c1.begin(), c1.end(), c2.begin(), c2.end())); assert(!std::equal(c1.begin(), c1.end(), c2.begin(), c2.end(), EqualMod6())); assert(std::is_permutation(c1.begin(), c1.end(), c2.begin(), c2.end(), EqualMod6())); assert(c1 == c2); assert(!(c1 != c2)); ( https://en.cppreference.com/w/cpp/container/unordered_set/operator_cmp is pretty ugly but I think it makes sense what it's saying.) |