Various bugfixes / enhancements for <flat_set>#3985
Various bugfixes / enhancements for <flat_set>#3985achabense wants to merge 20 commits intomicrosoft:feature/flat_setfrom
<flat_set>#3985Conversation
This comment was marked as outdated.
This comment was marked as outdated.
|
It seems the standard doesn't say what |
May be specified in [associative.reqmts.general]/49. But it seems to me that |
This comment was marked as resolved.
This comment was marked as resolved.
| const iterator _End = end(); | ||
| const iterator _Where = lower_bound(_Val); | ||
| const const_iterator _End = end(); | ||
| const const_iterator _Where = lower_bound(_Val); |
There was a problem hiding this comment.
see below.iterator is not convertible to const_iterator (return type) in many cases.
There was a problem hiding this comment.
Can you explain a bit? I thought it was required to have iterator convertible to const_iterator https://eel.is/c++draft/container.reqmts#6
There was a problem hiding this comment.
Ugh, my mistake; correction: const_iterator is not required to convert to iterator.
Update: the 3rd commit doesn't "fix" anything (but can be seen as nitpick/enhancement).
(There are real iterator fixes, see 4th commit (it's returning _Where in a branch which is a const_iterator, but the function should return an iterator) )
stl/inc/flat_set
Outdated
| return _Cont.insert(_Where, _STD forward<_Ty>(_Val)); | ||
| } | ||
| return _Where; | ||
| return _Cont.begin() + (_Where - _Begin); |
There was a problem hiding this comment.
I'm just making this line work (convert const_iterator to iterator); may need further improvements in the future (hopefully after the implementation settling down).
|
Lines 498 to 500 in d6e6b5a There are several places that, due to the nature of lower_bound, the usage of _Keys_equal (!_Compare(_Lhs, _Rhs) && !_Compare(_Rhs, _Lhs);) can be simplified to a single comparision (see std::binary_search for example). However this will make it harder to review. I think we can do these changes as enhancements after the implementation settling down.
|
|
Lines 137 to 141 in d6e6b5a I'm afraid this is not observable from flat_set/multiset, as flat_set/multiset is currently not using base class's operator=
Update: fixed by 10th commit. |
| auto insert(const value_type& _Val) { | ||
| return _Insert<_Kty>(_Val); | ||
| return _Emplace(_Val); | ||
| } |
There was a problem hiding this comment.
Before this change, even the following code will fail to compile:
flat_set<int> s;
int i = 1;
s.insert(i);As, a const _Kty& is not convertible to _Kty&& previously in _Insert...
| } | ||
|
|
||
| template <class _Ty> | ||
| requires (!_Multi && _Keylt_transparent && is_constructible_v<_Kty, _Ty>) || is_same_v<_Ty, _Kty> |
There was a problem hiding this comment.
about this removal:
- it's enough to constrain in the caller site (
insert/emplace). - shouldn't be
is_same_v<_Ty, _Kty>, as_Tycan beconst _Kty&
<flat_set>: _Base_flat_set's _Compressed_pair should take key_compare as its first argument<flat_set>
|
As to this function, I can imagine it causing surprises in practice. I think we should try to add debug check in the future (after the implementation settling down). template <class _Other>
requires (!_Multi && _Keylt_transparent && is_constructible_v<_Kty, _Other>)
auto insert(_Other&& _Val) {
return _Emplace(_STD forward<_Other>(_Val));
}
Also see this comment. |
|
Lines 548 to 550 in d6e6b5a Is it always correct to use const key_compare&? Is BinaryPredicate allowed to offer different behaviors based on key_compare& vs const key_compare&?
That is, is struct qux {
bool operator()(int a, int b) {
return a < b;
}
bool operator()(int a, int b)const {
return a > b;
}
}; |
… to keep in line with the standard
… and `_Emplace_hint` should not be constrainted. see comments.
…; merge `_Sort_potentially_sorted`
be0c29e to
14a58cd
Compare
|
I've force-pushed this pr to add serial numbers for each commit description. Nothing else is changed. |
| @@ -132,9 +119,74 @@ void test_constructors() { | |||
| flat_set<int> a{}; | |||
| a = {1, 7, 7, 7, 2, 100, -1}; | |||
There was a problem hiding this comment.
Before this pr, this was passing as the initializer_list was implicitly converted to flat_multiset and operator=(flat_set&&) was called here. Now I can make sure this is calling operator=(initializer_list)
|
Folowup to this comment: Hmm, is this UB or another implementation bug? Update: I think this example deserves some extra attention, as it shows the precondition is fairly tricky, and implies potential defects in the current implementation. At least I think we should try to catch such errors in debug mode. Update: (-update re-implemented in 16th commit) |
This comment was marked as resolved.
This comment was marked as resolved.
…e_hint`; use `const key_compare&` consistently; test nitpick
…_set::insert([hint,]auto&&)`
| return _Emplace(_Kty{_STD forward<_Args>(_Vals)...}); | ||
| constexpr bool _Is_key_type = _In_place_key_extract_set<_Kty, remove_cvref_t<_Args>...>::_Extractable; | ||
| if constexpr (_Is_key_type) { | ||
| return _Emplace(_STD forward<_Args>(_Vals)...); |
There was a problem hiding this comment.
As to emplace and emplace_hint:
When input is (const)_Kty(&/&&), I believe there shouldn't be a temporary before insertion, even for flat_multiset; especially, for flat_set a _Kty&& input should not be moved to a temporary if insertion fails.
For example, the following code holds true for set and unordered_set, and should hold true for flat_set as well.
Set<shared_ptr<int>> s;
auto a = shared_ptr<int>(new int);
auto b = a, c = a;
assert(a == b && a == c);
assert(!(a < b) && !(b < a));
s.insert(a);
s.emplace(move(b));
assert(b != nullptr);
s.insert(move(c));
assert(c != nullptr);| _STL_ASSERT(lower_bound(_Keyval) == _Where && !_Keys_equal(_Keyval, *_Where), | ||
| "find(val) was not equal to find(key_type{forward(val)})"); | ||
| return pair{_Cont.emplace(_Where, _STD move(_Keyval)), true}; | ||
| } |
There was a problem hiding this comment.
This is for debug of flat_set::insert([hint,]auto&&); there are fairly dangerous use patterns like:
std::flat_set<int, std::less<>> s{ 1,2,3,5,7 };
s.insert(2.5);Which can break invariant unexpectedly. See details in this comment.
Otherwise, this is actually equivalent to the if constexpr (is_same_v<remove_cvref_t<_Ty>, _Kty>) branch (as _Cont.emplace(_Where, _STD forward<_Ty>(_Val)) can do the same thing).
Due to the trouble it introduces, I believe flat_set::insert([hint,]auto&&) should be renamed in the standard to avoid accidental misuse (or at least explained in more details).
There was a problem hiding this comment.
I don't get why this is dangerous. Wouldn't 2.5 be converted to 2 first and cause the insertion to fail? I tested the 2.5 insertion with flat_map and the flat_map was unchanged.
There was a problem hiding this comment.
find(2.5) would get end(); find(int(2.5))->find(2) would get 2; this is precondition violation and is UB.
See this link
There was a problem hiding this comment.
For flat_set::emplace, I think a conversion to the key_type is required before insertion, as this is explicitly required in flat_multiset::emplace:

Conversion happens before insertion even if flat_multiset can have transparent comparators...

This means I cannot call emplace[_hint]() directly for insert(auto&&), as in emplace, for non-key types I need to firstly convert auto&& to a key_type, which may change the value of auto&&.
I think [flat.set.modifiers] should explicitly define flat_set::emplace...
There was a problem hiding this comment.
find(2.5) whould get
end(); find(int(2.5))->find(2) would get 2; this is precondition violation and is UB.See this link
So the implementation is not wrong then ... ? It's just counter-intuitive?
There was a problem hiding this comment.
I'm still not confident whether the implementation is correct for insert([hint,]auto&&); but yes, s.insert(2.5); is just UB behavior so has nothing to do with implementation correctness; I think this method is just too tricky and can cause a lot of surprises in practice.
| } | ||
| } else { | ||
| // _Val < (*_Where - 1) | ||
| _Where = _STD upper_bound(_Begin, _Where, _Val, _Compare); |
There was a problem hiding this comment.
This line was making the following test failing:
flat_set<int, lt, C> a{0, 5};
assert_all_requirements_and_equals(a, {0, 5});
a.emplace_hint(a.end());
assert_all_requirements_and_equals(a, {0, 5}); // < got {0, 0, 5}!flat_set should always use lower_bound for correctness; flat_multiset should use upper_bound for conformance and efficiency.
| if constexpr (_Multi) { | ||
| _STL_INTERNAL_STATIC_ASSERT(is_same_v<remove_cvref_t<_Ty>, _Kty>); | ||
| return _Cont.emplace(_Where, _STD forward<_Ty>(_Val)); | ||
| return _Cont.emplace(upper_bound(_Val), _STD forward<_Ty>(_Val)); |
… and some other nitpicks
|
For ease of review, I've opened #3993 to replace this one. |


This pr has been replaced by pr 3993.This pr is becoming large and hard to review, I'm going to replace this pr with a new one with all commits re-grouped when it is finished.This pr contains a lot of bugfixes/enhancements for
<flat_set>:About each commit:
1st: Fix usage of_Compressed_pair._Compressed_pairtries to compress its first member, but the implementation was erroneously trying to compress the container instead ofkey_compare.2nd: Add#pragma pack(push, _CRT_PACKING)etc.3rd: (This turns out to be merely a nitpick. see this comment.)4th:_Emplace_hintshould returniteratorinstead ofvoid, andconst_iterator _Wheremay be not be convertible toiterator. (As toconst const_iterator _Begin = cbegin(); const const_iterator _End = cend();, they are enhancements to match with_Where.)5th: Nitpicks to enhance constness.6th: Regroup/reorder non-constructor methods to match with the standard.insert(initializer_list)is found missing.7th: Implementinsert(initializer_list).8th: Improve implementation ofcontainsby usingbinary_search.9th: Fixinsertmethods. Especially,insert(const _Kty&)was broken before this commit.10th:operator=(initializer_list)was not observable fromflat_set/multiset.flat_set/multisetshouldusing _Mybase::operator=;. (this turns out to be problematic; fixed by 14th commit)11th: Various cleanups.12th:insert(const iter&, const iter&)should beinsert(iter, iter)13th: Implement LWG-3884 (*flat_foo is missing allocator-extended copy/move constructors*), as applied in N4958.14th: Fix 10th commit. Add tests for insertion methods, the new constructor and ebco optimization;test_insertis a general test covering commit4, 7, 9 and 12.15th:emplaceforgot to return the result of_Emplace; useconst key_compare&for comparision consistently; some other nitpicks.16th:emplaceandemplace_hintshould not construct temporary forkey_type(so that akey_type&&will not bemoved away if insertion fails); try to add debug check forflat_set::insert([hint,]auto&&).17th: For conformance and efficiency,flat_multiset::emplaceshould useupper_boundinstead oflower_bound.18th: Fix incorrect bound-finding behavior ofemplace_hint(flat_setshould always look forlower_bound); update test.