- Quality-of-implementation: Avoid calling __unwrap_iter in constexpr contexts. The user might conceivably write a contiguous iterator where normal iterator arithmetic is constexpr-friendly but std::to_address(it) isn't.
- Bugfix: When you pass contiguous iterators to std::copy, you should get back your contiguous iterator type, not a raw pointer. That means that libc++ can't __unwrap_iter unless it also does __rewrap_iter. Fortunately, this is implementable.
- Improve test coverage of the new contiguous_iterator test iterator. This catches the bug described above.
- Tests: Stop testing that we can std::copy into an input_iterator. Our test iterators may currently support that, but it seems nonsensical to me.
Details
- Reviewers
rnk ldionne mclow.lists EricWF - Group Reviewers
Restricted Project - Commits
- rG85167fb7c292: [libc++] Further improve the contiguous-iterator story, and fix some bugs.
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
libcxx/include/algorithm | ||
---|---|---|
1649 | It's been pointed out to me that this is fine; std::copy is required to have the constexpr keyword on the front, but it's never required to actually be constexpr. libc++ can say "std::copy is constexpr unless you give us a contiguous iterator with a non-constexpr to_address and/or operator->." | |
1653 | I still lack any great way to work around this one, though. I think if we want to use __unwrap_iter(first) on arbitrary user-provided contiguous iterators in copy, then we'll have to separately compute the return value using something like template<class _OrigIter, class _UnwrappedIter> _Iter __rewrap_iter(_OrigIter __first, _UnwrappedIter __result) { static_assert(__is_cpp17_contiguous_iterator<_OrigIter>, ""); // otherwise we won't get here return __first + (__result - _VSTD::__unwrap_iter(__first)); } template<class _OrigIter> _Iter __rewrap_iter(_OrigIter, _OrigIter __result) { return __result; } [...] return _VSTD::__rewrap_iter(__result, _VSTD::__copy(_VSTD::__unwrap_iter(__first), _VSTD::__unwrap_iter(__last), _VSTD::__unwrap_iter(__result))); This mostly destroys our hopes of "reducing the number of template instantiations"; but it does permit us to keep using memmove for user-defined contiguous iterators. |
libcxx/include/algorithm | ||
---|---|---|
1656–1658 | I don't understand this change - didn't you work around the constexpr problem by *not* calling unwrap_iter on iterators when we're in a constexpr context? Edit: Ah, I think you're trying to work around the *second* problem you pointed out, which is that you can't automatically re-create an arbitrary user-defined iterator from a raw pointer. Right? A solution for that could be to:
That way, if the iterator isn't implicitly constructible from the pointer, a user like Chrome can still get the optimization by specializing the struct to tell libc++ how to get an iterator back from a pointer. And by default, it'll work for most iterators out of the box. Thoughts on that solution? The only issue I can see is if implicit construction from a raw pointer is syntactically valid but doesn't do what we think it should do. I'm not sure how likely or dangerous that could be, but I would argue that if a contiguous iterator is constructible from a pointer but that constructor doesn't do the right thing, then you really voided your warranty with the Standard Library. |
Also, if we go down the road of adding a __rewrap_iter struct, can you add a test exercising that extension point under libcxx/?
Requesting changes for the review queue.
libcxx/include/algorithm | ||
---|---|---|
1656–1658 |
I don't think we need __rewrap_iter to be a customization point at all. A contiguous iterator (even a user-defined one) is always a random-access iterator too, so we can use __first + (__result - _VSTD::__unwrap_iter(__first)) as I indicated in my Phab comment a few lines above this. (If __unwrap_iter is a no-op, meaning that _OrigIter wasn't a contiguous iterator, just return __result unmodified.)
I strongly agree in fantasy-everything-as-it-should-be-land, but I'm less sure in actual-C++-land. Remember this is the language that invented std::pointer_traits<It>::to_address(it) and std::pointer_traits<It>::pointer_to(ptr) instead of just using static_cast<T*>(it) and static_cast<It>(ptr). |
libcxx/include/algorithm | ||
---|---|---|
1656–1658 | The issue with this approach is that it requires the unwrapped iterator to be reachable from the original iterator. That works for the algorithms we want to apply it to, but I could imagine other circumstances where that doesn't work, say if we're allocating a new range. |
It's been pointed out to me that this is fine; std::copy is required to have the constexpr keyword on the front, but it's never required to actually be constexpr. libc++ can say "std::copy is constexpr unless you give us a contiguous iterator with a non-constexpr to_address and/or operator->."