1. Changelog
-
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.
Arthur also proposes a major editorial change to the presentation order of [flat.foo.cons], to collect the allocator-extended constructors in one place, consistent with [priqueue.cons.alloc], [queue.cons.alloc], and [stack.cons.alloc].
3. Editorial introduction of [flat.foo.cons.alloc]
This editorial change was originally submitted as #5923 (October 2022). It bit-rotted after the resolutions of LWG 3802/3803. Here is a clean rebased copy, with easier-to-review formatting.
This wording trivially merge-conflicts with the resolution of [LWG3884] (which adds new allocator-extended constructors). I’m happy to rebase LWG3884 on top of this, or vice versa.
The only changes happening in this part, and their rationales, are:
-
Move all of the allocator-extended constructors down to the end of [flat.foo.cons].
-
Ease of reading.
-
Consistency with
,priority_queue
,queue
.stack
-
-
Introduce a new subsection, [flat.foo.cons.alloc], to hold just those constructors.
-
Consistency with [priqueue.cons.alloc], [queue.cons.alloc], [stack.cons.alloc].
-
-
Rename the template parameter
toAllocator
, in every case.Alloc -
Consistency with
,priority_queue
,queue
; all of which use the namestack
.Alloc -
This parameter could be an allocator (e.g.
, or it could be a non-allocator (e.g.std :: pmr :: polymorphic_allocator < int >
) which is nevertheless convertible to an allocator type. Thestd :: pmr :: memory_resource *
constraint makes this work.uses_allocator_v -
No blanket wording is activated nor deactivated by this name change.
-
Text is marked as
deleted
,
inserted
, or
3.1. [flat.map]
Change [flat.map.defn] as follows:
24.6.9.2 Definition [flat.map.defn][...] struct containers { key_container_type keys ; mapped_container_type values ; }; // [flat.map.cons], construct/copy/destroy constructors flat_map () : flat_map ( key_compare ()) { } flat_map ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); template < class Alloc ator > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc ator & a ); template < class Alloc ator > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc ator & a ); flat_map ( sorted_unique_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); template < class Alloc ator > flat_map ( sorted_unique_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc ator & a ); template < class Alloc ator > flat_map ( sorted_unique_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc ator & a ); explicit flat_map ( const key_compare & comp ) : c (), compare ( comp ) { } template < class Alloc ator > flat_map ( const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > explicit flat_map ( const Alloc ator & a ); template < class InputIterator > flat_map ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator , class Alloc ator > flat_map ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_map ( InputIterator first , InputIterator last , const Alloc ator & a ); 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 , class Alloc ator > flat_map ( from_range_t , R && rg , const Alloc ator & a ); 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 )); } template < container - compatible - range < value_type > R , class Alloc ator > flat_map ( from_range_t , R && rg , const key_compare & comp , const Alloc ator & a ); 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 < class InputIterator , class Alloc ator > flat_map ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_map ( sorted_unique_t , InputIterator first , InputIterator last , const Alloc ator & a ); flat_map ( initializer_list < value_type > il , const key_compare & comp = key_compare ()) : flat_map ( il . begin (), il . end (), comp ) { } template < class Alloc ator > flat_map ( initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_map ( initializer_list < value_type > il , const Alloc ator & a ); flat_map ( sorted_unique_t s , initializer_list < value_type > il , const key_compare & comp = key_compare ()) : flat_map ( s , il . begin (), il . end (), comp ) { } template < class Alloc ator > flat_map ( sorted_unique_t , initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_map ( sorted_unique_t , initializer_list < value_type > il , const Alloc ator & a ); // [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 , 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 Alloc > flat_map ( const key_compare & comp , const Alloc & a ); template < class Alloc > explicit flat_map ( 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 ( InputIterator first , InputIterator last , 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 InputIterator , class Alloc > flat_map ( sorted_unique_t , 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 Alloc > flat_map ( initializer_list < value_type > il , 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 ( sorted_unique_t , 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 ); flat_map & operator = ( initializer_list < value_type > il ); // iterators [...] 24.6.9.3 Constructors [flat.map.cons]
flat_map ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); 1․ Effects: Initializes
with
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 = ranges :: zip_view ( 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 to
and otherwise N log N, where N is the value of
value_comp () before this call.
key_cont . size () template < class Alloc ator > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc ator & a ); template < class Alloc ator > flat_map ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc ator & a ); 3․ Constraints:is
uses_allocator_v < key_container_type , Allocator > true
andis
uses_allocator_v < mapped_container_type , Allocator > true
.
4․ Effects: Equivalent to and
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
5․ Complexity: Same as and
flat_map ( key_cont , mapped_cont ) , respectively.
flat_map ( key_cont , mapped_cont , comp ) flat_map ( sorted_unique_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); 6․ Effects: Initializes
with
c . keys ,
std :: move ( key_cont ) with
c . values , and
std :: move ( mapped_cont ) with
compare .
comp 7․ Complexity: Constant.
24.6.9.x Constructors with allocators [flat.map.cons.alloc]x․ 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 );
4․ Effects: Equivalent to and
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
5․ Complexity: Same as and
flat_map ( key_cont , mapped_cont ) , respectively.
flat_map ( key_cont , mapped_cont , comp ) template < class Alloc ator > flat_map ( sorted_unique_t s , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc ator & a ); template < class Alloc ator > flat_map ( sorted_unique_t s , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc ator & a ); 8․ Constraints:is
uses_allocator_v < key_container_type , Allocator > true
andis
uses_allocator_v < mapped_container_type , Allocator > true
.9․ Effects: Equivalent to
and
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 10․ Complexity: Linear.
template < class Alloc ator > flat_map ( const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > explicit flat_map ( const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_map ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_map ( InputIterator first , InputIterator last , const Alloc ator & a ); template < container - compatible - range < value_type > R , class Alloc ator > flat_map ( from_range_t , R && rg , const Alloc ator & a ); template < container - compatible - range < value_type > R , class Alloc ator > flat_map ( from_range_t , R && rg , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_map ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_map ( sorted_unique_t , InputIterator first , InputIterator last , const Alloc ator & a ); template < class Alloc ator > flat_map ( initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_map ( initializer_list < value_type > il , const Alloc ator & a ); template < class Alloc ator > flat_map ( sorted_unique_t , initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_map ( sorted_unique_t , initializer_list < value_type > il , const Alloc ator & a ); 11․ Constraints:is
uses_allocator_v < key_container_type , Allocator > true
andis
uses_allocator_v < mapped_container_type , Allocator > true
.12․ Effects: Equivalent to the corresponding non-allocator constructors except that
and
c . keys are constructed with uses-allocator construction ([allocator.uses.construction]).
c . values
3.2. [flat.multimap]
Change [flat.multimap.defn] as follows:
24.6.10.2 Definition [flat.multimap.defn][...] struct containers { key_container_type keys ; mapped_container_type values ; }; // [flat.multimap.cons], construct/copy/destroy constructors flat_multimap () : flat_multimap ( key_compare ()) { } flat_multimap ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); template < class Alloc ator > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc ator & a ); flat_multimap ( sorted_equivalent_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); template < class Alloc ator > flat_multimap ( sorted_equivalent_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( sorted_equivalent_t , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc ator & a ); explicit flat_multimap ( const key_compare & comp ) : c (), compare ( comp ) { } template < class Alloc ator > flat_multimap ( const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > explicit flat_multimap ( const Alloc ator & a ); template < class InputIterator > flat_multimap ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator , class Alloc ator > flat_multimap ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multimap ( InputIterator first , InputIterator last , const Alloc ator & a ); 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 , class Alloc ator > flat_multimap ( from_range_t , R && rg , const Alloc ator & a ); 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 )); } template < container - compatible - range < value_type > R , class Alloc ator > flat_multimap ( from_range_t , R && rg , const key_compare & comp , const Alloc ator & a ); 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 < class InputIterator , class Alloc ator > flat_multimap ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multimap ( sorted_equivalent_t , InputIterator first , InputIterator last , const Alloc ator & a ); flat_multimap ( initializer_list < value_type > il , const key_compare & comp = key_compare ()) : flat_multimap ( il . begin (), il . end (), comp ) { } template < class Alloc ator > flat_multimap ( initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( initializer_list < value_type > il , const Alloc ator & a ); flat_multimap ( sorted_equivalent_t s , initializer_list < value_type > il , const key_compare & comp = key_compare ()) : flat_multimap ( s , il . begin (), il . end (), comp ) { } template < class Alloc ator > flat_multimap ( sorted_equivalent_t , initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( sorted_equivalent_t , initializer_list < value_type > il , const Alloc ator & a ); // [flat.multimap.cons.alloc], constructors with allocators 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 Alloc > flat_multimap ( const key_compare & comp , const Alloc & a ); template < class Alloc > explicit flat_multimap ( 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 ( InputIterator first , InputIterator last , 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 InputIterator , class Alloc > flat_multimap ( sorted_equivalent_t , 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 Alloc > flat_multimap ( initializer_list < value_type > il , 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 ( sorted_equivalent_t , 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 ); flat_multimap & operator = ( initializer_list < value_type > il ); // iterators [...] 24.6.10.3 Constructors [flat.multimap.cons]
flat_multimap ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); 1․ Effects: Initializes
with
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 to
and otherwise N log N, where N is the value of
value_comp () before this call.
key_cont . size () template < class Alloc ator > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc ator & a ); 3․ Constraints:is
uses_allocator_v < key_container_type , Allocator > true
andis
uses_allocator_v < mapped_container_type , Allocator > true
.
4․ Effects: Equivalent to and
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
5․ Complexity: Same as and
flat_multimap ( key_cont , mapped_cont ) , respectively.
flat_multimap ( key_cont , mapped_cont , comp ) flat_multimap ( sorted_equivalent_t , key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); 6․ Effects: Initializes
with
c . keys ,
std :: move ( key_cont ) with
c . values , and
std :: move ( mapped_cont ) with
compare .
comp 7․ Complexity: Constant.
24.6.10.x Constructors with allocators [flat.multimap.cons.alloc]x․ 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 );
4․ Effects: Equivalent to and
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
5․ Complexity: Same as and
flat_multimap ( key_cont , mapped_cont ) , respectively.
flat_multimap ( key_cont , mapped_cont , comp ) template < class Alloc ator > flat_multimap ( sorted_equivalent_t s , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( sorted_equivalent_t s , const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Alloc ator & a ); 8․ Constraints:is
uses_allocator_v < key_container_type , Allocator > true
andis
uses_allocator_v < mapped_container_type , Allocator > true
.9․ Effects: Equivalent to
and
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 10․ Complexity: Linear.
template < class Alloc ator > flat_multimap ( const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > explicit flat_multimap ( const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multimap ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multimap ( InputIterator first , InputIterator last , const Alloc ator & a ); template < container - compatible - range < value_type > R , class Alloc ator > flat_multimap ( from_range_t , R && rg , const Alloc ator & a ); template < container - compatible - range < value_type > R , class Alloc ator > flat_multimap ( from_range_t , R && rg , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multimap ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multimap ( sorted_equivalent_t , InputIterator first , InputIterator last , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( initializer_list < value_type > il , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( sorted_equivalent_t , initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_multimap ( sorted_equivalent_t , initializer_list < value_type > il , const Alloc ator & a ); 11․ Constraints:is
uses_allocator_v < key_container_type , Allocator > true
andis
uses_allocator_v < mapped_container_type , Allocator > true
.12․ Effects: Equivalent to the corresponding non-allocator constructors except that
and
c . keys are constructed with uses-allocator construction ([allocator.uses.construction]).
c . values
3.3. [flat.multiset]
Change [flat.multiset.defn] as follows:
24.6.12.2 Definition [flat.multiset.defn][...] using container_type = KeyContainer ; // [flat.multiset.cons], constructors flat_multiset () : flat_multiset ( key_compare ()) { } explicit flat_multiset ( container_type cont , const key_compare & comp = key_compare ()); template < class Alloc ator > flat_multiset ( const container_type & cont , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( const container_type & cont , const key_compare & comp , const Alloc ator & a ); flat_multiset ( sorted_equivalent_t , container_type cont , const key_compare & comp = key_compare ()) : c ( std :: move ( cont )), compare ( comp ) { } template < class Alloc ator > flat_multiset ( sorted_equivalent_t , const container_type & , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( sorted_equivalent_t , const container_type & cont , const key_compare & comp , const Alloc ator & a ); explicit flat_multiset ( const key_compare & comp ) : c (), compare ( comp ) { } template < class Alloc ator > flat_multiset ( const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > explicit flat_multiset ( const Alloc ator & a ); template < class InputIterator > flat_multiset ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator , class Alloc ator > flat_multiset ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multiset ( InputIterator first , InputIterator last , const Alloc ator & a ); 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 , class Alloc ator > flat_multiset ( from_range_t , R && rg , const Alloc ator & a ); 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 )); } template < container - compatible - range < value_type > R , class Alloc ator > flat_multiset ( from_range_t , R && rg , const key_compare & comp , const Alloc ator & a ); template < class InputIterator > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c ( first , last ), compare ( comp ) { } template < class InputIterator , class Alloc ator > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const Alloc ator & a ); flat_multiset ( initializer_list < value_type > il , const key_compare & comp = key_compare ()) : flat_multiset ( il . begin (), il . end (), comp ) { } template < class Alloc ator > flat_multiset ( initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( initializer_list < value_type > il , const Alloc ator & a ); flat_multiset ( sorted_equivalent_t s , initializer_list < value_type > il , const key_compare & comp = key_compare ()) : flat_multiset ( s , il . begin (), il . end (), comp ) { } template < class Alloc ator > flat_multiset ( sorted_equivalent_t , initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( sorted_equivalent_t , initializer_list < value_type > il , const Alloc ator & a ); // [flat.multiset.cons.alloc], constructors with allocators 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 Alloc > flat_multiset ( const key_compare & comp , const Alloc & a ); template < class Alloc > explicit flat_multiset ( 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 ( InputIterator first , InputIterator last , 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 InputIterator , class Alloc > flat_multiset ( sorted_equivalent_t , 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 Alloc > flat_multiset ( initializer_list < value_type > il , 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 ( sorted_equivalent_t , 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 ); flat_multiset & operator = ( initializer_list < value_type > ); // iterators [...] 24.6.12.3 Constructors [flat.multiset.cons]
explicit flat_multiset ( container_type cont , const key_compare & comp = 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
24.6.12.x Constructors with allocators [flat.multiset.cons.alloc]is already sorted with respect to
cont and otherwise N log N, where N is the value of
compare before this call.
cont . size () x․ The constructors in this subclause shall not participate in overload resolution unless
is
uses_allocator_v < container_type , Alloc > true
.template < class Alloc ator > flat_multiset ( const container_type & cont , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( const container_type & cont , const key_compare & comp , const Alloc ator & a );
3․ Constraints:is
uses_allocator_v < container_type , Allocator > true
.4․ Effects: Equivalent to
and
flat_multiset ( cont ) , respectively, except that c is constructed with uses-allocator construction ([allocator.uses.construction]).
flat_multiset ( cont , comp ) 5․ Complexity: Same as
and
flat_multiset ( cont ) , respectively.
flat_multiset ( cont , comp ) template < class Alloc ator > flat_multiset ( sorted_equivalent_t s , const container_type & cont , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( sorted_equivalent_t s , const container_type & cont , const key_compare & comp , const Alloc ator & a );
6․ Constraints:is
uses_allocator_v < container_type , Allocator > true
.7․ Effects: Equivalent to
and
flat_multiset ( s , cont ) , respectively, except that
flat_multiset ( s , cont , comp ) is constructed with uses-allocator construction ([allocator.uses.construction]).
c 8․ Complexity: Linear.
template < class Alloc ator > flat_multiset ( const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > explicit flat_multiset ( const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multiset ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multiset ( InputIterator first , InputIterator last , const Alloc ator & a ); template < container - compatible - range < value_type > R , class Alloc ator > flat_multiset ( from_range_t , R && rg , const Alloc ator & a ); template < container - compatible - range < value_type > R , class Alloc ator > flat_multiset ( from_range_t , R && rg , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_multiset ( sorted_equivalent_t , InputIterator first , InputIterator last , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( initializer_list < value_type > il , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( sorted_equivalent_t , initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_multiset ( sorted_equivalent_t , initializer_list < value_type > il , const Alloc ator & a );
9․ Constraints:is
uses_allocator_v < container_type , Allocator > true
.10․ Effects: Equivalent to the corresponding non-allocator constructors except that
is constructed with uses-allocator construction ([allocator.uses.construction]).
c
3.4. [flat.set]
Change [flat.set.defn] as follows:
24.6.11.2 Definition [flat.set.defn][...] using container_type = KeyContainer ; // [flat.set.cons], constructors flat_set () : flat_set ( key_compare ()) { } explicit flat_set ( container_type cont , const key_compare & comp = key_compare ()); template < class Alloc ator > flat_set ( const container_type & cont , const Alloc ator & a ); template < class Alloc ator > flat_set ( const container_type & cont , const key_compare & comp , const Alloc ator & a ); flat_set ( sorted_unique_t , container_type cont , const key_compare & comp = key_compare ()) : c ( std :: move ( cont )), compare ( comp ) { } template < class Alloc ator > flat_set ( sorted_unique_t , const container_type & cont , const Alloc ator & a ); template < class Alloc ator > flat_set ( sorted_unique_t , const container_type & cont , const key_compare & comp , const Alloc ator & a ); explicit flat_set ( const key_compare & comp ) : c (), compare ( comp ) { } template < class Alloc ator > flat_set ( const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > explicit flat_set ( const Alloc ator & a ); template < class InputIterator > flat_set ( InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c (), compare ( comp ) { insert ( first , last ); } template < class InputIterator , class Alloc ator > flat_set ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_set ( InputIterator first , InputIterator last , const Alloc ator & a ); 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 , class Alloc ator > flat_set ( from_range_t , R && rg , const Alloc ator & a ); 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 )); } template < container - compatible - range < value_type > R , class Alloc ator > flat_set ( from_range_t , R && rg , const key_compare & comp , const Alloc ator & a ); template < class InputIterator > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp = key_compare ()) : c ( first , last ), compare ( comp ) { } template < class InputIterator , class Alloc ator > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const Alloc ator & a ); flat_set ( initializer_list < value_type > il , const key_compare & comp = key_compare ()) : flat_set ( il . begin (), il . end (), comp ) { } template < class Alloc ator > flat_set ( initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_set ( initializer_list < value_type > il , const Alloc ator & a ); flat_set ( sorted_unique_t s , initializer_list < value_type > il , const key_compare & comp = key_compare ()) : flat_set ( s , il . begin (), il . end (), comp ) { } template < class Alloc ator > flat_set ( sorted_unique_t , initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_set ( sorted_unique_t , initializer_list < value_type > il , const Alloc ator & a ); // [flat.set.cons.alloc], constructors with allocators 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 & cont , const Alloc & a ); template < class Alloc > flat_set ( sorted_unique_t , const container_type & cont , const key_compare & comp , const Alloc & a ); template < class Alloc > flat_set ( const key_compare & comp , const Alloc & a ); template < class Alloc > explicit flat_set ( 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 ( InputIterator first , InputIterator last , 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 InputIterator , class Alloc > flat_set ( sorted_unique_t , 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 Alloc > flat_set ( initializer_list < value_type > il , 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 ( sorted_unique_t , 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 ); flat_set & operator = ( initializer_list < value_type > ); // iterators [...] 24.6.11.3 Constructors [flat.set.cons]
explicit flat_set ( container_type cont , const key_compare & comp = 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
24.6.11.x Constructors with allocators [flat.set.cons.alloc]is already sorted with respect to
cont and otherwise N log N, where N is the value of
compare before this call.
cont . size () x․ The constructors in this subclause shall not participate in overload resolution unless
is
uses_allocator_v < container_type , Alloc > true
.template < class Alloc ator > flat_set ( const container_type & cont , const Alloc ator & a ); template < class Alloc ator > flat_set ( const container_type & cont , const key_compare & comp , const Alloc ator & a );
3․ Constraints:is
uses_allocator_v < container_type , Allocator > true
.4․ Effects: Equivalent to
and
flat_set ( cont ) , respectively, except that
flat_set ( cont , comp ) is constructed with uses-allocator construction ([allocator.uses.construction]).
c 5․ Complexity: Same as
and
flat_set ( cont ) , respectively.
flat_set ( cont , comp ) template < class Alloc ator > flat_set ( sorted_unique_t s , const container_type & cont , const Alloc ator & a ); template < class Alloc ator > flat_set ( sorted_unique_t s , const container_type & cont , const key_compare & comp , const Alloc ator & a );
6․ Constraints:is
uses_allocator_v < container_type , Allocator > true
.7․ Effects: Equivalent to
and
flat_set ( s , cont ) , respectively, except that
flat_set ( s , cont , comp ) is constructed with uses-allocator construction ([allocator.uses.construction]).
c 8․ Complexity: Linear.
template < class Alloc ator > flat_set ( const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > explicit flat_set ( const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_set ( InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_set ( InputIterator first , InputIterator last , const Alloc ator & a ); template < container - compatible - range < value_type > R , class Alloc ator > flat_set ( from_range_t , R && rg , const Alloc ator & a ); template < container - compatible - range < value_type > R , class Alloc ator > flat_set ( from_range_t , R && rg , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const key_compare & comp , const Alloc ator & a ); template < class InputIterator , class Alloc ator > flat_set ( sorted_unique_t , InputIterator first , InputIterator last , const Alloc ator & a ); template < class Alloc ator > flat_set ( initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_set ( initializer_list < value_type > il , const Alloc ator & a ); template < class Alloc ator > flat_set ( sorted_unique_t , initializer_list < value_type > il , const key_compare & comp , const Alloc ator & a ); template < class Alloc ator > flat_set ( sorted_unique_t , initializer_list < value_type > il , const Alloc ator & a );
9․ Constraints:is
uses_allocator_v < container_type , Allocator > true
.10․ Effects: Equivalent to the corresponding non-allocator constructors except that
is constructed with uses-allocator construction ([allocator.uses.construction]).
c
4. Accidentally explicit constructor
STL style is that multi-argument constructors should be non-
; see [P1163].
This change is non-editorial, but should be non-controversial, unless anyone objects
to the delegating constructor’s doing an extra move of
. In practice, libc++
doesn’t actually move
twice; we consider the delegating constructor merely a tool
to shorten the spec.
std :: vector < int > v ; std :: flat_set s = { v , std :: less (), std :: allocator < int > () }; // OK std :: flat_set s = { v , std :: less () }; // Before: Ill-formed // After: OK
4.1. Wording
Change [flat.multiset.defn] as follows:
// [flat.multiset.cons], constructors flat_multiset () : flat_multiset ( key_compare ()) { } explicit flat_multiset ( container_type cont , const key_compare & comp = key_compare ()); explicit flat_multiset ( container_type cont ) : flat_multiset ( std :: move ( cont ), key_compare ()) { } flat_multiset ( container_type cont , const key_compare & comp );
Change [flat.multiset.cons] as follows:
explicit flat_multiset ( container_type cont , const key_compare & comp = key_compare () );
Change [flat.set.defn] as follows:
// [flat.set.cons], constructors flat_set () : flat_set ( key_compare ()) { } explicit flat_set ( container_type cont , const key_compare & comp = key_compare ()); explicit flat_set ( container_type cont ) : flat_set ( std :: move ( cont ), key_compare ()) { } flat_set ( container_type cont , const key_compare & comp );
Change [flat.set.cons] as follows:
explicit flat_set ( container_type cont , const key_compare & comp = key_compare () );
5. 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.
5.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 to
as if by:
c for ( const auto & auto && e : rg ) { c . insert ( c . end (), std :: forward < decltype ( e ) > ( 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.
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 elements to
as if by:
c Then, sorts the range of newly inserted elements with respect tofor ( auto && e : rg ) { c . insert ( c . end (), std :: forward < decltype ( e ) > ( e )); } , 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.
6. Add move semantics to flat_map :: insert_range
'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.
However, libc++'s implementation of
and
didn’t need
to change — we already "accidentally" support move semantics in the maps'
methods,
because we factor out a helper method
that is used by all three of
,
, and
.
It looks very similar to the specification proposed below.
The current specification says:
for ( const auto & e : rg ) { c . keys . insert ( c . keys . end (), e . first ); c . values . insert ( c . values . end (), e . second ); }
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
6.1. Wording
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 & value_type e : rg ) { 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 = ranges :: zip_view 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.
7. 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.
7.1. Heterogeneous multiset :: insert
[P2363] explains why the node-based
still lacks a heterogeneous
:
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
For
, the last sentence is false; generic
is unusable for PMR allocators, because it
must first construct
using the default allocator. Therefore we also propose to add a heterogeneous-comparison
overload of
.
template < class T , class C = std :: less < T >> using PmrFlatMultiset = std :: flat_multiset < T , C , std :: pmr :: vector < T >> ; std :: pmr :: monotonic_buffer_resource mr ( 1 ’000 ’000 ); std :: pmr :: set_default_resource ( std :: pmr :: null_memory_resource ()); const char s [] = "too long to fit in the small string buffer" ; auto m = std :: pmr :: multiset < std :: pmr :: string > ( & mr ); m . emplace ( s ); // OK m . insert ( s ); // runtime abort, cannot default-allocate x auto fm = PmrFlatMultiset < std :: pmr :: string > ( & mr ); fm . emplace ( s ); // runtime abort, cannot default-allocate t fm . insert ( s ); // runtime abort, cannot default-allocate x auto m2 = std :: pmr :: multiset < std :: pmr :: string , std :: less <>> ( & mr ); m2 . emplace ( s ); // OK m2 . insert ( s ); // runtime abort, cannot default-allocate x auto fm2 = PmrFlatMultiset < std :: pmr :: string , std :: less <>> ( & mr ); fm2 . emplace ( s ); // runtime abort, cannot default-allocate t fm2 . insert ( s ); // Before: runtime abort, cannot default-allocate x // After: OK
Notice the asymmetry between
and
. Arthur thinks it would be
nice to support heterogeneous
for node-based
and
(contra P2363’s reasoning),
but that’s out of scope for this particular paper P2767. For P2767’s purposes, we’re satisfied
once there exists some way of inserting
into
. We don’t mind that the working approaches
use different spellings:
versus
.
7.2. "Initializes" phrasing
The proposed wording below changes [flat.map.modifiers]/2 as follows, in 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
However, could we eliminate the jargon entirely by doing something like this instead? As written I don’t think this is proper standardese, but maybe LWG can fix it up:
2․ Effects:Initializes an objectLetof type
t with
pair < key_type , mapped_type > ; if
std :: forward < Args > ( args )... be
t . If the map already contains an element whose key is equivalent to
pair < key_type , mapped_type > ( std :: forward < Args > ( args )...) ,
t . first is unchanged. [...]
* this
7.3. Unusual 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.
7.4. Ambiguity with 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.
7.5. Wording
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 ); template < class ... Args > iterator emplace_hint ( const_iterator position , 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:
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. 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: 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.
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 )); template pair < iterator , bool > insert ( P && x ); template 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 ); template < class ... Args > iterator emplace_hint ( const_iterator position , 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 )); } template < class K > iterator 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.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.
template < class K > iterator insert ( K && x ); template < class K > iterator insert ( const_iterator hint , K && x ); x․ 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
.x․ Preconditions: The conversion from
into
x constructs an object
value_type for which
u is
find ( x ) == find ( u ) true
.x․ 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 )) 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 whose key is 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 element whose key is equivalent to.
x
8. insert_range ( sorted_unique , rg )
The multi-element insertion API consists of these overloads:
insert ( first , last ); insert ( il ); insert_range ( rg ); insert ( sorted_unique , first , last ); insert ( sorted_unique , il );
An overload of
is conspicuously missing.
auto rg = std :: views :: iota ( 0 , 100 ) | std :: take_while ( lessThan50 ); assert ( ! std :: ranges :: common_range < decltype ( rg ) > ); assert ( std :: ranges :: is_sorted ( rg )); std :: flat_set < int > fs ; fs . insert_range ( rg ); // OK, but unnecessarily re-sorts the input if ( auto cv = rg | std :: views :: common ; true) { fs . insert ( std :: sorted_unique , cv . begin (), cv . end ()); // OK, doesn’t re-sort, but arcane } fs . insert_range ( std :: sorted_unique , rg ); // Before: Ill-formed // After: OK
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 could add this overload easily.
8.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 < 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 < 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 < 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 ()
9. Sorting complexity
Most operations that require sorting come in
and non-
flavors;
the
flavor is rightly specified not to sort, which means it can be O(N)
instead of O(N + M log M). That’s fine. But some non-
constructors have
Complexity clauses that mandate O(N) performance on input that happens to be sorted
at runtime. The vendor has three ways to deal with this:
-
Guarantee that
(orstd :: sort
if § 12.1 Stable sorting in insert 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.
If we changed [alg.sort], then we’d get [flat.foo]'s unusual complexity guarantees for free, and the following changes in [flat.foo] would be no-ops, just a bit of editorial cleanup. However, Louis and Arthur would prefer to leave [alg.sort] alone for now, making the following changes in [flat.foo] significant and non-editorial.
9.1. Wording
If these changes are not adopted wholesale, we still propose editorially to replace
with
and "if the container arguments are already sorted with respect to
" with
"if
is already sorted with respect to
."
Change [flat.map.cons] as follows:
flat_map ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); 1․ Effects: Initializes
with
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 = ranges :: zip_view 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 toSame asand otherwise N log N, where N is the value of
value_comp () before this call.
key_cont . size () .
ranges :: sort ( ranges :: zip ( c . keys , c . values ), value_comp ())
Change [flat.multimap.cons] as follows:
flat_multimap ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare ()); 1․ Effects: Initializes
with
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 toSame asand otherwise N log N, where N is the value of
value_comp () before this call.
key_cont . size () .
ranges :: sort ( ranges :: zip ( c . keys , c . values ), value_comp ())
Change [flat.multiset.cons] as follows:
explicit flat_multiset ( container_type cont , const key_compare & comp = key_compare ()); 1․ Effects: Initializes
with
c and
std :: move ( cont ) with
compare
comp , and; sorts the range [,
begin () ) with respect to
end () .
compare 2․ Complexity:
Linear in N ifSame asis sorted with respect to
cont and otherwise N log N, where N is the value of
compare before this call.
cont . size () .
ranges :: sort ( c , compare )
Change [flat.set.cons] as follows:
explicit flat_set ( container_type cont , const key_compare & comp = key_compare ()); 1․ Effects: Initializes
with
c and
std :: move ( cont ) with
compare
comp ,; sorts the range [,
begin () ) with respect to
end ()
compare ,; and finally erases all but the first element from each group of consecutive equivalent elements.2․ Complexity:
Linear in N ifSame asis sorted with respect to
cont and otherwise N log N, where N is the value of
compare before this call.
cont . size () .
ranges :: sort ( c , compare )
10. replace
should take by value
The current specification for
takes the new container(s) by rvalue reference,
which means you can’t just pass in a container the way you can with the
constructor;
instead, you have to manually
the container.
This might have been originally intended as a guard against accidental expensive copying of containers.
But C++ doesn’t use this (explicit-pass-by-rvalue-reference) pattern anywhere else; and it’s
inconsistent with the specification of the
constructors, which do take by value
and happily allow passing in lvalue containers by copy.
std :: vector < int > v = { 1 , 2 , 3 }; std :: flat_set < int > fs ; fs = std :: flat_set ( v ); // OK fs . replace ( std :: vector ( v )); // OK fs . replace ( v ); // Before: Ill-formed // After: OK
Taking by value and move-constructing into place is almost always just as performant as
taking by rvalue-reference and move-constructing into place. Caveat: some containers
are expensive to move-construct.
is not such a container.
Boost
is. But we shouldn’t cater for such types, especially not at
the cost of API consistency.
boost :: container :: static_vector < int , 100 > v ; auto fs = std :: flat_set ( std :: move ( v )); // OK, does 2 expensive moves fs . replace ( std :: move ( v )); // Before: OK, does 1 expensive move // After: OK, does 2 expensive moves fs . replace ( v ); // Before: Ill-formed // After: OK, does 1 expensive copy and 1 expensive move
10.1. Wording
Change [flat.map.defn] as follows:
containers extract () && ; void replace ( key_container_type && key_cont , mapped_container_type && mapped_cont );
Change [flat.map.modifiers] as follows:
void replace ( 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:
containers extract () && ; void replace ( key_container_type && key_cont , mapped_container_type && mapped_cont );
Change [flat.multiset.defn] as follows:
container_type extract () && ; void replace ( container_type && );
Change [flat.multiset.modifiers] as follows:
void replace ( container_type && cont ); 12․ Preconditions: The elements of
are sorted with respect to
cont .
compare 13․ Effects: Equivalent to:
c = std :: move ( cont );
Change [flat.set.defn] as follows:
container_type extract () && ; void replace ( container_type && );
Change [flat.set.modifiers] as follows:
void replace ( container_type && cont ); 12․ Preconditions: The elements of
are sorted with respect to
cont , and
compare contains no equal elements.
cont 13․ Effects: Equivalent to:
c = std :: move ( cont );
11. Add flat_set :: keys ()
and
both 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.
auto ks = std :: vector < int , A > ({ 1 , 2 , 3 }, A ( 1 )); auto vs = std :: vector < int , A > ({ 4 , 5 , 6 }, A ( 2 )); auto m = std :: flat_map ( std :: move ( ks ), std :: move ( vs )); assert ( m . keys (). get_allocator () == A ( 1 )); assert ( m . values (). get_allocator () == A ( 2 )); // convenient auto ks = std :: vector < int , A > ({ 1 , 2 , 3 }, A ( 1 )); auto s = std :: flat_set ( std :: move ( ks )); assert ( std :: move ( s ). extract (). get_allocator () == A ( 1 )); // awkward, and modifies the value of s
For a const
, there’s literally no way to get at the container
and query its properties, such as capacity, allocator, or even size
(which is important for unit tests, if not for real programming).
using S = std :: flat_set < int , std :: less <> , std :: vector < int , A >> ; const S s = { 1 , 2 , 3 }; assert ( s . keys (). size () == 3 ); assert ( s . keys (). capacity () >= 3 ); // Before: Impossible to retrieve this information // After: OK
Therefore we suggest adding a getter for the
of a set, just
like we already have for the flat map types.
Notice that
's
getter returns a container of
,
not a container of
.
doesn’t have a
;
therefore it shouldn’t have
either.
11.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 ; }
12. Issues for discussion
The following subsections describe known issues with the flat containers where we don’t (yet) strongly propose a change. In some cases Louis Dionne asks for further clarification and/or changes to the wording. In some cases we’re just reporting implementation experience. In some cases it seems like a change is warranted, but it’s confusing enough that we don’t have a good wording patch yet.
12.1. Stable sorting in insert
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).
12.2. Non-explicit constructor from two containers
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 > ); // Test the two-container constructor... or is it? print_map ({ { 1 , 2 , 3 }, { 10 , 20 , 30 } }); // {1,10}, {2,20}, {3,30} print_map ({ { 1 , 2 }, { 10 , 20 } }); // {1,2}, {10,20} print_map ({ { 1 }, { 10 } }); // {1,10} print_map ({ {}, {} }); // {0,0}, {0,0}
To address this issue (if we wanted to), we could make one of the relevant constructors
.
We obviously can’t prevent
's construction from a braced list
of elements, nor
's from a braced list of (exactly two) elements, nor (in practice)
's construction from
; so the only constructor we could explicify to address this
would be
's own constructor from a braced list of (exactly two) containers. That is,
we’d do this (and likewise in
;
doesn’t need to change):
explicit flat_multimap ( key_container_type key_cont , mapped_container_type mapped_cont ) : flat_multimap ( std :: move ( key_cont ), std :: move ( mapped_cont ), key_compare ()) {} flat_multimap ( key_container_type key_cont , mapped_container_type mapped_cont , const key_compare & comp = key_compare () ); template < class Allocator > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const Allocator & a ); template < class Allocator > flat_multimap ( const key_container_type & key_cont , const mapped_container_type & mapped_cont , const key_compare & comp , const Allocator & a );
Arthur thinks this change would be appreciated by programmers in practice (or rather, if we don’t make this change, then programmers will only ever notice this issue when it bites them). But it would be surprisingly asymmetric to explicify only this one constructor. The small benefit probably does not justify the asymmetry.
We recently made some constructors
in P2711 "Making multi-param constructors of views explicit," but that paper’s goal was to increase uniformity and symmetry, not to decrease it.
12.2.1. Remove the container constructors?
A more ambitious alternative would be to eliminate the container constructors entirely,
and say that the only way to move containers into a flat container at all is via
,
after the initial construction has already happened. This would be analogous to
or
— a feature with no associated constructor, just a setter
after the fact. This would be really bold; but if we eliminated them, then we would also solve [LWG3802] (§ 12.11 Allocator-extended container constructors lack move semantics) in one fell swoop, and reduce the size of the constructor overload
set from 38 constructors down to 20.
Another reason to think about eliminating the container constructors — at least the non-
ones —
is faster than the more
convenient syntax
; this might be a pitfall for the average programmer.
auto ks = std :: pmr :: vector < int > ({ 1 , 2 , 3 }, & mr1 ); auto vs = std :: pmr :: vector < int > ({ 1 , 2 , 3 }, & mr2 ); using FS = std :: flat_set < int , std :: less <> , decltype ( ks ) > ; // Before: FS fs1 = FS ( std :: sorted_unique , std :: move ( ks )); // After: FS fs1 = FS ( ks . get_allocator ()); fs1 . replace ( std :: move ( ks )); using FM = std :: flat_map < int , int , std :: less <> , decltype ( ks ), decltype ( vs ) > ; // Before: FM fm1 = FM ( std :: sorted_unique , std :: move ( ks ), std :: move ( vs )); // Two different allocators in the same adaptor. // Almost certainly there is no valid reason to do this. // After: FM fm2 = FM ( ks . get_allocator ()); fm2 . replace ({ std :: move ( ks ), std :: move ( vs ) }); // The data from vs is copied into arena mr1. // This is less performant, but more sane. // It is physically impossible to create the equivalent of fm1 above.
A downside is that it’s tedious for the programmer to reproduce the effect of the
non-
container constructors:
// Before: FS fs2 = FS ( std :: move ( ks )); // After: FS fs2 = FS ( ks . get_allocator ()); std :: ranges :: sort ( ks ); ks . erase ( std :: ranges :: unique ( ks ). begin (), ks . end ()); fs2 . replace ( std :: move ( ks )); // After, if you can afford one extra allocation: FS fs2 = FS ( std :: from_range , ks | std :: views :: as_rvalue , ks . get_allocator ());
We could boldly address this by making
always sort, and then if you know
your data is already sorted, you would call
. That would
make the interface much more symmetric, too, because the sortedness
precondition would be 100% always indicated by the presence of
in the signature.
It is very late in the game to be doing design work; but this particular redesign does seem to have a lot of varied benefits for vendors and programmers, with only procedural downsides. I haven’t written wording for this yet, but can do so if LWG is interested.
12.3. 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).
12.3.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 ))
12.4. 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 four associative and four unordered containers (e.g. [set.overview]) explicitly provide signatures for all five special members, including the 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 :: experimental :: fixed_capacity_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.
12.5. 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.
12.5.1. Possible wording
(This wording merge-conflicts with the resolution of [LWG3884], which in turn merge-conflicts with the editorial refactoring at the beginning of this paper P2767. We can polish it later; the first question is whether LWG wants to pursue this direction at all.)
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 ());
12.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."
12.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
.
12.7. 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."
12.8. 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.
12.9. 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"?
12.10. 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
12.10.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.
12.11. Allocator-extended container constructors lack move semantics
(This whole section would be mooted by § 12.2.1 Remove the container constructors?.)
libc++ has implemented move-semantic allocator-extended constructors for all four flat containers (Godbolt). This is [LWG3802].
template < class T , class C = std :: less < T >> using PmrFlatSet = std :: flat_set < T , C , std :: pmr :: vector < T >> ; std :: pmr :: monotonic_buffer_resource mr ; auto sets = std :: pmr :: vector < PmrFlatSet < int >> ( & mr ); auto cont1 = std :: pmr :: vector < int > ({ 1 , 2 , 3 }, & mr ); sets . emplace_back ( std :: move ( cont1 )); // Before: Makes copies of all the data // After: Takes ownership of the existing data auto cont2 = std :: pmr :: vector < std :: unique_ptr < int >> (); auto fs = std :: flat_set ( std :: move ( cont2 )); // Before: Ill-formed // After: OK
The wording patch for this (based on top of P2767’s editorial refactoring) would look something like this. I haven’t written wording for this yet, but can do so, if LWG is interested, after P2767’s editorial refactoring is merged.
// [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 );
13. 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 this patched libc++ by default.
14. 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