Document number: P2047R5
Nina Dinka Ranns
An allocator-aware optional type
Library types that can potentially hold allocator-aware (AA)
objects should, themselves, be allocator-aware. A PMR container, for example,
depends on AA types following a known AA protocol so that it (the container)
can uniformly manage its memory. Even types that don't manage their own
memory, such as tuple, follow the AA rules when they hold one more
more AA elements. (A special case is pair, which is not actually AA
but effectively follows the rules through special handling
in uses-allocator construction.)
The current definition of std::optional does not follow the rules
for an AA type, even when holding an AA value. This limitation
makes std::optional unsuitable for storage in an AA container when
memory allocation customization is needed.
In this paper, we propose a new, allocator-aware
std::basic_optional usable as a container element type of allocator aware containers, and in any
other context where allocator propagation is expected. This new type would
not replace the current std::optional as the desired behaviour is
not compatible with how std::optional handles allocator aware types
at the moment. We do propose having a special treatement for std::basic_optional and
std::optional that allows for certain implicit conversions between
the two types.
We also propose an std::pmr::optional type which is a specialisation
of std::basic_optional for std::pmr::polymorphic_allocator<>.
This is a complete proposal with formal wording.
change history :
- replaced alloc_optional with additional overloads of make_optional that
take an allocator_arg_t : it was observed that alloc_optional doesn't actually
do any allocation, and that the name is not appropriate
- fixed the order of arguments in the Returns clause of what used to be alloc_optional
- adjusted for LWG 2833
- added discussion on deduction guides
- removed mentions of using a type alias since the type alias approach has proven to be problematic
- modified the specification of std::pmr::optional to cover both AA and non AA types
- added CTAD specification
- modified the specification of std::pmr::optional to be expressed in terms of
std::pmr::polymorphic_allocator<>, as opposed to std::pmr::memory_resource
- moved std::pmr::optional to optional header as discussed in the reflector discussion
- added request for discussion on making std::pmr::optional a more allocator generic type.
- added free swap function to be in line with P0178
- replaced instances of M_alloc with alloc
- clarified pmr::optional constructor descriptions to call out uses-allocator construction
- expanded basic_optional discussion
- switched to std::pmr::optional being a specialisation of std::basic_optional
- modified the wording to cover std::basic_optional
- various clean up of proposed wording
- setting the default allocator to be remove_cv_t version of value_type
- removed make facilities
Motivation and Proposal Summary
C++ has been moving towards a consistent PMR allocator model whereby
allocator-aware (AA) types adhere to a few rules:
- Once constructed, an object's allocator never changes.
- The object's constructors all follow one of two argument protocols so
that they work with uses-allocator construction and thus can be
inserted as container elements using the container's allocator.
- An object that can contain an AA element of user-specified type should
itself be AA and use its allocator to initialize its AA subobjects.
The current std::optional does not follow the above rules. When
disengaged, it forgets its allocator, violating the first rule; its
constructors don't follow the AA protocols, so containers cannot pass their
allocators to optional elements; and it does not hold on an
allocator by which it can initialize it's contained object. As a
result, std::optional is not a good fit for situations where
allocators are used.
A std::pmr::vector<optional<std::pmr::string>>, for example,
cannot ensure that all of strings within it use the same allocator,
violating a key invariant of PMR containers. If one of the elements of the
vector became disengaged and re-engaged, for example, the allocator for that
one element could change.
The basic_optional class template proposed here properly adheres to
the rules of an AA type when instantiated with an AA value type. It holds a
copy of the allocator used to construct it, even when disengaged, and uses
that allocator to construct its element when re-engaged.
This allows containers of basic_optional objects to correctly manage
Fruthermore, to support allocators with different allocator traits to the
PMR allocator model, basic_optional also uses
allocator propagation traits to determine the behaviour of optional object
with regards to allocator propagation.
basic_optional supports non-scoped propagating allocators
There are two ways of viewing basic_optional<T> from allocator propagation perspective :
#1 basic_optional<T> is like a std::tuple, i.e. it only accepts
the allocator at construction so it can forward it to the value_type object. One can
use a non-scoped propagation allocator, and when using a scoped propagation allocator
basic_optional<T> will not "consume" an allocator level. An optional
object is in a way like a tuple object as it does not use the allocator itself,
it only passes it into the value_type object.
#2 basic_optional<T> is like a std::vector, i.e. it is a container
of one or zero elements, and one should use a scoped propagating allocator if one wants
the value_type object to use the allocator. In this approach basic_optional<T> will "consume" an allocator level.
Using non-scoped propagating allocators
makes little sense in this scenario.
The proposal implements #1 as basic_optional itself does not allocate any
memory so it makes little sense for it to consume an alloctor level.
The basic design of an AA optional is straight-forward: Add an allocator to
all of its constructors and use that allocator to construct the value object
each type the optional is engaged. However, when holding a non-AA type,
there is no reason to pay the overhead of storing an allocator.
We believe there is no need to support the AA constructor interface for non-AA
types. Generic programming should use uses-allocator construction and
std::make_obj_using_allocator to invoke the correct constructors.
Conversions between std::optional<T> and std::basic_optional<T,Alloc>
optional<T> y = x; // #1
basic_optional<T,Alloc> z = y; // #2
optional<T> foo_constref(const optional<T>& );
foo_constref(x); // #3
foo_ref(x) // #4
In the example above, we do not believe #1,#2, and #3 are ever problematic,
but may be useful for code which currently uses optional. However,
#4, if allowed, would potentially modify the basic_optional in a way
that does not preserve the allocator requirements. Note that #4 is only
problematic if uses_allocator<basic_optional<T,Alloc>> == true.
Allowing #4 for cases where
uses_allocator<basic_optional<T,Alloc>> == false
would make re-using codebases which traffic in
non allocator aware optional possible when allocator does not matter.
However, it adds to the complexity of design.
It is also questionable whether there is a need for this conversion.
One can have two reasons to use basic_optional with non allocator
- writing generic code which serves both allocator and non allocator
aware types. Allowing interoperability with optional for only certain
cases seems unhelpful in such a case.
- using basic_optional for all optional types for simplicity
purposes. Allowing interoperability with optional would be useful in
We propose to not implement conversion #4 until the time it is needed.
However, library implementors might want to consider this as a possible
extension because it might inform the implementation design they go for.
Allocator used in value_or
It is not all that obvious which allocator should be used for the
object returned by value_or. Should the decision be left to the type
or should it be mandated by the optional ? That is, should the object
be constructed by plain copy/move construction or with uses-allocator
construction? The proposal leaves the decision to the value_type. If the user
cares about the allocator of the returned object, it should be explicitly
provided by the user. We may consider providing allocator extended version
of value_or in the future, if this use case proves to be common enough.
Deduction guides for mixed optionals unpack
std::optional x1 = value_type();
std::basic_optional copy1 = x1; // #1
std::optional x2 = value_type_aa();
std::basic_optional copy2 = x2; // #2
std::basic_optional x3 = value_type();
std::optional copy3 = x3; // #3
std::basic_optional x4 = value_type_aa();
std::optional copy4 = x4; // #4
What should the types of x1, x2, x3, and x4
in the above example be ?
The current proposal favours unpacking, and the types are:
#1 std::basic_optional<value_type, allocator<value_type>>,
#2 std::basic_optional<value_type_aa, allocator<value_type_aa>>,
#3 std::optional<value_type>, and
This seems to fit with the idea that
std::basic_optional and std::optional are types that are close enough that they deserve to be
treated equally wherever they can. However, it does mean that they are somehow blessed
by the standard to work together in a way a user defined type which inherits from std::optional
Non allocator aware basic_optional is not an alias for optional
An early draft of this proposal suggested using a type alias where a non-AA basic_optional
aliases std::optional, and AA basic_optional aliases a new unnamed type.
However, this causes usability issues. Type deduction in cases like :
template <typename T>
did not work. The above was equivalent to
template <typename T, typename Alloc>
void foo(std::conditional<std::uses_allocator <T, Alloc<>>::value,
and using the nested type of the std::conditional as the function template parameter made for an undeduced context.
Free swap has a wide contract
The free swap function has been made to have a wide contract and work with different allocators, as discussed in
Make facilities are not provided
Make facilities have been deemed unnecessary with the availability of CTAD. The current
version of the paper doesn't propose std::make_basic_optional nor std::pmr::make_optional.
Previous version suggested those be included, but with implementation experience, we found
a complexity in allowing the allocator type to be both specifiable and defaulted in non
allocator extended version of the make facility. We deem such complexity unnecessary
with the advent of the CTAD feature.
Allocator aware types are assumed to be move enabled
basic_optional uses explicit allocator construction except in cases where
invoking a direct value_type operation allows for move operations to remain
noexcept. This is the case in move constructor for all allocators, and in move assignment for allocators
that have propagate_on_container_move_assignment==true. If the value_type
is allocator aware, but does not support move semantics (i.e. moves deteriorate to copies),
it is possible that the allocator of the value_type object will get out of
sync with the allocator of the basic_optional. We do not expect such types
Feedback items for LEWG:
Should assignments use is_constructible trait ?
It's not construct from U we need, it's construct from U using the allocator. Do we need a new trait ?
Do we need an “allocator_aware” concept?
We might consider an “allocator aware” (AA) concept that requires
method that returns something convertible to std::pmr::polymorphic_allocator<>.
If we used this concept instead of the std::uses_allocator trait, the allocator would require
zero extra space in most (but not all) cases. (The exception would be a type that stores
its allocator outside of its footprint and whose footprint is smaller than
Add new paragraphs after 20.6.1/p1 [optional.general]
Modify 184.108.40.206 Assignment [optional.assign]
Insert new sections after 20.6.3 Class template optional [optional.optional]