Skip to content

Cleanups for <flat_set>#4064

Merged
StephanTLavavej merged 11 commits intomicrosoft:feature/flat_setfrom
achabense:_Flat_set_4
Oct 3, 2023
Merged

Cleanups for <flat_set>#4064
StephanTLavavej merged 11 commits intomicrosoft:feature/flat_setfrom
achabense:_Flat_set_4

Conversation

@achabense
Copy link
Contributor

@achabense achabense commented Sep 30, 2023

Contents in this pr:

  • Drop _Compressed_pair optimization, using separate container and comparer.
    ~ I think it will be ok to drop this optimization, as the spatial gain is not significant, and it greatly complicates constructors and brings indirection when accessing members. The spatial optimization will become easily achievable (and without the final constraint) when [[no_unique_address]] becomes available. I notice that mdspan has already adopted this approach.
  • Various refactorings, including some renamings, and giving separate definitions for _Emplace[_hint]:_Multi/!_Multi, for better clarity.
  • Record some remaining issues in the current codebase (// TRANSITION ones). Some of them are really tough :|
  • Fix a bug introduced by previous pr (my fault :( The problem is that even a successful noexcept move may not empty the container.

@achabense achabense requested a review from a team as a code owner September 30, 2023 19:10
for (const auto& _Val : _Range) {
_Mycont.insert(_Mycont.end(), _Val);
}
_Mycont.insert_range(_Mycont.end(), _STD forward<_Rng>(_Range));
Copy link
Contributor Author

@achabense achabense Sep 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I thought if append_range is not supported then insert_range is unlikely too, but now I begin to believe it's a good idea to use a container method as long as it is required in container.reqmts or sequence.reqmts)). The same goes for cbegin etc.

explicit _Base_flat_set(const _Alloc& _Al) : _My_pair(_Zero_then_variadic_args_t{}, _Al) {}
explicit _Base_flat_set(const _Alloc& _Al) : _Mycont(_Al), _Mycomp() {}

// TRANSITION, an allocator-aware container may not support "C(_First, _Last, _Al)".
Copy link
Contributor Author

@achabense achabense Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is no need to use make_obj_using_allocator for C(another,al) and C(al), as these constructors are required for allocator-aware containers. I believe a container that uses_allocator_v the allocator should also support these constructors.

For C(first,last,al), C(from_range...,al) and C(ilist,al), however, I realize that make_obj_using_allocator is not capable. As a failure to construct with these arguments are considered ill-formed by make_obj_using_allocator.

https://eel.is/c++draft/allocator.uses.construction#5.4.sentence-1
Otherwise, the program is ill-formed.

But the flat_set constructors only say the container should be constructed with the allocator, then behave the same as without the allocator (inserting the range). So these constructors should be valid even if the container doesn't directly support these constructions.

Effects: Equivalent to the corresponding non-allocator constructors except that c is constructed with uses-allocator construction ([allocator.uses.construction]).

A conservative fix would be constructing the container using only _Al, then assigning the range then sorting in the constructor body. I'm not sure whether we should try to utilize these C(...,al) constructors and fall back to the conservative approach though.


(In contrast, other adapters like stack is much more direct about container construction:

template<class InputIterator, class Alloc>
stack(InputIterator first, InputIterator last, const Alloc& alloc);
Effects: Initializes c with first as the first argument, last as the second argument, and alloc as the third argument.


// modifiers
// TRANSITION, the "insert" and "erase" methods may not be able to restore the invariant, if the underlying
// container is unable to provide strong guarantee for "erase" and "insert" methods.
Copy link
Contributor Author

@achabense achabense Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to #4059. Aside from exception guarantee, I think the standard is also not clear enough about how should the invariant be restored.

If any member function in [flat.set.defn] exits via an exception, the invariant is restored.
[Note 2: This can result in the flat_set's being emptied. — end note]

Should flat_meow be unchanged, or should flat_meow be emptied? I think the standard should really clarify this. At least for erasion and single-element insertion, strong-guarantee is possible if the underlying container can provide strong-guarantee too.

auto _Comp = _Get_comp_v();
const iterator _Begin = begin();
const iterator _Old_end = _Begin + static_cast<difference_type>(_Old_size);
const iterator _New_end = end();
Copy link
Contributor Author

@achabense achabense Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two _New_end were sharing different meanings in _Erase_dupes_if_not_multi and here. Renamed to common _End here to match with other methods (the variable in _Erase_dupes_if_not_multi is removed)

stl/inc/flat_set Outdated
_Erase_dupes_if_not_multi();

_STL_INTERNAL_CHECK(_Check_sorted(cbegin(), cend()));
_STL_INTERNAL_CHECK(_Is_sorted(cbegin(), cend()));
Copy link
Contributor Author

@achabense achabense Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regaining cend is necessary, as _Erase_dupes_if_not_multi may invalidate end. I think for clarity we'd better use _Is_sorted(_Mycont.cbegin(), _Mycont.cend()) for container-scale checks. (update: introduced _Is_sorted(container) for better clarity)

(Further, should we consistently use _Mycont.[c]begin/end rather than [c]begin/end in the methods to reduce a layer of indirection?)

@@ -70,17 +65,17 @@ public:
static_assert(random_access_iterator<iterator>, "The C++ Standard forbids containers without random "
"access iterators from being adapted. See [flatset.overview].");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See [flatset.overview] is slightly problematic, as this is also for flat_multiset; not dealt with in this pr. (Is it ok to remove these references?)

_STL_INTERNAL_STATIC_ASSERT(_Keylt_transparent && is_constructible_v<_Kty, _Ty>);
_Kty _Keyval(_STD forward<_Ty>(_Val));
_STL_ASSERT(_Can_insert(_Where, _Keyval), "The conversion from the heterogeneous key to key_type should "
"be consistent with the heterogeneous lookup!");
Copy link
Contributor Author

@achabense achabense Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we believe that the most common use-case for both heterogeneous lookup and insertion is the case when key_type and heterogeneous key represents the same thing like for std::string and std::string_view, which gives correct and unambiguous conversion from the heterogeneous key to key_type.

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2363r2.html#dec_conv_comp_consistency

The current implementation works well when key_type and heterogeneous key represents the same thing, and the assertion is based on this.

It's possible to support weirder (and much less useful) cases - find(_Val) == find(_Keyval) (for example, both == end()), but lower_bound(_Val) != lower_bound(_Keyval). That will relax the assertion to only !contains(_Keyval) as required by the standard, and the _Keyval should be forwarded to another _Emplace_hint function for correct insertion position. This is, however, unfair for common cases, and will make the performance a lot worse.

(And the hint is effectively useless for inconsistent cases, as at least one full bound-search is needed for either deciding the existence of the heterogeneous key, or deciding the insertion position of _Keyval.)

What does the standard say about Conversion and comparison consistency?

@StephanTLavavej StephanTLavavej added flat_meow C++23 container adaptors enhancement Something can be improved labels Oct 1, 2023
@StephanTLavavej StephanTLavavej self-assigned this Oct 1, 2023
_Mycont.insert_range(_Mycont.end(), _STD forward<_Rng>(_Range));
}
_Restore_invariants_after_insert<false>(_Old_size);
}
Copy link
Contributor Author

@achabense achabense Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    void insert(initializer_list<_Kty> _Ilist) {
        _Insert_range<false>(_Ilist.begin(), _Ilist.end());
    }

    void insert(_Tsorted, initializer_list<_Kty> _Ilist) {
        _Insert_range<true>(_Ilist.begin(), _Ilist.end());
    }

These methods can use cont.insert(end(), ilist) for insertion; temporarily kept unchanged for tidiness.


private:
_NODISCARD bool _Check_sorted(const_iterator _It, const const_iterator _End) const {
_NODISCARD bool _Is_sorted(const_iterator _It, const const_iterator _End) const {
Copy link
Contributor Author

@achabense achabense Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be clearer to name this function as _Is_Tsorted, if T means "type-dependent".

_Tsorted->_Tsorted_tag
_Is_sorted->_Is_Tsorted
_Msg_not_sorted->_Msg_not_Tsorted

However I'm not very sure about the original meaning of T in _Tsorted...

}
void replace(container_type&& _Cont) {
_STL_ASSERT(_Check_sorted(_Cont.cbegin(), _Cont.cend()), _Msg_not_sorted);
_Clear_guard<_Base_flat_set, is_nothrow_move_assignable_v<_Container>> _Guard{this};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This optimization was valid, but I think we'd better put this off after solving the remaining invariant-safety issues in insert/erase methods. (And "bool _Noexcept = false" might not be the most suitable form.)

@achabense achabense mentioned this pull request Oct 2, 2023
15 tasks
stl/inc/flat_set Outdated
_Base_flat_set() : _My_pair(_Zero_then_variadic_args_t{}) {}
_Base_flat_set() : _Mycont(), _Mycomp() {}

// TRANSITION, "_My_comp" may need to be copied, even in move construction / assignment.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use TRANSITION for things that need to ship in main, but that we hope to improve in the future. For things that must be addressed before this feature is officially merged, it seems that we should use FIXME. The distinction is that we never allow "FIXME" to reach main.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Mentioning this for the future, but I didn't push changes here.)

@StephanTLavavej StephanTLavavej merged commit b37931c into microsoft:feature/flat_set Oct 3, 2023
@StephanTLavavej
Copy link
Member

🥞 🧹 🎉

@achabense achabense deleted the _Flat_set_4 branch October 3, 2023 02:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Something can be improved flat_meow C++23 container adaptors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants