[libcxx-commits] [PATCH] D143914: [libc++] Clean up pair's constructors and assignment operators
Louis Dionne via Phabricator via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Feb 17 18:59:44 PST 2023
ldionne marked 4 inline comments as done.
ldionne added a comment.
In D143914#4124996 <https://reviews.llvm.org/D143914#4124996>, @rupprecht wrote:
> Still chugging away, looks like this is going to be at least 50 breakages. Most of them are trivial to fix (e.g. `return std::make_tuple()` in a method returning `std::pair` should be `return std::make_pair()`). There are a few that are a little surprising, such as this one which doesn't seem related to tuple compatibility:
>
> void func() {
> std::map<unsigned long, std::string> m;
>
> // OK
> std::vector<std::pair<unsigned long, std::string>> v1(m.begin(), m.end());
>
> // OK before, but now: error: no matching constructor for initialization
> std::vector<
> std::pair<const unsigned long, std::reference_wrapper<std::string>>>
> v2(m.begin(), m.end());
> }
This seems to be the relevant part:
include/c++/v1/vector:427:55: note: candidate template ignored: requirement 'is_constructible<std::pair<const unsigned long, std::reference_wrapper<std::string>>, std::pair<const unsigned long, std::string> &>::value' was not satisfied [with _ForwardIterator = std::__map_iterator<std::__tree_iterator<std::__value_type<unsigned long, std::string>, std::__tree_node<std::__value_type<unsigned long, std::string>, void *> *, long>>]
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI vector(_ForwardIterator __first, _ForwardIterator __last);
^
It basically boils down to this:
#include <map>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
int main(int, char**) {
std::map<unsigned long, std::string> m;
// OK
using Pair1 = std::pair<unsigned long, std::string>;
std::vector<Pair1> v1(m.begin(), m.end());
// OK before, but not anymore
using Pair2 = std::pair<const unsigned long, std::reference_wrapper<std::string>>;
static_assert(std::is_constructible_v<Pair2, std::pair<const unsigned long, std::string> &>);
}
This used to be accepted by libc++ but isn't anymore. This was never accepted by other standard libraries though: https://godbolt.org/z/c4svx1YTG.
Incidentally, checking for `static_assert(std::is_constructible_v<Pair2, std::pair<const unsigned long, std::string> const&>);` instead (notice I made the right hand side a `const&`) never works, on all stdlib implementations: https://godbolt.org/z/M8odqfvKM.
Hence, I'd argue this is somewhat brittle and I think this is both a corner case and also something that I'd be fine with breaking. Thoughts?
So, for the next steps:
1. I want to get the `subrange` `operator= const` vs `operator=` situation understood
2. I got back our internal build results and I did see a few failures similar to what @rupprecht saw. I think this will bite a few users, however the fix in most cases should be to use `-std=c++2b` and the code will work out of the box. None of these failures seemed too hard to fix.
================
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.
+ {
----------------
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=`.
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