(For WG21 members, LWG’s notes on P0339R6 are here.)
This paper condenses arguments from [Contra].
1. P0339’s example code
Okay, so, what does [P0339R6] propose to allow us to write? From the paper: Here is the "before" code, and here is the "after" code. P0339 shows a dramatic difference between the "before" version of allocating a linked-list node:
using node_alloc = typename alloc_traits :: template rebind_alloc < node > ; node_alloc m_alloc = ...; using alloc_node_traits = typename alloc_traits :: template rebind_traits < node > ; node * n = alloc_node_traits :: allocate ( m_alloc , 1 ); alloc_node_traits :: construct ( m_alloc , & n -> m_value , v ); n -> m_next = m_head ;
and the "after" version:
using allocator_type = std :: pmr :: polymorphic_allocator <> ; allocator_type m_alloc = ...; node * n = m_alloc . allocate_object < node > (); m_alloc . construct ( & n -> m_value , v ); n -> m_next = m_head ;
However, notice that the "before" version was an STL-style class template taking an allocator parameter,
whereas the "after" version is a concrete class type restricted to dealing with only a single allocator type —.
So the main difference is that removes the allocator template parameter.
Removing template parameters does indeed dramatically simplify code, but you don’t need to modify to get
that benefit! Let’s compare how the non-parameterized would look in pure vanilla C++17: here.
using node_alloc = std :: pmr :: polymorphic_allocator < node > ; node_alloc m_alloc = ...; node * n = m_alloc . allocate ( 1 ); m_alloc . construct ( & n -> m_value , v ); n -> m_next = m_head ;
The vanilla C++17 version of is actually simpler than P0339’s proposed C++2a version!
Notice that because is not aware of allocator types other than its hard-coded one,
we don’t have to go through to get at . We know that provides and methods that fit our needs exactly.
2. The new_object < T > API
P0339 adds the following member function to all specializations of :
template < class T , class ... CtorArgs > T * new_object ( CtorArgs && ... ctor_args );
It is unfortunate that this new API is being proposed only for , and not for
other concrete allocator types such as at the same time. Programmers of properly C++11-allocator-aware
containers will not be able to take advantage of the API at all.
P0339’s motivating example can’t use the API, because is not allocator-aware. deliberately lacks the constructors that would be needed to pipe the allocator from down into . That’s why P0339’s example code explicitly calls on the object, instead of letting it be recursively constructed by 's constructor or by .
Earlier revisions of P0339 proposed to add the API only to .
Therefore it needed the caller to supply template parameter in every case:
std :: polymorphic_allocator < node2 > m_alloc = ...; m_head = m_alloc . new_object < node2 > ( v , m_head );
But since is already associated with a fixed , it would be more concise to write simply
std :: polymorphic_allocator < node2 > m_alloc = ...; m_head = m_alloc . new_object ( v , m_head );
This is the motivation for one of our proposed changes to the P0339 and interfaces:
that they should default their template parameter to the allocator’s value type .
3. The interaction with CTAD and common typos
Earlier revisions of P0339 proposed to add the API only to .
Threfore it needed a "convenient" alias for the specialization (as opposed to
other specializations, which would not have had the new API).
The merged version of P0339 attaches the new API to all specializations of , yet still
proposes to slightly shorten the name of by giving a defaulted template parameter.
Before P0339, the following code snippet would be a syntax error. (You forgot the !)
template < class T > void * allocate_space_for_n_Ts_with_the_default_resource ( int n ) { std :: pmr :: polymorphic_allocator alloc ; return alloc . allocate ( n ); }
After P0339, thanks to the defaulted template parameter, and partly thanks to CTAD,
that code snippet compiles quietly and allocates bytes of memory, rather than the intended bytes.
Even in a world without CTAD, accidentally writing instead of is not unthinkable. I have personally observed Reddit commenters writing and instead of and .
A significant number of C++ developers are already confused about which of and are templates, which are type-erased, and which are classically polymorphic.
Allowing these developers to write as if it were a concrete
class type does them a grave disservice.
Consider the difference between
std :: pmr :: polymorphic_allocator a1 = std :: pmr :: new_delete_resource (); std :: pmr :: polymorphic_allocator a2 = std :: pmr :: vector < int > (). get_allocator ();
Above, is but is .
4. Summary of objections to P0339
-
P0339’s convenience functionality is not as optimally designed as it could be.
-
P0339’s own example shows the inferiority of P0339’s
functionality, compared to what’s already in C++17.allocate_object < T > -
P0339 pointlessly privileges
over all otherstd :: byte .T -
P0339 added a default template parameter that interacts badly with CTAD, and serves merely to hide bugs.
5. What might a convenience interface look like?
To get the benefits of P0339’s "convenience interface" without the downsides, one might introduce a non-templated which can be used without messing with the allocator model at all. Using the "handle" model
instead of the "allocator" model, we could write a that looks like this:
handle m_res = ...; node * n = m_res . allocate < node > ( 1 ); m_res . construct ( & n -> m_value , v ); n -> m_next = m_head ;
Here, is a data member of type . It doesn’t pretend to be an Allocator, because it doesn’t need to.
All accesses to its underlying resource go through the new convenience API, never through the C++11 allocator API.
The one place where the old allocator API is needed, ,
simply returns .
This idea can be implemented in vanilla C++17, entirely in user code. No changes to are needed.
For this reason, my first preference is that P0339’s changes to should be completely reverted.
6. If P0339 is not reverted, at least remove the defaulted parameter
Modify [mem.poly.allocator.class] as follows:
namespace std :: pmr { template < class Tp = byte > class polymorphic_allocator { memory_resource * memory_rsrc ; // exposition only
This stops from being usable without angle brackets.
Further proposals might then be entertained to introduce a "convenience alias" for ,
or , or , or any other "representative"
specialization of . However, since all specializations of have access to
P0339’s new API, and all specializations of are implicitly interconvertible, there is no reason
to privilege any one specialization above the others. Furthermore, is a particularly cumbersome
spelling of the "convenience" alias; it could be a proper (non-template) alias such as instead.
7. If P0339 is not reverted, default the first template parameters of allocate_object and new_object
Modify [mem.poly.allocator.class] as follows:
void * allocate_bytes ( size_t nbytes , size_t alignment = alignof ( max_align_t )); void deallocate_bytes ( void * p , size_t nbytes , size_t alignment = alignof ( max_align_t )); template < class T = Tp > T * allocate_object ( size_t n = 1 ); template < class T > void deallocate_object ( T * p , size_t n = 1 ); template < class T = Tp , class ... CtorArgs > T * new_object ( CtorArgs && ... ctor_args ); template < class T > void delete_object ( T * p ); template < class T , class ... Args > void construct ( T * p , Args && ... args ); template < class T > void destroy ( T * p );
This allows to be used without angle brackets.
Because the value type of the allocator depends on its template argument, this change should
not be taken unless the default template argument is removed from . If this
change were taken without that one, then the following line of code would accidentally
construct a instead of the desired :
auto * p = std :: polymorphic_allocator {}. construct ( 42 );
Appendix A: Proposed straw polls
| SF | F | N | A | SA | |
|---|---|---|---|---|---|
| Revert P0339; send these concerns back to the author of P0339. | _ | _ | _ | _ | _ |
Apply the proposed change to remove the default template argument from .
| _ | _ | _ | _ | _ |
Apply the proposed changes to remove the default template argument from and to
add a default template argument to and .
| _ | _ | _ | _ | _ |