1. Changelog
-
R2 (post-LEWG-telecon, pre-Tokyo 2024):
-
Vastly reorder, and separate the sections into "LWG business" and "LEWG business." Combine all the subsections that would be resolved at once by § 5 (LEWG) Monolithic proposal: tag narrow-contract functions, remove container ctors.
-
Replace many snippets with Tony Tables for clarity.
-
Wording nits.
-
-
R1 (post-Varna 2023):
-
LWG approved §3 as editorial.
-
LWG reviewed §4,5,6,7. Update those sections' rationales and proposed wordings.
-
Split § 4.4 Efficient flat_multiset::insert(K&&) and § 5.1 Deceptive list-initialization from two lists out of their respective sections.
-
-
R0 (pre-Varna 2023):
-
Initial draft.
-
2. Introduction
Arthur has implemented all of [P0429]
and [P1222]
for libc++.
As he implemented them, he and Louis Dionne collected issues that libc++ would like to see resolved
by LWG. This paper presents all of these issues together in one place, along with Arthur’s proposed
solutions for each one.
Some of the proposed solutions are LEWG-level design changes. Contrariwise, some of the issues collected here don’t have "solutions" at all, but are recorded merely For Your Information (for other vendors/implementors) to document the design choices libc++ has made.
3. LWG business
3.1. Editorial (merged in R0)
P2767R0’s editorial change was reviewed by LWG in Varna (2023-06-16) and approved by a vote of 7–0–1 (minutes); it is submitted as #6274. Jonathan Wakely is on the hook to approve and merge it.
Once this editorial business is merged, the rest of the diffs presented in this paper will apply cleanly.
3.2. Accidentally explicit constructor
STL style is that multi-argument constructors should be non-
; see [P1163].
This change is non-editorial, but non-controversial. LWG reviewed R0’s wording in
Varna and expressed a preferred direction.
Zhihao Yuan found the precedent for "Let
be..." in
.
3.2.1. Wording
Change [flat.multiset.defn] as follows:
// [flat.multiset.cons], constructors flat_multiset () : flat_multiset ( key_compare ()) { } explicit flat_multiset ( const key_compare & comp ) : c (), compare ( comp ) { } explicit flat_multiset ( container_type cont , const key_compare & comp = key_compare ()); explicit flat_multiset ( container_type cont ); flat_multiset ( container_type cont , const key_compare & comp ); flat_multiset ( sorted_equivalent_t , container_type cont , const key_compare & comp = key_compare ()) : c ( std :: move ( cont )), compare ( comp ) { }
Change [flat.multiset.cons] as follows:
x․ Letexplicit flat_multiset ( container_type cont , const key_compare & comp = key_compare ()); explicit flat_multiset ( container_type cont ); flat_multiset ( container_type cont , const key_compare & comp ); be
comp for the first overload.
key_compare () 1․ Effects: Initializes
with
c and
std :: move ( cont ) with
compare , and sorts the range [
comp ,
begin () ) with respect to
end () .
compare 2․ Complexity: Linear in N if
is already sorted with respect to
cont and otherwise N log N, where N is the value of
compare before this call.
cont . size ()
Change [flat.set.defn] as follows:
// [flat.set.cons], constructors flat_set () : flat_set ( key_compare ()) { } explicit flat_set ( const key_compare & comp ) : c (), compare ( comp ) { } explicit flat_set ( container_type cont , const key_compare & comp = key_compare ()); explicit flat_set ( container_type cont ); flat_set ( container_type cont , const key_compare & comp ); flat_set ( sorted_unique_t , container_type cont , const key_compare & comp = key_compare ()) : c ( std :: move ( cont )), compare ( comp ) { }
Change [flat.set.cons] as follows:
x․ Letexplicit flat_set ( container_type cont , const key_compare & comp = key_compare ()); explicit flat_set ( container_type cont ); flat_set ( container_type cont , const key_compare & comp ); be
comp for the first overload.
key_compare () 1․ Effects: Initializes
with
c and
std :: move ( cont ) with
compare , sorts the range [
comp ,
begin () ) with respect to
end () , and finally erases all but the first element from each group of consecutive equivalent elements.
compare 2․ Complexity: Linear in N if
is already sorted with respect to
cont and otherwise N log N, where N is the value of
compare before this call.
cont . size ()
3.3. Add move semantics to flat_set :: insert_range
std :: flat_set < std :: string > fs1 ; std :: vector < std :: string > v1 = { "hello" , "world" }; fs1 . insert_range ( std :: views :: as_rvalue ( v1 )); // Before: Copies the strings. // After: Moves the strings. std :: flat_set < std :: unique_ptr < int >> fs2 ; std :: vector < std :: unique_ptr < int >> v2 ; fs2 . insert_range ( std :: views :: as_rvalue ( v2 )); // Before: Ill-formed. // After: Moves the unique_ptrs.
Compare the current wording for
(added by P1222) versus
(added in P1206R5 and tweaked in R6).
’s wording is not only more robustly Rangified, but also more performant.
Arthur doesn’t know why P1206R5/R6 chose to pass
to
but plain old lvalue
to
; it seems like it should have forwarded in both places or else forwarded in neither place.
On 2023-07-17, Casey Carter concurred: "Let’s assume it’s just a mistake and change both places consistently."
3.3.1. Wording
Change [flat.set.modifiers]/10 as follows:
template < container - compatible - range < value_type > R > void insert_range ( R && rg ); 10․ Effects:
Adds elements toas if by:
c Adds the elements offor ( const auto & e : rg ) { c . insert ( c . end (), e ); } to
rg via
c if that is a valid expression, or
c . append_range ( rg ) otherwise. Then, sorts the range of newly inserted elements with respect to
ranges :: copy ( rg , back_inserter ( c )) ; merges the resulting sorted range and the sorted range of pre-existing elements into a single sorted range; and finally erases all but the first element from each group of consecutive equivalent elements.
compare 11․ Complexity: N + M log M, where N is
before the operation and M is
size () .
ranges :: distance ( rg ) 12․ Remarks: Since this operation performs an in-place merge, it may allocate memory.
Add a new section to [flat.multiset.modifiers] explaining the semantics of
.
template < container - compatible - range < value_type > R > void insert_range ( R && rg ); x․ Effects: Adds the elements of
to
rg via
c if that is a valid expression, or
c . append_range ( rg ) otherwise. Then, sorts the range of newly inserted elements with respect to
ranges :: copy ( rg , back_inserter ( c )) , and merges the resulting sorted range and the sorted range of pre-existing elements into a single sorted range.
compare x․ Complexity: N + M log M, where N is
before the operation and M is
size () .
ranges :: distance ( rg ) x․ Remarks: Since this operation performs an in-place merge, it may allocate memory.
3.4. (LWG4000) Add move semantics to flat_map :: insert_range
Note: This resolves [LWG4000].
’s
has the same issue as
’s, at least on paper.
std :: flat_map < int , std :: unique_ptr < int >> fs2 ; std :: vector < std :: pair < int , std :: unique_ptr < int >>> v2 ; fs2 . insert_range ( std :: views :: as_rvalue ( v2 )); // Before: Ill-formed. // After: Moves the unique_ptrs.
The
method is constrained on
,
i.e.
.
But in fact the current spec’s algorithm never attempts to convert
to
.
Instead, it implicitly requires that
be convertible to
and
be convertible to
. If
is
something without
and
members, the current spec doesn’t work at all.
std :: pair < int , int > p1 = { 1 , 2 }; std :: reference_wrapper < std :: pair < int , int >> a [] = { p1 }; std :: flat_map < int , int > fm ; fm . insert_range ( a ); // Before: Ill-formed: reference_wrapper has no .first member // After: OK
The wording below implicitly converts
to
, and then
move-constructs from
and
into the map’s containers. Note that
and
are never reference types, so
is correct.
LWG in Varna briefly feinted in the direction of trying to metaprogram away the extra move-construct
(e.g. by changing the lambda’s parameter type to something like
),
but I felt strongly that the chance of getting that correct on the first try was near-nil.
Besides, we realized that we were trying to save O(M) move operations along a codepath
whose very next step was to do an O(M log M) sort and an O(N)
.
If any library vendor actually wants to try that kind of metaprogramming, they are welcome to do so under the As-If Rule. libc++ will not do it.
3.4.1. Wording
Note: This section was updated in R1 following LWG’s preferred direction.
I don’t recall why LWG preferred
over the ranged-for-loop shown in [LWG4000]'s P/R.
We don’t need to touch [flat.multimap.modifiers]; in fact it does not currently exist; because of [flat.multimap.overview]/4:
4․ Except as otherwise noted, operations onare equivalent to those of
flat_multimap , except that
flat_map operations do not remove or replace elements with equal keys.
flat_multimap
Change [flat.map.modifiers]/12 as follows:
template < container - compatible - range < value_type > R > void insert_range ( R && rg ); 12․ Effects: Adds elements to
as if by:
c for ( const auto & e : rg ) ranges :: for_each ( rg , [ & c ]( value_type e ) { c . keys . insert ( c . keys . end (), std :: move ( e . first ) ); c . values . insert ( c . values . end (), std :: move ( e . second ) ); } ); Then, sorts the range of newly inserted elements with respect to
; merges the resulting sorted range and the sorted range of pre-existing elements into a single sorted range; and finally erases the duplicate elements as if by:
value_comp () auto zv = views :: zip ( c . keys , c . values ); auto it = ranges :: unique ( zv , key_equiv ( compare )). begin (); auto dist = distance ( zv . begin (), it ); c . keys . erase ( c . keys . begin () + dist , c . keys . end ()); c . values . erase ( c . values . begin () + dist , c . values . end ()); 13․ Complexity: N + M log M, where N is
before the operation and M is
size () .
ranges :: distance ( rg ) 14․ Remarks: Since this operation performs an in-place merge, it may allocate memory.
3.5. insert
is more primitive than emplace
std :: pmr :: set_default_resource ( std :: pmr :: null_memory_resource ()); std :: pmr :: monotonic_buffer_resource mr ( 1 ’000 ’000 ); auto fm = PmrFlatSet < std :: pmr :: string > ( & mr ); std :: pmr :: string ps ( "too long to fit in the small string buffer" , & mr ); fm . insert ( ps ); // Before: runtime abort, cannot default-allocate t // After: OK
Whenever we insert or emplace into a set or multiset, we have two tasks:
-
Construct the new
object.value_type -
Find the correct insertion point for the new object.
receives a bag of constructor arguments, so it has no choice: it must do
"construct
, find insertion point for
, move-insert
into the container" exactly as specified above.
But
receives an already-constructed
! It shouldn’t discard that valuable information;
it should use the given
to find the appropriate insertion point, and then construct the new object
directly in place. There is no reason to construct
on the stack first.
The current definition of
is not expressed in terms of
. P2767R0 proposed
not to change it, simply to keep the diff small. But LWG in Varna asked to make that simplification.
libc++'s implementation already implements
in terms of
, so there doesn’t seem to be
any sneaky subtlety in that area; we can just do it. So P2767R1 does it.
3.5.1. "Initializes" phrasing
The proposed wording below brings [flat.map.modifiers]/2 into line with e.g. [variant.ctor]/21 and [forward.list.modifiers]/23:
2․ Effects:InitializesDirect-non-list-initializes an objectof type
t with
pair < key_type , mapped_type > ; if the map already contains an element whose key is equivalent to
std :: forward < Args > ( args )... ,
t . first is unchanged. [...]
* this
P2767R0 asked whether LWG had appetite to invent something less jargony. No, LWG did not.
3.5.2. Inconsistent emplace
constraints
The usual pattern in [containers] is that
has a precondition ([sequence.reqmts], [associative.reqmts.general])
but no Constraints element. That is,
is not SFINAE-friendly. And it has only the one overload,
so it doesn’t need a constraint for purposes of overload resolution.
No Constraints on
:
,
,
, containers, associative containers, unordered containers,
,
.
Constraints on
:
,
,
,
,
.
I believe a Constraints element was accidentally copy-pasted from the spec of
—
’s large overload set — to
,
and then from there to
. The constraint is already (correctly) absent
from
.
Therefore, the proposed wording for this section simply deletes those Constraints elements.
With the Constraints elements gone, and
always implemented as
,
there is no longer any need for an English description of
. It is now specified by code only.
The rewrite of
into
is frightening, because of
the very large overload set of
; but I think it’s safe: it should always be a perfect match for
and never a viable match for any other overload at all.
3.5.3. Ambiguous insert ( first , last )
struct Address { const char * p_ = nullptr ; Address ( auto p ) : p_ (( const char * ) &* p ) {} auto operator <=> ( const Address & ) const = default ; }; std :: flat_set < Address > m , n ; m . insert ( n . begin (), n . end ()); // Before: Ambiguous with insert(pos, K&&) // After: Unambiguously insert(first, last)
We simply need to copy the usual wording "Constraints: For the second overload,
and
are both false
" from [P2363] or
.
This is a problem only for the sets, where
can be convertible to
.
It’s not a problem for the maps, so they don’t need to change.
3.5.4. flat_multiset :: emplace
by rotation?
template < class T , class C = std :: less < T >> using PmrFlatMultiset = std :: flat_multiset < T , C , std :: pmr :: vector < T >> ; auto fms = PmrFlatMultiset < std :: pmr :: string > ( & mr ); fms . emplace ( "hello world" );
The C++23 status quo is that we "initialize an object
of type
" with
,
and then insert it into
. The initial construction of
uses the wrong allocator.
P2767R1 proposes to simplify the specification of
in terms of
;
but does not propose to change this behavior. We’ll still be constructing a temporary with the wrong
allocator.
Tim Song has observed that we could instead specify
like this
(following
’s precedent):
template < class ... Args > iterator emplace ( Args && ... args );
1․ Constraints:is
is_constructible_v < value_type , Args ... > true
.2․ Effects:
First, initializes an objectAdds an element toof type
t with
value_type , then inserts
std :: forward < Args > ( args )...
t as if by:
c auto it = ranges :: upper_bound ( c , t , compare ); c . insert ( it , std :: move ( t )); c . emplace_back ( std :: forward < Args > ( args )...); auto n = upper_bound ( c . begin (), c . end () - 1 , c . back (), compare ) - c . begin (); ranges :: rotate ( c . begin () + n , c . end () - 1 , c . end ()); 3․ Returns: An iterator that points to the inserted element.
This would ensure that the new object never gets constructed with the wrong allocator. However, this benefit comes with at least three downsides:
-
A user-provided random-access container (e.g. one based on a skip list) might have a way to insert in the middle of the container without moving any other elements at all, or moving only O(1) elements instead of O(n) elements.
-
Even for
it seems more expensive to rotate the elements than to do the right-shift thatvector
would do. Is the cost worth the benefit?c . insert -
The proposal applies to
but not toflat_multiset :: emplace
(because we don’t want to insert a duplicate and then have to erase it again) and not toflat_set :: emplace
(because we don’t have a container offlat_multimap :: emplace
s to emplace into, in that case).pair
We could mitigate the first two downsides by leaving
alone, and doing the
rotate-based version only for
. There, if the caller gives us an accurate hint,
we won’t need to rotate anything at all; and when we do rotate, it can only be because
the caller gave us an inaccurate hint (i.e. it’s "their fault"). But this would still
suffer the third downside (i.e. inconsistency), and it would be more complicated to teach.
Arthur thinks that it is best not to use
.
3.5.5. Wording
Note: This section was updated in R1 following LWG’s preferred direction.
We no longer add
to
; that wording has moved to § 4.4 Efficient flat_multiset::insert(K&&).
We don’t need to touch [flat.multimap.modifiers]; in fact it does not currently exist; because of [flat.multimap.overview]/4:
4․ Except as otherwise noted, operations onare equivalent to those of
flat_multimap , except that
flat_map operations do not remove or replace elements with equal keys.
flat_multimap
Change [flat.map.defn] as follows:
// [flat.map.modifiers], modifiers template < class ... Args > pair < iterator , bool > emplace ( Args && ... args ) ; { return insert ( value_type ( std :: forward < Args > ( args )...)); } template < class ... Args > iterator emplace_hint ( const_iterator position , Args && ... args ) ; { return insert ( position , value_type ( std :: forward < Args > ( args )...)); } pair < iterator , bool > insert ( const value_type & x ) ; { return emplace ( x ); } pair < iterator , bool > insert ( value_type && x ) ; { return emplace ( std :: move ( x )); } iterator insert ( const_iterator position , const value_type & x ) ; { return emplace_hint ( position , x ); } iterator insert ( const_iterator position , value_type && x ) ; { return emplace_hint ( position , std :: move ( x )); } template < class P > pair < iterator , bool > insert ( P && x ); template < class P > iterator insert ( const_iterator position , P && );
Change [flat.map.modifiers] as follows:
template < class ... Args > pair < iterator , bool > emplace ( Args && ... args ); 1․ Constraints:is
is_constructible_v < pair < key_type , mapped_type > , Args ... > true
.
2․ Effects: Initializes an objectof type
t with
pair < key_type , mapped_type > ; if the map already contains an element whose key is equivalent to
std :: forward < Args > ( args )... ,
t . first is unchanged. Otherwise, equivalent to:
* this auto key_it = ranges :: upper_bound ( c . keys , t . first , compare ); auto value_it = c . values . begin () + distance ( c . keys . begin (), key_it ); c . keys . insert ( key_it , std :: move ( t . first )); c . values . insert ( value_it , std :: move ( t . second )); 3․ Returns: Thecomponent of the returned pair is
bool true
if and only if the insertion took place, and the iterator component of the pair points to the element with key equivalent to.
t . first x․ Effects: If the map already contains an element whose key is equivalent topair < iterator , bool > insert ( const value_type & x ); pair < iterator , bool > insert ( value_type && x ); ,
x . first and
* this are unchanged. Otherwise, equivalent to:
x for the first overload andauto key_it = ranges :: upper_bound ( c . keys , x . first , compare ); auto value_it = c . values . begin () + distance ( c . keys . begin (), key_it ); c . keys . insert ( key_it , x . first ); c . values . insert ( value_it , x . second ); for the second overload.auto key_it = ranges :: upper_bound ( c . keys , x . first , compare ); auto value_it = c . values . begin () + distance ( c . keys . begin (), key_it ); c . keys . insert ( key_it , std :: move ( x . first )); c . values . insert ( value_it , std :: move ( x . second )); 3․ Returns: The
component of the returned pair is
bool true
if and only if the insertion took place, and the iterator component of the pair points to the element with key equivalent to.
x . first template < class P > pair < iterator , bool > insert ( P && x ); template < class P > iterator insert ( const_iterator position , P && x ); 4․ Constraints:
is
is_constructible_v < pair < key_type , mapped_type > , P > true
. For the second overload,and
is_convertible_v < P && , const_iterator > are both
is_convertible_v < P && , iterator > false
.5․ Effects: The first form is equivalent to
. The second form is equivalent to
return emplace ( std :: forward < P > ( x )); .
return emplace_hint ( position , std :: forward < P > ( x ));
Change [flat.multimap.defn] as follows:
// modifiers template < class ... Args > iterator emplace ( Args && ... args ) ; { return insert ( value_type ( std :: forward < Args > ( args )...)); } template < class ... Args > iterator emplace_hint ( const_iterator position , Args && ... args ) ; { return insert ( position , value_type ( std :: forward < Args > ( args )...)); } iterator insert ( const value_type & x ) ; { return emplace ( x ); } iterator insert ( value_type && x ) ; { return emplace ( std :: move ( x )); } iterator insert ( const_iterator position , const value_type & x ) ; { return emplace_hint ( position , x ); } iterator insert ( const_iterator position , value_type && x ) ; { return emplace_hint ( position , std :: move ( x )); } template < class P > iterator insert ( P && x ); template < class P > iterator insert ( const_iterator position , P && );
Change [flat.multiset.defn] as follows:
// [flat.multiset.modifiers], modifiers template < class ... Args > iterator emplace ( Args && ... args ) ; { return insert ( value_type ( std :: forward < Args > ( args )...)); } template < class ... Args > iterator emplace_hint ( const_iterator position , Args && ... args ) ; { return insert ( position , value_type ( std :: forward < Args > ( args )...)); } iterator insert ( const value_type & x ) ; { return emplace ( x ); } iterator insert ( value_type && x ) ; { return emplace ( std :: move ( x )); } iterator insert ( const_iterator position , const value_type & x ) ; { return emplace_hint ( position , x ); } iterator insert ( const_iterator position , value_type && x ) ; { return emplace_hint ( position , std :: move ( x )); }
Change [flat.multiset.modifiers] as follows:
template < class ... Args > iterator emplace ( Args && ... args );
1․ Constraints:is
is_constructible_v < value_type , Args ... > true
.
2․ Effects: First, initializes an objectof type
t with
value_type , then inserts
std :: forward < Args > ( args )... as if by:
t auto it = ranges :: upper_bound ( c , t , compare ); c . insert ( it , std :: move ( t ));
3․ Returns: An iterator that points to the inserted element.x․ Effects: Inserts a new element as if by:iterator insert ( const value_type & x ); iterator insert ( value_type && x ); for the first overload orauto it = ranges :: upper_bound ( c , x , compare ); c . insert ( it , x ); for the second overload.auto it = ranges :: upper_bound ( c , x , compare ); c . insert ( it , std :: move ( x )); x․ Returns: An iterator that points to the inserted element.
Change [flat.set.defn] as follows:
// [flat.set.modifiers], modifiers template < class ... Args > pair < iterator , bool > emplace ( Args && ... args ) ; { return insert ( value_type ( std :: forward < Args > ( args )...)); } template < class ... Args > iterator emplace_hint ( const_iterator position , Args && ... args ) ; { return insert ( position , value_type ( std :: forward < Args > ( args )...)); } pair < iterator , bool > insert ( const value_type & x ) ; { return emplace ( x ); } pair < iterator , bool > insert ( value_type && x ) ; { return emplace ( std :: move ( x )); } template < class K > pair < iterator , bool > insert ( K && x ); iterator insert ( const_iterator position , const value_type & x ) ; { return emplace_hint ( position , x ); } iterator insert ( const_iterator position , value_type && x ) ; { return emplace_hint ( position , std :: move ( x )); } template < class K > iterator insert ( const_iterator hint , K && x );
Change [flat.set.modifiers] as follows:
x․ Effects: If the set already contains an element equivalent topair < iterator , bool > insert ( const value_type & x ); pair < iterator , bool > insert ( value_type && x ); ,
x and
* this are unchanged. Otherwise, inserts a new element as if by:
x for the first overload orauto it = ranges :: upper_bound ( c , x , compare ); c . insert ( it , x ); for the second overload.auto it = ranges :: upper_bound ( c , x , compare ); c . insert ( it , std :: move ( x )); x․ Returns: The
component of the returned pair is
bool true
if and only if the insertion took place. The returned iterator points to the element equivalent to.
x template < class K > pair < iterator , bool > insert ( K && x ); template < class K > iterator insert ( const_iterator hint , K && x ); 1․ Constraints: The qualified-id
is valid and denotes a type.
Compare :: is_transparent is
is_constructible_v < value_type , K > true
. For the second overload,and
is_convertible_v < K && , const_iterator > are both
is_convertible_v < K && , iterator > false
.2․ Preconditions: The conversion from
into
x constructs an object
value_type
u ,for whichis
find ( x ) == find ( u ) true
.3․ Effects: If the set already contains an element equivalent to
,
x and
* this are unchanged. Otherwise, inserts a new element as if by
x .
emplace ( std :: forward < K > ( x )) 4․ Returns: In the first overload, the
component of the returned pair is
bool true
if and only if the insertion took place. The returned iterator points to the elementwhose key isequivalent to.
x
3.6. Inconsistent handling of redundancy in [flat.multiset] and [flat.multimap]
See #6246.
[flat.multiset.overview/4] uses
the same boilerplate as
,
,
, and
:
Descriptions are provided here only for operations on flat_multiset
that are not described
in one of the general sections or for operations where there is additional semantic information.
[flat.multimap.overview/4] uses different boilerplate:
Except as otherwise noted, operations onare equivalent to those of
flat_multimap , except that
flat_map operations do not remove or replace elements with equal keys.
flat_multimap [Example 1:
constructors and emplace do not erase non-unique elements after sorting them. —end example]
flat_multimap
That’s a little handwavey: It doesn’t bother to explain that
is "equivalent to"
. It doesn’t bother to explain how
the
return value of
is derived from the
return value of
.
But it does make the spec a lot shorter! Should we apply the same technique to [flat.multiset] that we already apply to [flat.multimap]? I haven’t written wording for this yet, but can do so if LWG is interested.
Vice versa, should we tweak [flat.multimap]'s wording to address the two "doesn’t bother" points above? Should we tweak it to say "equivalent keys" or "duplicate keys" instead of "equal keys"?
3.7. Special member functions
[flat.set.defn] currently does not declare any special member functions. This implies that they are defaulted, which implicitly specifies their constexprness, noexceptness, and triviality.
All three C++20 container adaptors (e.g. [priqueue.overview]) follow that approach, too.
All eight associative and/or unordered containers (e.g. [set.overview]) explicitly provide signatures for all five special members, including destructor, like this:
set ( const set & x ); set ( set && x ); [...] ~ set (); set & operator = ( const set & x ); set & operator = ( set && x ) noexcept ( allocator_traits < Allocator >:: is_always_equal :: value && is_nothrow_move_assignable_v < Compare > );
Should
’s spec hew more closely to
’s or
’s?
We tentatively think
’s is the better model, so that the vendor would be free to
make the special members non-defaulted. Vendors are still permitted to strengthen the
noexceptness and/or triviality of declared functions; for example, both libc++ and libstdc++
make
conditionally noexcept.
using V = std :: inplace_vector < int , 100 > ; static_assert ( std :: is_trivially_destructible_v < V > ); using M = std :: flat_set < int , std :: less < int > , V > ; static_assert ( std :: is_trivially_destructible_v < M > ); // Before: Mandatory. // After: Permitted but not mandatory.
3.7.1. Moving from the comparator
As shown above, the associative and unordered containers give their move-assignment operator
a noexcept-spec that strongly implies it must move-from the comparator, never copy it.
This particularly affects libstdc++, where
will copy a stateful comparator
(e.g.
) instead of moving-from it, but
must move
the comparator, leaving the source object in a "radioactive" state.
This whole area is the subject of [LWG2227] "Stateful comparison objects in associative containers," and is certainly beyond the scope of this paper P2767.
struct TestLess : std :: less <> { // make this too big to fit in the small buffer char pad_ [ 1000 ]; }; using C = std :: function < bool ( int , int ) > ; std :: set < int , C > s ({ 1 , 2 , 3 }, C ( TestLess ())); assert ( s . key_comp () != nullptr ); auto t = std :: move ( s ); assert ( s . key_comp () != nullptr ); // libstdc++: Succeeds // libc++: Fails s . clear (); s . insert ({ 1 , 2 }); // libstdc++: OK // libc++: Throws std::bad_function_call t = std :: move ( s ); s . insert ({ 1 , 2 }); // Everyone: Throws std::bad_function_call
If we user-declare
,
we must decide whether to give it a similar noexcept-spec.
3.7.2. Partial wording
For example, change [flat.map.defn] as follows:
// [flat.map.cons], constructors flat_map () : flat_map ( key_compare ()) { } flat_map ( const flat_map & ); flat_map ( flat_map && ); flat_map & operator = ( const flat_multimap & ); flat_map & operator = ( flat_multimap && ) noexcept ( is_nothrow_move_assignable_v < KeyContainer > && is_nothrow_move_assignable_v < MappedContainer > && is_nothrow_move_assignable_v < Compare > );) ~ flat_map ();
flat_map ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ());
4. LEWG business
4.1. iterator
and const_iterator
and
are presented as if they could be
two different types. This is consistent with how we do it in
and
.
But in practice, all three vendors make
and
the
same type. We should consider refactoring the spec of
,
, and
all at once to mandate that these iterator types be the same type, the same way we
already do for
and (since C++23 introduced
)
.
This would allow us to remove a lot of redundant overloads from the specification,
e.g. we don’t need both
and
if those are the same iterator type. Vendors can already do this removal in their implementations
if they choose to. This is merely a question of our getting to do it in the paper specification too,
under the banner of "standardizing existing practice."
4.2. Zero-initialization of containers
Jean-Philippe Boivin points out that the flat containers currently specify their constructors
as value-initializing, rather than default-initializing, the
and
containers.
This is extremely expensive for in-place containers such as P0843
(proposed for C++26).
A reduced example looks like this (Godbolt):
struct flat_set { using key_compare = std :: less < int > ; using container_type = std :: inplace_vector < int , 1024 > ; flat_set () : flat_set ( key_compare ()) { } explicit flat_set ( const key_compare & comp ) : c_ (), compare_ ( comp ) { } private : [[ no_unique_address ]] key_compare compare_ ; container_type c_ ; };
The value-initialization of
zeroes the memory footprint of
and then calls
’s
default constructor (which initializes
but is otherwise trivial). This involves a 4096-byte
.
On the other hand, if
’s constructors were defined as
flat_set () = default ; explicit flat_set ( const key_compare & comp ) : compare_ ( comp ) { }
then there would be no 4096-byte
. This would make
more suitable for embedded programming,
where every cycle counts.
We don’t care about the cost of zero-initializing the comparator, since comparators are usually empty types, or at least small.
4.2.1. Wording
Change [flat.map.defn] as follows:
[...]// [flat.map.cons], constructors flat_map () : flat_map ( key_compare ()) { } flat_map () = default ; explicit flat_map ( const key_compare & comp ) : c (), compare ( comp ) { } flat_map ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); flat_map ( sorted_unique_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); template < class InputIterator > flat_map ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator > flat_map ( sorted_unique_t s , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( s , first , last ); } template < container - compatible - range < value_type > R > flat_map ( from_range_t fr , R && rg ) : flat_map ( fr , std :: forward < R > ( rg ), key_compare ()) { } template < container - compatible - range < value_type > R > flat_map ( from_range_t , R && rg , const key_compare & comp ) : flat_map ( comp ) { insert_range ( std :: forward < R > ( rg )); } [...]
Change [flat.multimap.defn] as follows:
[...]// [flat.multimap.cons], constructors flat_multimap () : flat_multimap ( key_compare ()) { } flat_multimap () = default ; explicit flat_multimap ( const key_compare & comp ) : c (), compare ( comp ) { } flat_multimap ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); flat_multimap ( sorted_equivalent_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); template < class InputIterator > flat_multimap ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator > flat_multimap ( sorted_equivalent_t s , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( s , first , last ); } template < container - compatible - range < value_type > R > flat_multimap ( from_range_t fr , R && rg ) : flat_multimap ( fr , std :: forward < R > ( rg ), key_compare ()) { } template < container - compatible - range < value_type > R > flat_multimap ( from_range_t , R && rg , const key_compare & comp ) : flat_multimap ( comp ) { insert_range ( std :: forward < R > ( rg )); } [...]
Change [flat.multiset.defn] as follows:
[...]// [flat.multiset.cons], constructors flat_multiset () : flat_multiset ( key_compare ()) { } flat_multiset () = default ; explicit flat_multiset ( const key_compare & comp ) : c (), compare ( comp ) { } explicit flat_multiset ( container_type cont , const key_compare & comp = key_compare ()); flat_multiset ( sorted_equivalent_t , container_type cont , const key_compare & comp = key_compare ()) : c ( std :: move ( cont )), compare ( comp ) { } template < class InputIterator > flat_multiset ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c ( first , last ), compare ( comp ) { } template < container - compatible - range < value_type > R > flat_multiset ( from_range_t fr , R && rg ) : flat_multiset ( fr , std :: forward < R > ( rg ), key_compare ()) { } template < container - compatible - range < value_type > R > flat_multiset ( from_range_t , R && rg , const key_compare & comp ) : flat_multiset ( comp ) { insert_range ( std :: forward < R > ( rg )); } [...]
Change [flat.set.defn] as follows:
[...]// [flat.set.cons], constructors flat_set () : flat_set ( key_compare ()) { } flat_set () = default ; explicit flat_set ( const key_compare & comp ) : c (), compare ( comp ) { } explicit flat_set ( container_type cont , const key_compare & comp = key_compare ()); flat_set ( sorted_unique_t , container_type cont , const key_compare & comp = key_compare ()) : c ( std :: move ( cont )), compare ( comp ) { } template < class InputIterator > flat_set ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c ( first , last ), compare ( comp ) { } template < container - compatible - range < value_type > R > flat_set ( from_range_t fr , R && rg ) : flat_set ( fr , std :: forward < R > ( rg ), key_compare ()) { } template < container - compatible - range < value_type > R > flat_set ( from_range_t , R && rg , const key_compare & comp ) : flat_set ( comp ) { insert_range ( std :: forward < R > ( rg )); } [...]
4.3. Add flat_set :: keys ()
and
provide
and
getters:
// observers key_compare key_comp () const ; value_compare value_comp () const ; const key_container_type & keys () const noexcept { return c . keys ; } const mapped_container_type & values () const noexcept { return c . values ; }
and
do not:
// observers key_compare key_comp () const ; value_compare value_comp () const ;
libc++ has found that
and
are helpful in unit tests.
The
method is a poor replacement, because it is mutating.
For a const
, there’s literally no way to get at the container
and query its properties, such as capacity, allocator, or even size.
Therefore we suggest adding
.
Notice that
’s
getter returns a container of
,
not a container of
.
doesn’t have a
;
therefore it shouldn’t have
either.
Before | After |
---|---|
|
|
4.3.1. Wording
Change [flat.multiset.defn] as follows:
// observers key_compare key_comp () const ; value_compare value_comp () const ; const container_type & keys () const noexcept { return c ; }
Change [flat.set.defn] as follows:
// observers key_compare key_comp () const ; value_compare value_comp () const ; const container_type & keys () const noexcept { return c ; }
4.4. Efficient flat_multiset :: insert ( K && )
Before | After |
---|---|
|
|
|
|
| |
|
|
|
|
[P2363] explains why they chose not to add
to the node-based
:
Adding heterogeneous
overload makes no sense for associative containers with non-unique keys (
insert ,
std :: multimap ,
std :: multiset and
std :: unordered_multimap ) because the insertion will be successful in any case and the key would be always constructed. All additional overheads introduced by
std :: unordered_multiset can be mitigated by using
insert .
emplace
That last sentence is false for the vector-based
: We cannot mitigate overheads by using
, because
must first construct
outside the vector using the default allocator (which may throw
).
cannot use the proper allocator, because
is (deliberately) not allocator-aware.
Therefore we propose to add
.
4.4.1. Wording
Note: This diff assumes § 3.5.5 Wording as the starting point.
Change [flat.multiset.defn] as follows:
// [flat.multiset.modifiers], modifiers template < class ... Args > iterator emplace ( Args && ... args ) { return insert ( value_type ( std :: forward < Args > ( args )...)); } template < class ... Args > iterator emplace_hint ( const_iterator position , Args && ... args ) { return insert ( position , value_type ( std :: forward < Args > ( args )...)); } iterator insert ( const value_type & x ); iterator insert ( value_type && x ); template < class K > iterator insert ( K && x ); iterator insert ( const_iterator position , const value_type & x ); iterator insert ( const_iterator position , value_type && x ); template < class K > iterator insert ( const_iterator hint , K && x );
Change [flat.multiset.modifiers] as follows:
iterator insert ( const value_type & x ); iterator insert ( value_type && x ); x․ Effects: Inserts a new element as if by:
auto it = ranges :: upper_bound ( c , x , compare ); c . insert ( it , x ); for the first overload or
auto it = ranges :: upper_bound ( c , x , compare ); c . insert ( it , std :: move ( x )); for the second overload.
x․ Returns: An iterator that points to the inserted element.
x․ Constraints: The qualified-idtemplate < class K > iterator insert ( K && x ); is valid and denotes a type.
Compare :: is_transparent is
is_constructible_v < value_type , K > true
. For the second overload,and
is_convertible_v < K && , const_iterator > are both
is_convertible_v < K && , iterator > false
.x․ Preconditions: The conversion from
into
x constructs an object
value_type for which
u is
upper_bound ( x ) == upper_bound ( u ) true
.x․ Effects: Inserts a new element as if by
auto it = ranges :: upper_bound ( c , x , compare ); c . emplace ( it , std :: forward < K > ( x )); x․ Returns: An iterator that points to the inserted element.
4.5. Complexity of equal_range
[associative.reqmts.general]/171 and /174 define the complexity of
and
as "Logarithmic."
This means that we have the following required complexities, for both
and
:
std :: set < std :: string > s ; std :: multiset < std :: string > ms ; std :: set < std :: string , std :: less <>> st ; std :: multiset < std :: string , std :: less <>> mst ; s . equal_range ( "abc" s ); // 171: lower_bound, lower_bound+1; (1 + lg N) operations total ms . equal_range ( "abc" s ); // 171: lower_bound, upper_bound; (2 lg N) operations total st . equal_range ( "abc" ); // 174: lower_bound, upper_bound; (2 lg N) operations total mst . equal_range ( "abc" ); // 174: lower_bound, upper_bound; (2 lg N) operations total
For
, [associative.reqmts.general]/7.22 forces us to consider the possibility that
is a less granular equivalence relation than
; i.e., even though
this is a
, it might still contain "duplicates" from the point of view of the heterogeneous comparator.
It would be efficient in practice to find
in lg N time and then step forward linearly
until we find an element not equal to
— the expected number of duplicates for the average real-world workload
is small. But the number of duplicates theoretically could be O(N); so we’re not allowed to do this
(at least not without an arbitrary cap, e.g. if we don’t find the end of the range in 10 probes then fall back
to
— bookkeeping which would again unnecessarily slow down the average case).
Consider a working programmer who writes
std :: flat_set < std :: string > s ; s . equal_range ( "abc" ); // lower_bound, lower_bound+1; (1 + lg N) operations total
and then switches to a heterogeneous comparator in an effort to "speed up" the code
by avoiding the conversion to
:
std :: flat_set < std :: string , std :: less <>> st ; st . equal_range ( "abc" ); // lower_bound, upper_bound; (2 lg N) operations total, cache-unfriendly
libc++ would like to see vendors given a little more freedom to experiment here.
The proposed wording below doesn’t require any vendor to change their implementation, since an existing implementation in O(log N) certainly also satisfies O(M + log N).
4.5.1. Wording
Change [associative.reqmts.general] as follows:
a_tran . upper_bound ( ku ) 166․ Result:
;
iterator for constant
const_iterator .
a_tran 167․ Returns: An iterator pointing to the first element with key
such that
r , or
c ( ku , r ) if such an element is not found.
a_tran . end () 168․ Complexity: Logarithmic.
b . equal_range ( k ) 169․ Result:
;
pair < iterator , iterator > for constant
pair < const_iterator , const_iterator > .
b 170․ Effects: Equivalent to:
return make_pair ( b . lower_bound ( k ), b . upper_bound ( k )); 171․ Complexity:
Logarithmic.O(M + log N), where N isand M is
b . size () .
distance ( b . lower_bound ( k ), b . upper_bound ( k )) a_tran . equal_range ( ke ) 172․ Result:
;
pair < iterator , iterator > for constant
pair < const_iterator , const_iterator > .
a_tran 173․ Effects: Equivalent to:
return make_pair ( a_tran . lower_bound ( ke ), a_tran . upper_bound ( ke )); 174․ Complexity:
Logarithmic.O(M + log N), where N isand M is
a_tran . size () .
distance ( a_tran . lower_bound ( ke ), a_tran . upper_bound ( ke ))
4.6. "Qualifies as a container"
Arthur’s libc++ implements an alternative resolution to [LWG3803]. This resolution applies
generally to all container adaptors, and has the advantage of not ad-hoc relying on a
complicated type trait (
) but being a little more consistent with the
pre-existing spec.
The choice of
is simply because
is already present
for allocators, and
is already present for many iterators (those that inherit
from
, for example). We could just as well choose a criterion like
"The expression
is well-formed when treated as an unevaluated operand."
4.6.1. Wording
Arthur’s preferred resolution is shown in
this color
.
Parts of the current C++23 Working Draft that were introduced by the adopted
resolution of LWG3803, but would be removed by this change, are shown in
Change [container.reqmts]/69 as follows:
69․ The behavior of certain container member functions and deduction guides depends on whether types qualify as input iterators, containers, or allocators.x․ The extent to which an implementation determines that a type cannot be an input iterator is unspecified, except that as a minimum integral types shall not qualify as input iterators.
x․
Likewise, theThe extent to which an implementation determines that a type cannot be an allocator is unspecified, except that as a minimum a typeshall not qualify as an allocator unless it meets both of the following conditions:
A
(69.1) The qualified-id
is valid and denotes a type ([temp.deduct]).
A :: value_type (69.2) The expression
is well-formed when treated as an unevaluated operand.
declval < A &> (). allocate ( size_t {}) x․ The extent to which an implementation determines that a type cannot be a container is unspecified, except that as a minimum a type C shall not qualify as a container unless the qualified-id
is valid and denotes a type.
C :: const_iterator
Change [container.adaptors.general]/6 as follows:
6․ A deduction guide for a container adaptor shall not participate in overload resolution if any of the following are true:
(6.1) It has an
template parameter and a type that does not qualify as an input iterator is deduced for that parameter.
InputIterator (6.2) It has a
template parameter and a type that qualifies as an allocator is deduced for that parameter.
Compare (6.3) It has a
,
Container , or
KeyContainer template parameter and a type that
MappedContainer qualifies as an allocatordoes not qualify as a container is deduced for that parameter.(6.4) It has no
,
Container , or
KeyContainer template parameter, and it has an
MappedContainer template parameter, and a type that does not qualify as an allocator is deduced for that parameter.
Allocator (6.5) It has both
and
Container template parameters, and
Allocator is
uses_allocator_v < Container , Allocator > false
.(6.6) It has both
and
KeyContainer template parameters, and
Allocator is
uses_allocator_v < KeyContainer , Allocator > false
.(6.7) It has both and
KeyContainer template parameters, and
Compare is_invocable_v < const Compare & , const typename KeyContainer :: value_type & , const typename KeyContainer :: value_type &> is not a valid expression or is false
.(6.8) It has both
and
MappedContainer template parameters, and
Allocator is
uses_allocator_v < MappedContainer , Allocator > false
.
4.7. Support for non-standard containers
Vendors are required to support "random-access containers," which means
(except for
)
and
. It’s unclear if vendors are required to support non-standard containers such as
; and if so, what public API those containers must provide in order
to interoperate with
.
For example, suppose the underlying container supports
but not
. Then I would expect that I couldn’t initialize a
with
; but I should still be able to initialize it with
,
right? It would be nice to see what’s required and what’s encouraged in this area.
libc++ goes very slightly out of its way to support
as the underlying container,
even though we believe we’re not required to support it.
4.8. Noexcept swap
This area seems to have been tweaked quite a bit at Batavia 2018. See the minutes.
is currently specified as unconditionally noexcept, which is inconsistent both
with
and with the pre-existing adaptors.
// [flat.set.defn] void swap ( flat_set & y ) noexcept ; // [priqueue.overview] void swap ( priority_queue & q ) noexcept ( is_nothrow_swappable_v < Container > && is_nothrow_swappable_v < Compare > ) { using std :: swap ; swap ( c , q . c ); swap ( comp , q . comp ); } // [set.overview] void swap ( set & ) noexcept ( allocator_traits < Allocator >:: is_always_equal :: value && is_nothrow_swappable_v < Compare > ); [associative.reqmts.except]/3: For associative containers, no
function throws an exception unless that exception is thrown by the swap of the container’s
swap object (if any).
Compare
Suppose the underlying container has a throwing move-constructor (like MSVC
), and lacks a custom
;
then
can throw. Or, suppose the underlying container is
;
then its
must swap
objects, which can throw.
Now, what happens if swapping the containers actually does throw? Then both containers are in an unknown
state, so we must restore the invariant by clearing both containers. We do the same thing if
or
throws, so this is nothing new.
What happens if swapping the comparators throws? Then we cannot recover. Louis suggests adding
as a constraint. The intent is nicely ergonomic:
Almost all comparators are nothrow swappable (including
, if we want to support that: [LWG2227]).
And the resulting behavior seems better: In the pathological corner case where
cannot safely provide
swappability, it’s better for it not to be swappable at all, than for it to falsely advertise a
noexcept
. But, if we constrain away
’s hidden-friend
,
will still
compile; it’ll just fall back to
. We could actually
the
function, like this:
void swap ( flat_map & y ) noexcept ; void swap ( flat_map & y ) noexcept ( is_nothrow_swappable_v < key_container_type > && is_nothrow_swappable_v < mapped_container_type > ) requires is_nothrow_swappable_v < key_compare > void clear () noexcept ; [...] friend void swap ( flat_map & x , flat_map & y ) noexcept { x . swap ( y ); } friend void swap ( flat_map & x , flat_map & y ) noexcept ( is_nothrow_swappable_v < key_container_type > && is_nothrow_swappable_v < mapped_container_type > ) requires is_nothrow_swappable_v < key_compare > { x . swap ( y ); } friend void swap ( flat_map & x , flat_map & y ) requires ( ! is_nothrow_swappable_v < key_compare > ) = delete ;
but even then
would still compile; it would fall back to the
three-step move-and-assign.
It seems to be impossible to make
non-swappable. The only reasonable options are:
-
Keep the status quo (unconditional
) and terminate the program if swapping containers throwsnoexcept -
Conditional
like in [priqueue.overview], and don’t care if swapping throws (i.e. matchnoexcept
’s status quo)priority_queue -
Conditional
like in [priqueue.overview], and clear both containers if swapping throws (but this won’t prevent radioactivity, ifnoexcept
left the comparator in a bad state)swap
4.8.1. Specified in terms of ranges :: swap
Orthogonally: It surprises us that [flat.map.modifiers] specifies
’s
implementation in terms of
etc. This feels overspecified; [container.reqmts] adequately covers the semantics of swapping,
and vendors should be able to decide for themselves how to swap comparator objects, just like
we do in
. Arguably the tight specification helps programmers providing their own
underlying containers: they know they just need to provide an ADL
or the requirements
of [concept.swappable]/2.3.
But one might point out to those programmers that their underlying container
must provide the API required in [container.reqmts]/49–51,
so the vendor can use ADL
or
if they want to.
libc++'s implementation currently uses ADL
, which is equivalent to
for all containers satisfying [container.reqmts]/51;
but the difference between
and
is probably observable.
4.9. Stable sorting
For the tree-based associative containers, [associative.reqmts.general] defines
the single-element
to insert in a well-defined order; [associative.reqmts.general] defines
the multi-element
to insert in an unspecified order.
Nevertheless, in practice, all three vendors implement the latter as a simple
loop over the former, so we have this de-facto behavior portable everywhere:
struct Apathy { bool operator ()( int , int ) const { return false; } }; int a [] = { 1 , 2 , 3 , 4 , 5 }; std :: multiset < int , Apathy > s ; // #1 for ( int i : a ) s . insert ( i ); assert ( std :: ranges :: equal ( s , a )); // de jure // #2 s . insert ( a , a + 5 ); assert ( std :: ranges :: is_permutation ( s , a )); // de jure assert ( std :: ranges :: equal ( s , a )); // de facto portable // #3 s . insert_range ( a ); assert ( std :: ranges :: is_permutation ( s , a )); // de jure assert ( std :: ranges :: equal ( s , a )); // de facto portable
Similarly with equivalent keys in a
or
:
std :: pair < int , int > a [] = {{ 1 , 1 },{ 1 , 2 },{ 1 , 3 }}; std :: map < int , int , Apathy > m ; // #1 for ( auto kv : a ) m . insert ( kv ); assert ( m [ 1 ] == 1 ); // de jure // #2 m . insert ( a , a + 5 ); assert ( m [ 1 ] > 0 ); // de jure assert ( m [ 1 ] == 1 ); // de facto portable // #3 m . insert_range ( a ); assert ( m [ 1 ] > 0 ); // de jure assert ( m [ 1 ] == 1 ); // de facto portable
Arthur’s libc++ implementation leans into the idea that
is a drop-in replacement for
,
and ensures that
will behave exactly like
.
std :: flat_multiset < int , Apathy > fs ; // #1 for ( int i : a ) fs . insert ( i ); assert ( std :: ranges :: equal ( fs , a )); // de jure // #2 fs . insert ( a , a + 5 ); assert ( std :: ranges :: is_permutation ( fs , a )); // de jure assert ( std :: ranges :: equal ( fs , a )); // libc++ // #3 fs . insert_range ( a ); assert ( std :: ranges :: is_permutation ( fs , a )); // de jure assert ( std :: ranges :: equal ( fs , a )); // libc++
is defined by [flat.set.modifiers] to insert in order and then "sort the range." The vendor will be tempted to use
,
which in practice is not stable. Arthur’s implementation uses
specifically
to ensure that
will give the same results as
for all multi-element insertions.
Louis Dionne worries that by providing this additional de-facto guarantee, libc++ might be creating a "portability trap" — the programmer writes obvious code that works perfectly on libc++, and then when the programmer migrates to libstdc++ or Microsoft STL, they suddenly find that their code no longer works.
Therefore Louis asks whether LWG could specifically require that newly inserted elements be sorted stably, e.g.
template < class InputIterator > void insert ( InputIterator first , InputIterator last ); 5․ Effects: Adds elements to
as if by:
c c . insert ( c . end (), first , last ); Then, stably sorts the range of newly inserted elements with respect to
; merges the resulting sorted range and the sorted range of pre-existing elements into a single sorted range; and finally erases all but the first element from each group of consecutive equivalent elements.
compare 6․ Complexity: N + M log M, where N is
before the operation and M is
size () .
distance ( first , last ) 7․ Remarks: Since this operation performs an in-place merge, it may allocate memory.
This operation already requires an in-place merge, which allocates memory, so requiring it to also do a stable sort — which allocates memory — might not be considered such a big deal.
The alternative here would be for libc++ to lean into the idea that
is supposed to
leave the order of equivalent elements unspecified, and instrument it under libc++'s existing
flag (currently used only for
,
, and
).
This would preserve the symmetry between
and
, by making both of them de facto
randomized order (at least in debug mode).
4.10. insert_range ( sorted_unique , rg )
The multi-element insertion API consists of these five overloads:
insert ( first , last ); // 1a insert ( il ); // 2a insert_range ( rg ); // 3a insert ( sorted_unique , first , last ); // 1b insert ( sorted_unique , il ); // 2b
is conspicuously missing.
Before | After |
---|---|
| |
|
|
|
|
Now, we’re also conspicuously missing a constructor overload
.
There we have a real API-design conflict: Which of
and
should come first?
This is enough of a reason to simply give up on that constructor. But
has no such API-design
problem. We can easily add this overload.
4.10.1. Wording
Change [flat.map.defn] as follows:
template < class InputIterator > void insert ( InputIterator first , InputIterator last ); template < class InputIterator > void insert ( sorted_unique_t , InputIterator first , InputIterator last ); template < container - compatible - range < value_type > R > void insert_range ( R && rg ); template < container - compatible - range < value_type > R > void insert_range ( sorted_unique_t , R && rg ); void insert ( initializer_list < value_type > il ) { insert ( il . begin (), il . end ()); } void insert ( sorted_unique_t s , initializer_list < value_type > il ) { insert ( s , il . begin (), il . end ()); }
Add a new entry to [flat.map.modifiers] as follows:
template < class InputIterator > void insert ( sorted_equivalent_t , InputIterator first , InputIterator last ); 7․ Effects: Equivalent to
.
insert ( first , last ) 8․ Complexity: Linear.
template < container - compatible - range < value_type > R > void insert_range ( sorted_unique_t , R && rg ); x․ Effects: Equivalent to
.
insert_range ( std :: forward < R > ( rg )) x․ Complexity: Linear in N, where N is
after the operation.
size ()
Change [flat.multimap.defn] as follows:
template < class InputIterator > void insert ( InputIterator first , InputIterator last ); template < class InputIterator > void insert ( sorted_equivalent_t , InputIterator first , InputIterator last ); template < container - compatible - range < value_type > R > void insert_range ( R && rg ); template < container - compatible - range < value_type > R > void insert_range ( sorted_equivalent_t , R && rg ); void insert ( initializer_list < value_type > il ) { insert ( il . begin (), il . end ()); } void insert ( sorted_equivalent_t s , initializer_list < value_type > il ) { insert ( s , il . begin (), il . end ()); }
Change [flat.multiset.defn] as follows:
template < class InputIterator > void insert ( InputIterator first , InputIterator last ); template < class InputIterator > void insert ( sorted_equivalent_t , InputIterator first , InputIterator last ); template < container - compatible - range < value_type > R > void insert_range ( R && rg ); template < container - compatible - range < value_type > R > void insert_range ( sorted_equivalent_t , R && rg ); void insert ( initializer_list < value_type > il ) { insert ( il . begin (), il . end ()); } void insert ( sorted_equivalent_t s , initializer_list < value_type > il ) { insert ( s , il . begin (), il . end ()); }
Add a new entry to [flat.multiset.modifiers] as follows:
template < class InputIterator > void insert ( sorted_equivalent_t , InputIterator first , InputIterator last ); 7․ Effects: Equivalent to
.
insert ( first , last ) 8․ Complexity: Linear.
template < container - compatible - range < value_type > R > void insert_range ( sorted_equivalent_t , R && rg ); x․ Effects: Equivalent to
.
insert_range ( std :: forward < R > ( rg )) x․ Complexity: Linear in N, where N is
after the operation.
size ()
Change [flat.set.defn] as follows:
template < class InputIterator > void insert ( InputIterator first , InputIterator last ); template < class InputIterator > void insert ( sorted_unique_t , InputIterator first , InputIterator last ); template < container - compatible - range < value_type > R > void insert_range ( R && rg ); template < container - compatible - range < value_type > R > void insert_range ( sorted_unique_t , R && rg ); void insert ( initializer_list < value_type > il ) { insert ( il . begin (), il . end ()); } void insert ( sorted_unique_t s , initializer_list < value_type > il ) { insert ( s , il . begin (), il . end ()); }
Add a new entry to [flat.set.modifiers] as follows:
template < class InputIterator > void insert ( sorted_unique_t , InputIterator first , InputIterator last ); 8․ Effects: Equivalent to
.
insert ( first , last ) 9․ Complexity: Linear.
template < container - compatible - range < value_type > R > void insert_range ( R && rg ); 10․ Effects: Adds elements to
as if by:
c for ( const auto & e : rg ) { c . insert ( c . end (), e ); } Then, sorts the range of newly inserted elements with respect to
; merges the resulting sorted range and the sorted range of pre-existing elements into a single sorted range; and finally erases all but the first element from each group of consecutive equivalent elements.
compare 11․ Complexity: N + M log M, where N is
before the operation and M is
size () .
ranges :: distance ( rg ) 12․ Remarks: Since this operation performs an in-place merge, it may allocate memory.
template < container - compatible - range < value_type > R > void insert_range ( sorted_unique_t , R && rg ); x․ Effects: Equivalent to
.
insert_range ( std :: forward < R > ( rg )) x․ Complexity: Linear in N, where N is
after the operation.
size ()
5. (LEWG) Monolithic proposal: tag narrow-contract functions, remove container ctors
The following proposal is large, and late-breaking. The flat adaptors technically shipped in C++23,
but no library vendor has implemented them yet — except for Arthur’s own
and
libc++ implementation, both of which are motivation and implementation experience for this very proposal.
So WG21 does have time to fix these issues as a DR, if we are responsible enough to do so.
There are a constellation of problems with the existing flat-adaptor API. I’ll present these problems, and then present a monolithic solution with all of these benefits:
-
Reduces
’s constructor overload set from 26 (pre-LWG3802) down to 20flat_map -
Reduces
’s deduction guide set from 12 down to 6flat_map -
Resolves [LWG3802], which saves adding 12 more constructors to
(§ 5.7 (LWG3802) Allocator-extended container ctors lack move semantics)flat_map -
Eliminates the cause of users' surprise at
(§ 5.1 Deceptive list-initialization from two lists)flat_map < int , int > fm = {{}, {}} -
Prevents accidentally constructing a
that straddles two different allocators (§ 5.2 flat_map violates PMR invariant)flat_map -
Eliminates "Complexity: Linear" elements that would otherwise need LWG attention (§ 5.3 Complexity clauses of container ctors)
-
Makes all narrow-contract functions uniformly take the
tag (§ 5.4 replace is unexpectedly narrow-contract)sorted_unique -
Makes all mutators uniformly come in tagged (narrow-contract) and untagged (wide-contract) flavors
-
Provides a wide-contract version of
, which didn’t exist before (§ 5.5 Wide-contract replace is difficult to simulate)replace -
Provides a narrow-contract version of
, which didn’t exist before (i.e. § 4.10 insert_range(sorted_unique, rg))insert_range -
Since no vendor has shipped flat adaptors yet, this patch reduces rather than increases vendors' workload
5.1. Deceptive list-initialization from two lists
Arthur has observed in a blog post that
’s non-explicit constructor from two containers is deceptive when used with braced initializers.
void print_map ( std :: flat_multimap < int , int > ); print_map ({ { 1 , 2 , 3 }, { 10 , 20 , 30 } }); // prints {1,10}, {2,20}, {3,30} print_map ({ { 1 , 2 }, { 10 , 20 } }); // prints {1,2}, {10,20} print_map ({ { 1 }, { 10 } }); // prints {1,10} print_map ({ {}, {} }); // prints {0,0}, {0,0}
To address this issue (if we wanted to), we could make
’s container constructors
.
But my preferred solution, presented here, is to remove those container constructors.
5.2. flat_map
violates PMR invariant
Using the two-container constructor of
, we can construct
a
that uses two different PMR allocators at once.
This violates the basic philosophical invariant of PMR: that a single
data structure never "straddles" two arenas at once.
Before | After |
---|---|
| |
|
|
|
|
5.3. Complexity clauses of container ctors
The flat adaptors' container constructors have Complexity clauses that mandate O(N) performance on input that happens to be sorted at runtime, even when calling the untagged wide-contract container constructor. The vendor has three ways to deal with this:
-
Guarantee that
(orstd :: sort
if § 4.9 Stable sorting is adopted) will always run in O(N) time on sorted input. This is not currently mandated by [alg.sort], so no vendor is likely to know off the top of their heads that they really guarantee this for all possible sorted inputs. libc++ certainly doesn’t know this for sure.std :: stable_sort -
Add a linear-time pass on the front, e.g.
. This wastes O(N) cycles in every case: it might improve the asymptotic performance in the rare case that the input happens to be sorted at runtime, but only at the cost of slowing down the expected case where the input is not sorted. libc++ doesn’t want to slow down the average caller.if ( ! std :: is_sorted ( first , last )) std :: sort ( first , last ) -
Ignore the spec’s Complexity requirement. libc++ also doesn’t want to do this.
Louis Dionne would be happy with a resolution that requires all vendors to implement this
optimization in
itself, for ranges that happen to be sorted. I.e., change [sort]/5 as follows:
5․ Complexity: Let N be. If the input is already sorted with respect to
last - first and
comp , O(N) comparisons and projections. Otherwise, O(N log N) comparisons and projections. In either case, twice as many projections as comparisons.
proj
and change [stable.sort]/5 as follows:
5․ Complexity: Let N be. If the input is already sorted with respect to
last - first and
comp , O(N) comparisons. If enough extra memory is available, O(N log(N)) comparisons. Otherwise,
proj at mostO(N log2(N)) comparisons. Ineitherany case, twice as many projections asthe number ofcomparisons.
[P2767R0] presented a second possible solution, as a patch against the Complexity clauses of the flat adaptors' container constructors. The third solution, presented in [P2767R0] and again below, is simply to remove the container constructors, so that we don’t have to worry about this entire problem.
5.4. replace
is unexpectedly narrow-contract
Most flat-adaptor member functions with narrow contracts are tagged with
resp.
. Currently the only exception is
. It has the same
narrow contract as
— that
must be sorted and uniqued —
Before | After |
---|---|
| |
|
|
|
|
5.5. Wide-contract replace
is difficult to simulate
Suppose the programmer has a
with certain keys and values,
and wants to apply a transformation to the keys. He has to do something
like this:
Before | After |
---|---|
| |
|
|
|
|
|
|
5.6. replace
can’t take lvalues
The current specification for
takes the new container(s) by rvalue reference.
This might have been originally intended as a guard against accidental expensive copying of containers.
But C++ doesn’t use this pattern anywhere else; and it’s inconsistent with the container constructors,
which do take by value and happily allow passing in lvalue containers by copy.
Before | After |
---|---|
| |
|
|
|
|
|
|
5.7. (LWG3802) Allocator-extended container ctors lack move semantics
Arthur’s libc++ has implemented move-semantic allocator-extended constructors for all four flat containers (Godbolt). This is [LWG3802].
template < class K , class V , class C = std :: less < K >> using PmrFlatMap = std :: flat_map < K , V , C , std :: pmr :: vector < K > , std :: pmr :: vector < V >> ; std :: pmr :: monotonic_buffer_resource mr ; auto keys = std :: pmr :: vector < int > ({ 1 , 2 , 3 }, & mr ); auto values = std :: pmr :: vector < int > ({ 1 , 2 , 3 }, & mr ); auto maps = std :: pmr :: vector < PmrFlatMap < int , int >> ( & mr ); maps . emplace_back ( std :: move ( keys ), values ); // Before: Makes copies of both keys and values // After: Moves-from keys; copies values auto keys2 = std :: pmr :: vector < std :: unique_ptr < int >> (); auto sets = std :: pmr :: vector < PmrFlatSet < std :: unique_ptr < int >>> (); sets . emplace_back ( std :: move ( keys2 )); // Before: Ill-formed // After: OK, moves-from keys2
To make this work, we’d have to add 12 new constructors to
, like this.
But I propose that instead of bloating the existing overload set, we shrink it
by removing the container constructors altogether.
// [flat.map.cons.alloc], constructors with allocators template < class Alloc > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( const key_container_type & key_cont , mapped_container_type && mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( key_container_type && key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( key_container_type && key_cont , mapped_container_type && mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( const key_container_type & key_cont , mapped_container_type && mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( key_container_type && key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( key_container_type && key_cont , mapped_container_type && mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , const key_container_type & key_cont , mapped_container_type && mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , key_container_type && key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , key_container_type && key_cont , mapped_container_type && mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , const key_container_type & key_cont , mapped_container_type && mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , key_container_type && key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , key_container_type && key_cont , mapped_container_type && mapped_cont , const key_compare & comp , const Alloc & a );
5.8. Monolithic wording
Change [flat.map.defn] as follows:
[...] // [flat.map.cons], constructors flat_map () : flat_map ( key_compare ()) { } explicit flat_map ( const key_compare & comp ) : c (), compare ( comp ) { } flat_map ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); flat_map ( sorted_unique_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); template < class InputIterator > flat_map ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator > flat_map ( sorted_unique_t s , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( s , first , last ); } template < container - compatible - range < value_type > R > flat_map ( from_range_t fr , R && rg ) : flat_map ( fr , std :: forward < R > ( rg ), key_compare ()) { } template < container - compatible - range < value_type > R > flat_map ( from_range_t , R && rg , const key_compare & comp ) : flat_map ( comp ) { insert_range ( std :: forward < R > ( rg )); } [...] // [flat.map.cons.alloc], constructors with allocators template < class Alloc > explicit flat_map ( const Alloc & a ); template < class Alloc > flat_map ( const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_map ( InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_map ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_map ( sorted_unique_t , InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_map ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_map ( from_range_t , R && rg , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_map ( from_range_t , R && rg , const key_compare & comp , const Alloc & a ); [...] containers extract () && ; void replace ( key_container_type && key_cont , mapped_container_type && mapped_cont ); void replace ( sorted_unique_t , key_container_type key_cont , mapped_container_type mapped_cont ); [...] }; template < class KeyContainer , class MappedContainer , class Compare = less < typename KeyContainer :: value_type >> flat_map ( KeyContainer , MappedContainer , Compare = Compare ()) -> flat_map < typename KeyContainer :: value_type , typename MappedContainer :: value_type , Compare , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Allocator > flat_map ( KeyContainer , MappedContainer , Allocator ) -> flat_map < typename KeyContainer :: value_type , typename MappedContainer :: value_type , less < typename KeyContainer :: value_type > , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Compare , class Allocator > flat_map ( KeyContainer , MappedContainer , Compare , Allocator ) -> flat_map < typename KeyContainer :: value_type , typename MappedContainer :: value_type , Compare , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Compare = less < typename KeyContainer :: value_type >> flat_map ( sorted_unique_t , KeyContainer , MappedContainer , Compare = Compare ()) -> flat_map < typename KeyContainer :: value_type , typename MappedContainer :: value_type , Compare , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Allocator > flat_map ( sorted_unique_t , KeyContainer , MappedContainer , Allocator ) -> flat_map < typename KeyContainer :: value_type , typename MappedContainer :: value_type , less < typename KeyContainer :: value_type > , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Compare , class Allocator > flat_map ( sorted_unique_t , KeyContainer , MappedContainer , Compare , Allocator ) -> flat_map < typename KeyContainer :: value_type , typename MappedContainer :: value_type , Compare , KeyContainer , MappedContainer > ; template < class InputIterator , class Compare = less < iter - key - type < InputIterator >>> flat_map ( InputIterator , InputIterator , Compare = Compare ()) -> flat_map < iter - key - type < InputIterator > , iter - mapped - type < InputIterator > , Compare > ; template < class InputIterator , class Compare = less < iter - key - type < InputIterator >>> flat_map ( sorted_unique_t , InputIterator , InputIterator , Compare = Compare ()) -> flat_map < iter - key - type < InputIterator > , iter - mapped - type < InputIterator > , Compare > ; template < ranges :: input_range R , class Compare = less < range - key - type < R >> , class Allocator = allocator < byte >> flat_map ( from_range_t , R && , Compare = Compare (), Allocator = Allocator ()) -> flat_map < range - key - type < R > , range - mapped - type < R > , Compare , vector < range - key - type < R > , alloc - rebind < Allocator , range - key - type < R >>> , vector < range - mapped - type < R > , alloc - rebind < Allocator , range - mapped - type < R >>>> ; template < ranges :: input_range R , class Allocator > flat_map ( from_range_t , R && , Allocator ) -> flat_map < range - key - type < R > , range - mapped - type < R > , less < range - key - type < R >> , vector < range - key - type < R > , alloc - rebind < Allocator , range - key - type < R >>> , vector < range - mapped - type < R > , alloc - rebind < Allocator , range - mapped - type < R >>>> ; template < class Key , class T , class Compare = less < Key >> flat_map ( initializer_list < pair < Key , T >> , Compare = Compare ()) -> flat_map < Key , T , Compare > ; template < class Key , class T , class Compare = less < Key >> flat_map ( sorted_unique_t , initializer_list < pair < Key , T >> , Compare = Compare ()) -> flat_map < Key , T , Compare > ;
Remove the whole of [flat.map.cons], and change [flat.map.cons.alloc], as follows:
Constructors [flat.map.cons]flat_map ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ());
1․ Effects: Initializeswith
c . keys ,
std :: move ( key_cont ) with
c . values , and
std :: move ( mapped_cont ) with
compare ; sorts the range [
comp ,
begin () ) with respect to
end () ; and finally erases the duplicate elements as if by:
value_comp () auto zv = views :: zip ( c . keys , c . values ); auto it = ranges :: unique ( zv , key_equiv ( compare )). begin (); auto dist = distance ( zv . begin (), it ); c . keys . erase ( c . keys . begin () + dist , c . keys . end ()); c . values . erase ( c . values . begin () + dist , c . values . end ());
2․ Complexity: Linear in N if the container arguments are already sorted with respect toand otherwise N log N, where N is the value of
value_comp () before this call.
key_cont . size () flat_map ( sorted_unique_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ());
3․ Effects: Initializeswith
c . keys ,
std :: move ( key_cont ) with
c . values , and
std :: move ( mapped_cont ) with
compare .
comp
4․ Complexity: Constant.Constructors with allocators [flat.map.cons.alloc]
1․ The constructors in this subclause shall not participate in overload resolution unless
is
uses_allocator_v < key_container_type , Alloc > true
andis
uses_allocator_v < mapped_container_type , Alloc > true
.template < class Alloc > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a );
2․ Effects: Equivalent toand
flat_map ( key_cont , mapped_cont ) , respectively, except that
flat_map ( key_cont , mapped_cont , comp ) and
c . keys are constructed with uses-allocator construction ([allocator.uses.construction]).
c . values
3․ Complexity: Same asand
flat_map ( key_cont , mapped_cont ) , respectively.
flat_map ( key_cont , mapped_cont , comp ) template < class Alloc > flat_map ( sorted_unique_t s , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t s , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a );
4․ Effects: Equivalent toand
flat_map ( s , key_cont , mapped_cont ) , respectively, except that
flat_map ( s , key_cont , mapped_cont , comp ) and
c . keys are constructed with uses-allocator construction ([allocator.uses.construction]).
c . values
5․ Complexity: Linear.template < class Alloc > explicit flat_map ( const Alloc & a ); template < class Alloc > flat_map ( const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_map ( InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_map ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_map ( sorted_unique_t , InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_map ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_map ( from_range_t , R && rg , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_map ( from_range_t , R && rg , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( initializer_list < value_type > il , const Alloc & a ); template < class Alloc > flat_map ( initializer_list < value_type > il , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , initializer_list < value_type > il , const Alloc & a ); template < class Alloc > flat_map ( sorted_unique_t , initializer_list < value_type > il , const key_compare & comp , const Alloc & a ); 6․ Effects: Equivalent to the corresponding non-allocator constructors except that
and
c . keys are constructed with uses-allocator construction ([allocator.uses.construction]).
c . values
Change [flat.map.modifiers] as follows:
[...]containers extract () && ; 34․ Postconditions:
is emptied, even if the function exits via an exception.
* this 35․ Returns:
.
std :: move ( c ) void replace ( key_container_type && key_cont , mapped_container_type && mapped_cont ); x․ Preconditions:
is
key_cont . size () == mapped_cont . size () true
.x․ Effects: Replaces
with
c . keys and
key_cont with
c . values ; sorts the range [
mapped_cont ,
begin () ) with respect to
end () ; and finally erases the duplicate elements as if by:
value_comp () auto zv = views :: zip ( c . keys , c . values ); auto it = ranges :: unique ( zv , key_equiv ( compare )). begin (); auto dist = distance ( zv . begin (), it ); c . keys . erase ( c . keys . begin () + dist , c . keys . end ()); c . values . erase ( c . values . begin () + dist , c . values . end ()); void replace ( sorted_unique_t , key_container_type key_cont , mapped_container_type mapped_cont ); 36․ Preconditions:
is
key_cont . size () == mapped_cont . size () true
, the elements ofare sorted with respect to
key_cont , and
compare contains no equal elements.
key_cont 37․ Effects: Equivalent to:
c . keys = std :: move ( key_cont ); c . values = std :: move ( mapped_cont );
Change [flat.multimap.defn] as follows:
[...] // [flat.multimap.cons], constructors flat_multimap () : flat_multimap ( key_compare ()) { } explicit flat_multimap ( const key_compare & comp ) : c (), compare ( comp ) { } flat_multimap ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); flat_multimap ( sorted_equivalent_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); template < class InputIterator > flat_multimap ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator > flat_multimap ( sorted_equivalent_t s , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( s , first , last ); } template < container - compatible - range < value_type > R > flat_multimap ( from_range_t fr , R && rg ) : flat_multimap ( fr , std :: forward < R > ( rg ), key_compare ()) { } template < container - compatible - range < value_type > R > flat_multimap ( from_range_t , R && rg , const key_compare & comp ) : flat_multimap ( comp ) { insert_range ( std :: forward < R > ( rg )); } [...] // [flat.multimap.cons.alloc], constructors with allocators template < class Alloc > explicit flat_multimap ( const Alloc & a ); template < class Alloc > flat_multimap ( const key_compare & comp , const Alloc & a ); template < class Alloc > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_multimap ( sorted_equivalent_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_multimap ( sorted_equivalent_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_multimap ( InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_multimap ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_multimap ( sorted_equivalent_t , InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_multimap ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_multimap ( from_range_t , R && rg , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_multimap ( from_range_t , R && rg , const key_compare & comp , const Alloc & a ); [...] containers extract () && ; void replace ( key_container_type && key_cont , mapped_container_type && mapped_cont ); void replace ( sorted_equivalent_t , key_container_type key_cont , mapped_container_type mapped_cont ); [...] }; template < class KeyContainer , class MappedContainer , class Compare = less < typename KeyContainer :: value_type >> flat_multimap ( KeyContainer , MappedContainer , Compare = Compare ()) -> flat_multimap < typename KeyContainer :: value_type , typename MappedContainer :: value_type , Compare , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Allocator > flat_multimap ( KeyContainer , MappedContainer , Allocator ) -> flat_multimap < typename KeyContainer :: value_type , typename MappedContainer :: value_type , less < typename KeyContainer :: value_type > , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Compare , class Allocator > flat_multimap ( KeyContainer , MappedContainer , Compare , Allocator ) -> flat_multimap < typename KeyContainer :: value_type , typename MappedContainer :: value_type , Compare , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Compare = less < typename KeyContainer :: value_type >> flat_multimap ( sorted_equivalent_t , KeyContainer , MappedContainer , Compare = Compare ()) -> flat_multimap < typename KeyContainer :: value_type , typename MappedContainer :: value_type , Compare , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Allocator > flat_multimap ( sorted_equivalent_t , KeyContainer , MappedContainer , Allocator ) -> flat_multimap < typename KeyContainer :: value_type , typename MappedContainer :: value_type , less < typename KeyContainer :: value_type > , KeyContainer , MappedContainer > ; template < class KeyContainer , class MappedContainer , class Compare , class Allocator > flat_multimap ( sorted_equivalent_t , KeyContainer , MappedContainer , Compare , Allocator ) -> flat_multimap < typename KeyContainer :: value_type , typename MappedContainer :: value_type , Compare , KeyContainer , MappedContainer > ; template < class InputIterator , class Compare = less < iter - key - type < InputIterator >>> flat_multimap ( InputIterator , InputIterator , Compare = Compare ()) -> flat_multimap < iter - key - type < InputIterator > , iter - mapped - type < InputIterator > , Compare > ; template < class InputIterator , class Compare = less < iter - key - type < InputIterator >>> flat_multimap ( sorted_equivalent_t , InputIterator , InputIterator , Compare = Compare ()) -> flat_multimap < iter - key - type < InputIterator > , iter - mapped - type < InputIterator > , Compare > ; template < ranges :: input_range R , class Compare = less < range - key - type < R >> , class Allocator = allocator < byte >> flat_multimap ( from_range_t , R && , Compare = Compare (), Allocator = Allocator ()) -> flat_multimap < range - key - type < R > , range - mapped - type < R > , Compare , vector < range - key - type < R > , alloc - rebind < Allocator , range - key - type < R >>> , vector < range - mapped - type < R > , alloc - rebind < Allocator , range - mapped - type < R >>>> ; template < ranges :: input_range R , class Allocator > flat_multimap ( from_range_t , R && , Allocator ) -> flat_multimap < range - key - type < R > , range - mapped - type < R > , less < range - key - type < R >> , vector < range - key - type < R > , alloc - rebind < Allocator , range - key - type < R >>> , vector < range - mapped - type < R > , alloc - rebind < Allocator , range - mapped - type < R >>>> ; template < class Key , class T , class Compare = less < Key >> flat_multimap ( initializer_list < pair < Key , T >> , Compare = Compare ()) -> flat_multimap < Key , T , Compare > ; template < class Key , class T , class Compare = less < Key >> flat_multimap ( sorted_equivalent_t , initializer_list < pair < Key , T >> , Compare = Compare ()) -> flat_multimap < Key , T , Compare > ;
Remove the whole of [flat.multimap.cons], and change [flat.multimap.cons.alloc], as follows:
Constructors [flat.multimap.cons]flat_multimap ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ());
1․ Effects: Initializeswith
c . keys ,
std :: move ( key_cont ) with
c . values , and
std :: move ( mapped_cont ) with
compare ; sorts the range [
comp ,
begin () ) with respect to
end () .
value_comp ()
2․ Complexity: Linear in N if the container arguments are already sorted with respect toand otherwise N log N, where N is the value of
value_comp () before this call.
key_cont . size () flat_multimap ( sorted_equivalent_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ());
3․ Effects: Initializeswith
c . keys ,
std :: move ( key_cont ) with
c . values , and
std :: move ( mapped_cont ) with
compare .
comp
4․ Complexity: Constant.Constructors with allocators [flat.multimap.cons.alloc]
1․ The constructors in this subclause shall not participate in overload resolution unless
is
uses_allocator_v < key_container_type , Alloc > true
andis
uses_allocator_v < mapped_container_type , Alloc > true
.template < class Alloc > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a );
2․ Effects: Equivalent toand
flat_multimap ( key_cont , mapped_cont ) , respectively, except that
flat_multimap ( key_cont , mapped_cont , comp ) and
c . keys are constructed with uses-allocator construction ([allocator.uses.construction]).
c . values
3․ Complexity: Same asand
flat_multimap ( key_cont , mapped_cont ) , respectively.
flat_multimap ( key_cont , mapped_cont , comp ) template < class Alloc > flat_multimap ( sorted_equivalent_t s , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc & a ); template < class Alloc > flat_multimap ( sorted_equivalent_t s , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc & a );
4․ Effects: Equivalent toand
flat_multimap ( s , key_cont , mapped_cont ) , respectively, except that
flat_multimap ( s , key_cont , mapped_cont , comp ) and
c . keys are constructed with uses-allocator construction ([allocator.uses.construction]).
c . values
5․ Complexity: Linear.template < class Alloc > explicit flat_multimap ( const Alloc & a ); template < class Alloc > flat_multimap ( const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_multimap ( InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_multimap ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_multimap ( sorted_equivalent_t , InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_multimap ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_multimap ( from_range_t , R && rg , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_multimap ( from_range_t , R && rg , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_multimap ( initializer_list < value_type > il , const Alloc & a ); template < class Alloc > flat_multimap ( initializer_list < value_type > il , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_multimap ( sorted_equivalent_t , initializer_list < value_type > il , const Alloc & a ); template < class Alloc > flat_multimap ( sorted_equivalent_t , initializer_list < value_type > il , const key_compare & comp , const Alloc & a ); 6․ Effects: Equivalent to the corresponding non-allocator constructors except that
and
c . keys are constructed with uses-allocator construction ([allocator.uses.construction]).
c . values
Change [flat.multiset.defn] as follows:
[...] // [flat.multiset.cons], constructors flat_multiset () : flat_multiset ( key_compare ()) { } explicit flat_multiset ( const key_compare & comp ) : c (), compare ( comp ) { } explicit flat_multiset ( container_type cont , const key_compare & comp = key_compare ()); flat_multiset ( sorted_equivalent_t , container_type cont , const key_compare & comp = key_compare ()) : c ( std :: move ( cont )), compare ( comp ) { } template < class InputIterator > flat_multiset ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c ( first , last ), compare ( comp ) { } template < container - compatible - range < value_type > R > flat_multiset ( from_range_t fr , R && rg ) : flat_multiset ( fr , std :: forward < R > ( rg ), key_compare ()) { } template < container - compatible - range < value_type > R > flat_multiset ( from_range_t , R && rg , const key_compare & comp ) : flat_multiset ( comp ) { insert_range ( std :: forward < R > ( rg )); } [...] // [flat.multiset.cons.alloc], constructors with allocators template < class Alloc > explicit flat_multiset ( const Alloc & a ); template < class Alloc > flat_multiset ( const key_compare & comp , const Alloc & a ); template < class Alloc > flat_multiset ( const container_type & cont , const Alloc & a ); template < class Alloc > flat_multiset ( const container_type & cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_multiset ( sorted_equivalent_t , const container_type & , const Alloc & a ); template < class Alloc > flat_multiset ( sorted_equivalent_t , const container_type & cont , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_multiset ( InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_multiset ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_multiset ( from_range_t , R && rg , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_multiset ( from_range_t , R && rg , const key_compare & comp , const Alloc & a ); [...] container_type extract () && ; void replace ( container_type && ); void replace ( sorted_equivalent_t , container_type && ); [...] }; template < class KeyContainer , class Compare = less < typename KeyContainer :: value_type >> flat_multiset ( KeyContainer , Compare = Compare ()) -> flat_multiset < typename KeyContainer :: value_type , Compare , KeyContainer > ; template < class KeyContainer , class Allocator > flat_multiset ( KeyContainer , Allocator ) -> flat_multiset < typename KeyContainer :: value_type , less < typename KeyContainer :: value_type > , KeyContainer > ; template < class KeyContainer , class Compare , class Allocator > flat_multiset ( KeyContainer , Compare , Allocator ) -> flat_multiset < typename KeyContainer :: value_type , Compare , KeyContainer > ; template < class KeyContainer , class Compare = less < typename KeyContainer :: value_type >> flat_multiset ( sorted_equivalent_t , KeyContainer , Compare = Compare ()) -> flat_multiset < typename KeyContainer :: value_type , Compare , KeyContainer > ; template < class KeyContainer , class Allocator > flat_multiset ( sorted_equivalent_t , KeyContainer , Allocator ) -> flat_multiset < typename KeyContainer :: value_type , less < typename KeyContainer :: value_type > , KeyContainer > ; template < class KeyContainer , class Compare , class Allocator > flat_multiset ( sorted_equivalent_t , KeyContainer , Compare , Allocator ) -> flat_multiset < typename KeyContainer :: value_type , Compare , KeyContainer > ; template < class InputIterator , class Compare = less < iter - value - type < InputIterator >>> flat_multiset ( InputIterator , InputIterator , Compare = Compare ()) -> flat_multiset < iter - value - type < InputIterator > , iter - value - type < InputIterator > , Compare > ; template < class InputIterator , class Compare = less < iter - value - type < InputIterator >>> flat_multiset ( sorted_equivalent_t , InputIterator , InputIterator , Compare = Compare ()) -> flat_multiset < iter - value - type < InputIterator > , iter - value - type < InputIterator > , Compare > ; template < ranges :: input_range R , class Compare = less < ranges :: range_value_t < R >> , class Allocator = allocator < ranges :: range_value_t < R >>> flat_multiset ( from_range_t , R && , Compare = Compare (), Allocator = Allocator ()) -> flat_multiset < ranges :: range_value_t < R > , Compare , vector < ranges :: range_value_t < R > , alloc - rebind < Allocator , ranges :: range_value_t < R >>>> ; template < ranges :: input_range R , class Allocator > flat_multiset ( from_range_t , R && , Allocator ) -> flat_multiset < ranges :: range_value_t < R > , less < ranges :: range_value_t < R >> , vector < ranges :: range_value_t < R > , alloc - rebind < Allocator , ranges :: range_value_t < R >>>> ; template < class Key , class Compare = less < Key >> flat_multiset ( initializer_list < Key > , Compare = Compare ()) -> flat_multiset < Key , Compare > ; template < class Key , class Compare = less < Key >> flat_multiset ( sorted_equivalent_t , initializer_list < Key > , Compare = Compare ()) -> flat_multiset < Key , Compare > ;
Remove the whole of [flat.multiset.cons], and change [flat.multiset.cons.alloc], as follows:
Constructors [flat.multiset.cons]explicit flat_multiset ( container_type cont , const key_compare & comp = key_compare ());
1․ Effects: Initializeswith
c and
std :: move ( cont ) with
compare , and sorts the range [
comp ,
begin () ) with respect to
end () .
compare
2․ Complexity: Linear in N ifis already sorted with respect to
cont and otherwise N log N, where N is the value of
compare before this call.
cont . size () Constructors with allocators [flat.multiset.cons.alloc]
1․ The constructors in this subclause shall not participate in overload resolution unless
is
uses_allocator_v < container_type , Alloc > true
.template < class Alloc > flat_multiset ( const container_type & cont , const Alloc & a ); template < class Alloc > flat_multiset ( const container_type & cont , const key_compare & comp , const Alloc & a );
2․ Effects: Equivalent toand
flat_multiset ( cont ) , respectively, except that
flat_multiset ( cont , comp ) is constructed with uses-allocator construction ([allocator.uses.construction]).
c
3․ Complexity: Same asand
flat_multiset ( cont ) , respectively.
flat_multiset ( cont , comp ) template < class Alloc > flat_multiset ( sorted_equivalent_t s , const container_type & cont , const Alloc & a ); template < class Alloc > flat_multiset ( sorted_equivalent_t s , const container_type & cont , const key_compare & comp , const Allocator & a );
4․ Effects: Equivalent toand
flat_multiset ( s , cont ) , respectively, except that
flat_multiset ( s , cont , comp ) is constructed with uses-allocator construction ([allocator.uses.construction]).
c
5․ Complexity: Linear.template < class Alloc > explicit flat_multiset ( const Alloc & a ); template < class Alloc > flat_multiset ( const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_multiset ( InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_multiset ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_multiset ( from_range_t , R && rg , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_multiset ( from_range_t , R && rg , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_multiset ( initializer_list < value_type > il , const Alloc & a ); template < class Alloc > flat_multiset ( initializer_list < value_type > il , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_multiset ( sorted_equivalent_t , initializer_list < value_type > il , const Alloc & a ); template < class Alloc > flat_multiset ( sorted_equivalent_t , initializer_list < value_type > il , const key_compare & comp , const Alloc & a ); 6․ Effects: Equivalent to the corresponding non-allocator constructors except that
is constructed with uses-allocator construction ([allocator.uses.construction]).
c
Change [flat.multiset.modifiers] as follows:
[...]container_type extract () && ; 10․ Postconditions:
is emptied, even if the function exits via an exception.
* this 11․ Returns:
.
std :: move ( c ) void replace ( container_type && cont ); x․ Effects: Replaces
with
c , and sorts the range [
cont ,
begin () ) with respect to
end () .
compare void replace ( sorted_equivalent_t , container_type cont ); 12․ Preconditions:
The elements ofare
cont is sorted with respect to
cont .
compare 13․ Effects: Equivalent to:
c = std :: move ( cont );
Change [flat.set.defn] as follows:
[...] // [flat.set.cons], constructors flat_set () : flat_set ( key_compare ()) { } explicit flat_set ( const key_compare & comp ) : c (), compare ( comp ) { } explicit flat_set ( container_type cont , const key_compare & comp = key_compare ()); flat_set ( sorted_unique_t , container_type cont , const key_compare & comp = key_compare ()) : c ( std :: move ( cont )), compare ( comp ) { } template < class InputIterator > flat_set ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c ( first , last ), compare ( comp ) { } template < container - compatible - range < value_type > R > flat_set ( from_range_t fr , R && rg ) : flat_set ( fr , std :: forward < R > ( rg ), key_compare ()) { } template < container - compatible - range < value_type > R > flat_set ( from_range_t , R && rg , const key_compare & comp ) : flat_set ( comp ) { insert_range ( std :: forward < R > ( rg )); } [...] // [flat.set.cons.alloc], constructors with allocators template < class Alloc > explicit flat_set ( const Alloc & a ); template < class Alloc > flat_set ( const key_compare & comp , const Alloc & a ); template < class Alloc > flat_set ( const container_type & cont , const Alloc & a ); template < class Alloc > flat_set ( const container_type & cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_set ( sorted_unique_t , const container_type & , const Alloc & a ); template < class Alloc > flat_set ( sorted_unique_t , const container_type & cont , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_set ( InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_set ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_set ( from_range_t , R && rg , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_set ( from_range_t , R && rg , const key_compare & comp , const Alloc & a ); [...] container_type extract () && ; void replace ( container_type && ); void replace ( sorted_unique_t , container_type && ); [...] }; template < class KeyContainer , class Compare = less < typename KeyContainer :: value_type >> flat_set ( KeyContainer , Compare = Compare ()) -> flat_set < typename KeyContainer :: value_type , Compare , KeyContainer > ; template < class KeyContainer , class Allocator > flat_set ( KeyContainer , Allocator ) -> flat_set < typename KeyContainer :: value_type , less < typename KeyContainer :: value_type > , KeyContainer > ; template < class KeyContainer , class Compare , class Allocator > flat_set ( KeyContainer , Compare , Allocator ) -> flat_set < typename KeyContainer :: value_type , Compare , KeyContainer > ; template < class KeyContainer , class Compare = less < typename KeyContainer :: value_type >> flat_set ( sorted_unique_t , KeyContainer , Compare = Compare ()) -> flat_set < typename KeyContainer :: value_type , Compare , KeyContainer > ; template < class KeyContainer , class Allocator > flat_set ( sorted_unique_t , KeyContainer , Allocator ) -> flat_set < typename KeyContainer :: value_type , less < typename KeyContainer :: value_type > , KeyContainer > ; template < class KeyContainer , class Compare , class Allocator > flat_set ( sorted_unique_t , KeyContainer , Compare , Allocator ) -> flat_set < typename KeyContainer :: value_type , Compare , KeyContainer > ; template < class InputIterator , class Compare = less < iter - value - type < InputIterator >>> flat_set ( InputIterator , InputIterator , Compare = Compare ()) -> flat_set < iter - value - type < InputIterator > , Compare > ; template < class InputIterator , class Compare = less < iter - value - type < InputIterator >>> flat_set ( sorted_unique_t , InputIterator , InputIterator , Compare = Compare ()) -> flat_set < iter - value - type < InputIterator > , Compare > ; template < ranges :: input_range R , class Compare = less < ranges :: range_value_t < R >> , class Allocator = allocator < ranges :: range_value_t < R >>> flat_set ( from_range_t , R && , Compare = Compare (), Allocator = Allocator ()) -> flat_set < ranges :: range_value_t < R > , Compare , vector < ranges :: range_value_t < R > , alloc - rebind < Allocator , ranges :: range_value_t < R >>>> ; template < ranges :: input_range R , class Allocator > flat_set ( from_range_t , R && , Allocator ) -> flat_set < ranges :: range_value_t < R > , less < ranges :: range_value_t < R >> , vector < ranges :: range_value_t < R > , alloc - rebind < Allocator , ranges :: range_value_t < R >>>> ; template < class Key , class Compare = less < Key >> flat_set ( initializer_list < Key > , Compare = Compare ()) -> flat_set < Key , Compare > ; template < class Key , class Compare = less < Key >> flat_set ( sorted_unique_t , initializer_list < Key > , Compare = Compare ()) -> flat_set < Key , Compare > ;
Remove the whole of [flat.set.cons], and change [flat.set.cons.alloc] as follows:
Constructors [flat.set.cons]explicit flat_set ( container_type cont , const key_compare & comp = key_compare ());
1․ Effects: Initializeswith
c and
std :: move ( cont ) with
compare , sorts the range [
comp ,
begin () ) with respect to
end () , and finally erases all but the first element from each group of consecutive equivalent elements.
compare
2․ Complexity: Linear in N ifis already sorted with respect to
cont and otherwise N log N, where N is the value of
compare before this call.
cont . size () Constructors with allocators [flat.set.cons.alloc]
1․ The constructors in this subclause shall not participate in overload resolution unless
is
uses_allocator_v < container_type , Alloc > true
.template < class Alloc > flat_set ( const container_type & cont , const Alloc & a ); template < class Alloc > flat_set ( const container_type & cont , const key_compare & comp , const Alloc & a );
2․ Effects: Equivalent toand
flat_set ( cont ) , respectively, except that
flat_set ( cont , comp ) is constructed with uses-allocator construction ([allocator.uses.construction]).
c
3․ Complexity: Same asand
flat_set ( cont ) , respectively.
flat_set ( cont , comp ) template < class Alloc > flat_set ( sorted_unique_t s , const container_type & cont , const Alloc & a ); template < class Alloc > flat_set ( sorted_unique_t s , const container_type & cont , const key_compare & comp , const Alloc & a );
4․ Effects: Equivalent toand
flat_set ( s , cont ) , respectively, except that
flat_set ( s , cont , comp ) is constructed with uses-allocator construction ([allocator.uses.construction]).
c
5․ Complexity: Linear.template < class Alloc > explicit flat_set ( const Alloc & a ); template < class Alloc > flat_set ( const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_set ( InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_set ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < class InputIterator , class Alloc > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const Alloc & a ); template < class InputIterator , class Alloc > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_set ( from_range_t , R && rg , const Alloc & a ); template < container - compatible - range < value_type > R , class Alloc > flat_set ( from_range_t , R && rg , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_set ( initializer_list < value_type > il , const Alloc & a ); template < class Alloc > flat_set ( initializer_list < value_type > il , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_set ( sorted_unique_t , initializer_list < value_type > il , const Alloc & a ); template < class Alloc > flat_set ( sorted_unique_t , initializer_list < value_type > il , const key_compare & comp , const Alloc & a ); 6․ Effects: Equivalent to the corresponding non-allocator constructors except that
is constructed with uses-allocator construction ([allocator.uses.construction]).
c
Change [flat.set.modifiers] as follows:
[...]container_type extract () && ; 14․ Postconditions:
is emptied, even if the function exits via an exception.
* this 15․ Returns:
.
std :: move ( c ) void replace ( container_type && cont ); x․ Effects: Replaces
with
c ; sorts the range [
cont ,
begin () ) with respect to
end () ; and finally erases all but the first element from each group of consecutive equivalent elements.
compare void replace ( sorted_unique_t , container_type cont ); 16․ Preconditions:
The elements ofare
cont is sorted with respect to
cont , and
compare contains no
cont equalequivalent elements.17․ Effects: Equivalent to:
c = std :: move ( cont );
6. Implementation experience
I have implemented all of [P0429]/[P1222] and all of this proposal as a series of patches against libc++ trunk; see [Patch]. You can experiment with it on Godbolt Compiler Explorer; just use the P1144 branch of Clang, which uses Arthur’s patched libc++ by default.
7. Acknowledgments
-
Thanks to Tomasz Kamiński for recommending Arthur write this paper.
-
Thanks to Louis Dionne for his code review of libc++'s
implementation.flat_set