(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 .
| _ | _ | _ | _ | _ |