[libcxx-commits] [PATCH] D87171: Implement P0919R3

Ruslan Arutyunyan via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Wed Oct 28 04:42:29 PDT 2020


rarutyun marked 5 inline comments as done.
rarutyun added inline comments.


================
Comment at: libcxx/test/support/is_transparent.h:102
+
+struct Comp2Int { // convertible from int
+  Comp2Int() : i_(0) {}
----------------
ldionne wrote:
> rarutyun wrote:
> > ldionne wrote:
> > > rarutyun wrote:
> > > > ldionne wrote:
> > > > > Could you make this maintain a pointer to an `int` stored externally instead of having a static data member? This will make it easier to port the test to `constexpr` in the future. I've had to fix a lot of those while working on constexpr allocators and vector :)
> > > > I think I understand the idea. Sure, I can make that.
> > > Basically, it would look like this:
> > > 
> > > ```
> > > struct Comp2Int { // convertible from int
> > >   Comp2Int(int* conversions_count) : i_(0), conversions_count_(conversions_count) {}
> > >   Comp2Int(int i, int* conversions_count) : i_(i), conversions_count_(conversions_count) { ++*conversions_count_; }
> > >   int get_conversions_count() { return *conversions_count_; }
> > >   operator int() const noexcept { return i_; }
> > > 
> > > private:
> > >   int* conversions_count_;
> > >   int i_;
> > > };
> > > ```
> > > 
> > > `reset_conversions_count` should not be relevant anymore.
> > > 
> > > Then, from the tests, you create a local `int conversions = 0` and pass the address of that to `Comp2Int`.
> > > 
> > I actually thought about that in a different manner. But what I had in mind also doesn't help with the `constexpr` use-case I guess.
> > 
> > With what you suggest I don't understand how to make `Comp2Int` class implicitly constructible from `int`. I just pass one `int` value to the `find` or `count` (or whatever) interface. This `int` argument won't match the constructor with two arguments.
> > 
> > 
> Ah, yeah, that makes it impossible. If you're using `int`.
> 
> However, what we can do is:
> 
> ```
> template <typename T>
> struct CountConversions {
>   CountConversions(T value, int* counter) : ... { }
>   operator T() const { ++*conversions_; return value_; }
> 
> private:
>   T value_;
>   int* conversions_;
> };
> 
> template <typename T>
> struct ComparableTo {
>   ComparableTo() = default;
>   ComparableTo(T value) : value_(value) { }
> 
>   friend bool operator==(T const& lhs, ComparableTo const& rhs) {
>     return lhs == rhs.value_;
>   }
> 
>   friend bool operator==(ComparableTo const& lhs, T const& rhs) {
>     return lhs.value_ == rhs;
>   }
> 
>   friend bool operator==(ComparableTo const& lhs, ComparableTo const& rhs) {
>     return lhs.value_ == rhs.value_;
>   }
> 
> private:
>   T value_;
> };
> ```
> 
> Then, use:
> 
> ```
> std::unordered_set<ComparableTo<int>, transparent_hash, std::equal_to<>> set = {
>   ComparableTo<int>(1),
>   ComparableTo<int>(2),
>   ComparableTo<int>(3)
> };
> 
> int conversions = 0;
> assert(c.contains(CountConversions(1, &conversions)));
> assert(c.contains(CountConversions(2, &conversions)));
> assert(conversions == 0);
> assert(!c.contains(CountConversions(4, &conversions)));
> assert(conversions == 0);
> ```
> 
> I haven't tested this, but I think it should work.
> 
No, it doesn't. You still have conversions in the scenario above. If you create the `unordered_set<ComparableTo<int>>` then the `T = int` then the hidden friends have `int` as well. When the heterogeneous API is called it calls `operator==` that causes `CountConversions` to be converted to `int` and it makes `conversions_` to be incremented. Of course the test fails.

I think that if we create `ComparableTo<CountConversions<int>>` that might actually work but you need to teach the `operator==` to work with such types. But the biggest problem is you should create `int conversions` variable in places where you want to initialize the container but you don't need it, it's just dummy parameter to initialize the container of `ComparableTo<CountConversions<int>>`.

Having all of that in mind I made a step further and seems like I found the way how to test the containers with passing external reference counter. I uploaded the patch with that approach. It allows to avoid dummy `int conversions` definition when container is initialized. It also tests the constructor of `KeyType`, which IMO better fits the testing expectations rather then conversion operator of heterogeneous `Key`.


================
Comment at: libcxx/test/support/is_transparent.h:107
+  static void reset_conversions_count() { conversions_count = 0; }
+  operator int() const noexcept { return i_; }
+
----------------
ldionne wrote:
> Is this necessary for testing, or only to implement the comparison operators below and the hashing above? If the latter, I would much rather remove this as it makes things harder to understand -- this type focuses on being comparable with `int`, and constructible from `int`.
> 
> Edit: Please try using the definitions I've provided above and let me know if that works -- if it does, I think it would be simpler and wouldn't require a conversion operator for the type stored inside the container.
Yes, it was necessary to implement hash above and allow this type to be used in `std::hash<int>`. Due to all of these wrappers I've added we cannot use `std::hash<int>` anyway. But we still need to calculate the hash for both `ComparableTo` and `CounterWrapper`. So, I added `get()` method to both. The other approach is `friend` but I don't think that it's better


================
Comment at: libcxx/test/support/test_transparent_unordered.h:20
+          typename Equal>
+using unord_set_type = UnorderedMap<Comp2Int, Hash, Equal>;
+
----------------
ldionne wrote:
> Now I understand what you meant in your previous comment. I don't feel like these aliases buy us a lot of readability -- I would prefer removing them.
> 
> Also, they seem to be reversed (`unord_set_type = UnorderedMap<...>` seems wrong).
Yes, thanks for noticing. It's just name swapping. I probably agree that they don't bring much readability but what the improve is maintainability. I was needed to rewrite the tests on the new approach. Those aliases helped me to do that easier. I would prefer to leave them. They also help to change `KeyEqual` and `Hash` types without listing others.

If you insist I may remove them but my opinion that they bring value.


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D87171/new/

https://reviews.llvm.org/D87171



More information about the libcxx-commits mailing list