[libcxx-commits] [PATCH] D143914: [libc++] Clean up pair's constructors and assignment operators
Tim Song via Phabricator via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Feb 17 19:53:31 PST 2023
tcanens added inline comments.
================
Comment at: libcxx/test/std/utilities/utility/pairs/pairs.pair/assign.pair_like_rv_const.pass.cpp:57
+
+ // Make sure it doesn't work for `ranges::subrange`, since it is called out explicitly.
+ {
----------------
ldionne wrote:
> huixie90 wrote:
> > tcanens wrote:
> > > ldionne wrote:
> > > > @tcanens @huixie90
> > > >
> > > > This behavior seems to be inconsistent with the non-const-qualified `operator=` from `pair-like`. For `operator=(PairLike)` (not `const`), it seems like we go through `subrange`'s `operator pair-like()`, which makes this work. But with `operator=(pair-like) const`, it looks like this conversion doesn't trigger and we end up not being able to perform the assignment. Does that match the LWG design intent?
> > > It doesn't trigger because `ConstAssignable` is not convertible from `int*`? Are the const/non-const cases actually different?
> > >
> > > IIRC the constraint is there to prevent bypassing the slicing check on subrange's `operator pair-like`.
> > speaking of `subrange`, I think it is worth adding more tests such as
> > ```
> > Pair p = subrange1;
> > Pair p{subrange1};
> > auto p = static_cast<Pair>(subrange);
> > ```
> > where the pair's constructor and subrange's conversion operator both can work.
> > (apparently subrange's conversion operator is more constrained than pair's constructor but I think that does not change anything?)
> @tcanens
>
> > It doesn't trigger because ConstAssignable is not convertible from int*
>
> Ah, yes, obviously that's *one* problem. But even after fixing it, the behavior for const and non-const is different AFAICT:
>
> ```
> // const assignment
> struct ConstAssignable {
> mutable int* ptr = nullptr;
> ConstAssignable() = default;
> constexpr ConstAssignable(int* p) : ptr(p) { } // enable `subrange::operator pair-like`
> constexpr ConstAssignable const& operator=(int* p) const { ptr = p; return *this; }
> };
>
> static_assert( std::is_assignable_v<std::pair<ConstAssignable, ConstAssignable> const&, std::tuple<int*, int*>>); // test the test
> static_assert(!std::is_assignable_v<std::pair<ConstAssignable, ConstAssignable> const&, std::ranges::subrange<int*>>);
>
> // non-const assignment
> struct Assignable {
> int* ptr = nullptr;
> Assignable() = default;
> constexpr Assignable(int* p) : ptr(p) { } // enable `subrange::operator pair-like`
> constexpr Assignable& operator=(int* p) { ptr = p; return *this; }
> };
> int data[] = {1, 2, 3, 4, 5};
> std::ranges::subrange<int*> a(data);
> std::pair<Assignable, Assignable> p;
> p = a; // that's fine
> ```
>
> Did I miss something else here? There must be something else going on that causes the `operator pair-like` for `std::subrange` to not trigger when the elements are const-assignable but not "just" assignable. Honestly, I am left wondering whether a `const` `operator=` triggers the same sequence of implicit conversions than the non-`const` `operator=`.
>
The difference is that `ConstAssignable` isn't const-assignable from `ConstAssignable` (but `Assignable` is assignable from `Assignable`).
`operator pair-like` converts the subrange to a `std::pair<ConstAssignable, ConstAssignable>` (or std::pair<Assignable, Assignable>`) and the assignment is performed from that, so you need const-assignment from `ConstAssignable`, not `int*`.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D143914/new/
https://reviews.llvm.org/D143914
More information about the libcxx-commits
mailing list