1. Introduction
[flat.map.defn] defines
as:
template < class Key , class T , class Compare = less < Key > , class KeyContainer = vector < Key > , class MappedContainer = vector < T >> class flat_map {
[flat.map.overview]/7
requires that and be sequence containers.
Notably, is
not a sequence container.
Because and both default to , the
following code is ill-formed:
std :: flat_map < int , bool > my_map ;
To use as a mapped type, the user must specify an alternative sequence
container, such as :
std :: flat_map < int , bool , std :: less < int > , std :: vector < int > , std :: deque < bool >> my_map ;
This formulation is verbose and requires the user to know exactly which alternative sequence container to drop in. Additionally, the compilation errors generated when a user fails to override the container are difficult to understand, especially for new C++ developers.
A similar issue exists for , , and .
This paper proposes changes to , , , and
so that is usable as an underlying container without
affecting any other conforming instantiations.
2. Details
Before covering the proposed wording changes, it is important to understand why
cannot be used as the container in the adaptors. We will
center the discussion on , but the same concepts apply to the other
three flat adaptors.
There are two problems:
-
Incompatible Reference Types:
,reference and the return types ofconst_reference andflat_map :: operator [] are spelled in terms offlat_map :: at andmapped_type (see [flat.map.defn]). The reference type forvalue_type is notvector < bool > . It is a proxy class that simulates a direct reference to abool & .bool -
Sequence Container Requirement: The sequence container requirement explicitly excludes
(see [flat.map.overview]/7).vector < bool >
The fix for the first problem is to replace as the reference type with
whatever reference type the underlying container’s iterators produce (via
).
For every container that is valid today this is a no-op.
is and
is , so the typedefs and
signatures remain unchanged for all currently-supported containers.
For , is the proxy class type and
is . These resolutions make the
declarations well-formed, allowing code like .
The fix for the second problem is to admit by name.
does not meet the sequence container requirements. As just one
example, violates the equal
container requirement. Other than the wording change, no other changes are
required to address the second problem.
3. Other Container Adaptors
, , and do not suffer from this problem because their
and typedefs are defined in terms of the
underlying container. As an example, consider [priority.queue]:
namespace std { template < class T , class Container = vector < T > , class Compare = less < typename Container :: value_type >> class priority_queue { public : using value_type = Container :: value_type ; using reference = Container :: reference ; using const_reference = Container :: const_reference ; using size_type = Container :: size_type ; using container_type = Container ; using value_compare = Compare ; // ...
In these cases, the adaptors expose whatever reference type the underlying container
provides. In the case of , resolves to
, which is the proxy class.
4. bool Keys and Sets
The case that motivates this proposal is . The same change also
makes work as a key (, ,
, ).
and hold at most two entries.
and hold at most two distinct
keys. These instantiations are not particularly useful, and we don’t expect
people to use them often. However, it’s more consistent to support them.
Additionally, once is permitted on the mapped side, allowing it
on the key side is trivial.
5. Proposed Wording
Insertions are shown as
green text
and deletions as
red strike-through text
.
5.1. [flat.map]
Modify [flat.map.overview]/7 as follows:
Any type
that meets the sequence container requirements ([sequence.reqmts]) , orC ([vector.bool]), can be used to instantiatevector < bool > , as long asflat_map meets the Cpp17RandomAccessIterator requirements and invocations of member functionsC :: iterator andC :: size do not exit via an exception. In particular,C :: max_size ([vector]) ,vector , andvector < bool > ([deque]) can be used.deque
[Note 3:Whileis not a sequence container.vector < bool > is not a sequence container, it provides the necessary interface and proxy iterators to be supported here. — end note]vector < bool >
Modify [flat.map.defn] as follows:
namespace std { template < class Key , class T , class Compare = less < Key > , class KeyContainer = vector < Key > , class MappedContainer = vector < T >> class flat_map { public : // types using key_type = Key ; using mapped_type = T ; using value_type = pair < key_type , mapped_type > ; using key_compare = Compare ; using value_compare = unspecified ; using reference = pair < const key_type & , mapped_type &> ; using const_reference = pair < const key_type & , const mapped_type &> ; using key - const - reference = iter_reference_t < typename KeyContainer :: const_iterator > ; // exposition only using mapped - reference = iter_reference_t < typename MappedContainer :: iterator > ; // exposition only using mapped - const - reference = iter_reference_t < typename MappedContainer :: const_iterator > ; // exposition only using reference = pair < key - const - reference , mapped - reference > ; using const_reference = pair < key - const - reference , mapped - const - reference > ; using size_type = size_t ; using difference_type = ptrdiff_t ; using iterator = implementation - defined ; // see [container.requirements] using const_iterator = implementation - defined ; // see [container.requirements] using reverse_iterator = std :: reverse_iterator < iterator > ; using const_reverse_iterator = std :: reverse_iterator < const_iterator > ; using key_container_type = KeyContainer ; using mapped_container_type = MappedContainer ; // ... class value_compare { private : key_compare comp ; // exposition only constexpr value_compare ( key_compare c ) : comp ( c ) {} // exposition only public : bool operator ()( const_reference x , const_reference y ) const { bool operator ()( pair < const key_type & , const mapped_type &> x , pair < const key_type & , const mapped_type &> y ) const { return comp ( x . first , y . first ); } }; // ... }; }
Modify [flat.map.access] as follows:
mapped_type & operator []( const key_type & x ); iter_reference_t < typename MappedContainer :: iterator > operator []( const key_type & x ); mapped_type & operator []( key_type && x ); iter_reference_t < typename MappedContainer :: iterator > operator []( key_type && x ); template < class K > mapped_type & operator []( K && x ); iter_reference_t < typename MappedContainer :: iterator > operator []( K && x ); mapped_type & at ( const key_type & x ); iter_reference_t < typename MappedContainer :: iterator > at ( const key_type & x ); const mapped_type & at ( const key_type & x ) const ; iter_reference_t < typename MappedContainer :: const_iterator > at ( const key_type & x ) const ; template < class K > mapped_type & at ( const K & x ); iter_reference_t < typename MappedContainer :: iterator > at ( const K & x ); template < class K > const mapped_type & at ( const K & x ) const ; iter_reference_t < typename MappedContainer :: const_iterator > at ( const K & x ) const ;
5.2. [flat.multimap]
Modify [flat.multimap.overview]/7 as follows:
Any type
that meets the sequence container requirements ([sequence.reqmts]) , orC ([vector.bool]), can be used to instantiatevector < bool > , as long asflat_multimap meets the Cpp17RandomAccessIterator requirements and invocations of member functionsC :: iterator andC :: size do not exit via an exception. In particular,C :: max_size ([vector]) ,vector , andvector < bool > ([deque]) can be used.deque
[Note 3:Whileis not a sequence container.vector < bool > is not a sequence container, it provides the necessary interface and proxy iterators to be supported here. — end note]vector < bool >
Modify [flat.multimap.defn] as follows:
namespace std { template < class Key , class T , class Compare = less < Key > , class KeyContainer = vector < Key > , class MappedContainer = vector < T >> class flat_multimap { public : // types using key_type = Key ; using mapped_type = T ; using value_type = pair < key_type , mapped_type > ; using key_compare = Compare ; using value_compare = unspecified ; using reference = pair < const key_type & , mapped_type &> ; using const_reference = pair < const key_type & , const mapped_type &> ; using key - const - reference = iter_reference_t < typename KeyContainer :: const_iterator > ; // exposition only using mapped - reference = iter_reference_t < typename MappedContainer :: iterator > ; // exposition only using mapped - const - reference = iter_reference_t < typename MappedContainer :: const_iterator > ; // exposition only using reference = pair < key - const - reference , mapped - reference > ; using const_reference = pair < key - const - reference , mapped - const - reference > ; using size_type = size_t ; using difference_type = ptrdiff_t ; using iterator = implementation - defined ; // see [container.requirements] using const_iterator = implementation - defined ; // see [container.requirements] using reverse_iterator = std :: reverse_iterator < iterator > ; using const_reverse_iterator = std :: reverse_iterator < const_iterator > ; using key_container_type = KeyContainer ; using mapped_container_type = MappedContainer ; // ... class value_compare { private : key_compare comp ; // exposition only constexpr value_compare ( key_compare c ) : comp ( c ) {} // exposition only public : bool operator ()( const_reference x , const_reference y ) const { bool operator ()( pair < const key_type & , const mapped_type &> x , pair < const key_type & , const mapped_type &> y ) const { return comp ( x . first , y . first ); } }; // ... }; }
5.3. [flat.set]
Modify [flat.set.overview]/7 as follows:
Any sequence container ([sequence.reqmts]) supporting Cpp17RandomAccessIterator , or
([vector.bool]), can be used to instantiatevector < bool > . In particular,flat_set ([vector]) ,vector , andvector < bool > ([deque]) can be used.deque
[Note 3:Whileis not a sequence container.vector < bool > is not a sequence container, it provides the necessary interface and proxy iterators to be supported here. — end note]vector < bool >
Modify [flat.set.defn] as follows:
namespace std { template < class Key , class Compare = less < Key > , class KeyContainer = vector < Key >> class flat_set { public : // types using key_type = Key ; using value_type = Key ; using key_compare = Compare ; using value_compare = Compare ; using reference = value_type & ; using const_reference = const value_type & ; using reference = iter_reference_t < typename KeyContainer :: iterator > ; using const_reference = iter_reference_t < typename KeyContainer :: const_iterator > ; using size_type = typename KeyContainer :: size_type ; using difference_type = typename KeyContainer :: difference_type ; using iterator = implementation - defined ; // see [container.requirements] using const_iterator = iterator ; using reverse_iterator = std :: reverse_iterator < iterator > ; using const_reverse_iterator = std :: reverse_iterator < const_iterator > ; using container_type = KeyContainer ; // ... }; }
5.4. [flat.multiset]
Modify [flat.multiset.overview]/7 as follows:
Any sequence container ([sequence.reqmts]) supporting Cpp17RandomAccessIterator , or
([vector.bool]), can be used to instantiatevector < bool > . In particular,flat_multiset ([vector]) ,vector , andvector < bool > ([deque]) can be used.deque
[Note 3:Whileis not a sequence container.vector < bool > is not a sequence container, it provides the necessary interface and proxy iterators to be supported here. — end note]vector < bool >
Modify [flat.multiset.defn] as follows:
namespace std { template < class Key , class Compare = less < Key > , class KeyContainer = vector < Key >> class flat_multiset { public : // types using key_type = Key ; using value_type = Key ; using key_compare = Compare ; using value_compare = Compare ; using reference = value_type & ; using const_reference = const value_type & ; using reference = iter_reference_t < typename KeyContainer :: iterator > ; using const_reference = iter_reference_t < typename KeyContainer :: const_iterator > ; using size_type = typename KeyContainer :: size_type ; using difference_type = typename KeyContainer :: difference_type ; using iterator = implementation - defined ; // see [container.requirements] using const_iterator = iterator ; using reverse_iterator = std :: reverse_iterator < iterator > ; using const_reverse_iterator = std :: reverse_iterator < const_iterator > ; using container_type = KeyContainer ; // ... }; }
6. Implementation Experience
The proposed wording has been implemented in libc++ on the
branch in github.com/aryann/llvm-project.
The existing flat adaptors test suite passes against it. A new
test for each of the four flat adaptors exercises
as an underlying container.
7. Acknowledgements
Thanks to Arthur O’Dwyer, whose std-proposals post outlined the implementation approach this paper builds on.