| Document number |
P3739R3 |
| Date |
2025-10-05 |
| Reply-to |
Jarrad J. Waterloo <descender76 at gmail dot com>
|
| Audience |
Library Working Group (LWG) |
Standard Library Hardening - using std::optional<T&>
Table of contents
Changelog
R3
- added more context to historical discussion
- added
Frequently Asked Questions section
R2
- added
Inspecting exception_ptr
- added
define_static_{string,object,array}
- added more
const
- added more formatting
Abstract
Utilize std::optional<T&> and T& in the C++26 standard instead of T* order to harden it against null pointer dereference errors for similar memory safety reasons as Standard Library Hardening and Minor additions to C++26 standard library hardening .
This was discussed during the March 2024 WG21 meeting in Tokyo, Japan at the Library Working Group during the standardization of inplace_vector but could not be adopted since std::optional<T&> was not sufficiently along in the C++26 standardization process. Now that std::optional<T&> was adopted during the June 2025 Sophia meeting, it would be ideal that inplace_vector and other superfluous instances of T* underwent minor revisions to better align it with the rest of C++26 and consequently provide a more robust interface.
The first request is that 3 of inplace_vector's modifiers be revised to return a const std::optional<T&> instead of a pointer.
|
template< class… Args >
constexpr pointerconst optional<T&> try_emplace_back( Args&&… args );
constexpr pointerconst optional<T&> try_push_back( const T& value );
constexpr pointerconst optional<T&> try_push_back( T&& value );
|
The second request is that Inspecting exception_ptr be similarly revised.
|
template <class E>
constexpr const E*const optional<const T&> exception_ptr_cast(const exception_ptr& p) noexcept
|
The third request is that define_static_{string,object,array} be similarly revised.
|
template <class T>
consteval auto define_static_object(T&& v) -> remove_reference_t<T> const*const T&;
|
Motivation
Returning a pointer from these three methods are less than an ideal interface. The primary problem is that it leads to null pointer dereference errors. It is essentially a 100% unsafe interface. Now contrast that with the different iterations of optional.
While optional<T> can't be used as an alternative to pointer, in C++17 when it was standardized, optional had an equal number of unsafe and safe modifiers. The unsafe ones were operator-> and operator* and the safe modifiers were value and value_or. In other words, optional was born with 50% null pointer dereference safety.
In C++23, the and_then, transform and or_else modifiers were added. This brought the total of modifiers to 5 of 7. The result is that 71% of all modifiers are safe from null pointer dereference errors.
However, only in C++26 with the advent of std::optional<T&> does optional becomes a viable substitute for a nullable pointer. Also in this release is std::optional range support which turns optional is a range of 0 or 1. This opens the door for 125 range algorithms and 34 range adapters to become null pointer deference safe modifiers for optional. Those range algorithms and adapters are growing at a much faster rate than new modifiers being added to the optional class itself. This brings the modifer safety rating up to 98%. The original 2 unsafe modifiers and hence the remaining 2% are being addressed by Standard Library Hardening .
Besides the safety benefit, the monadic and range usage are more ergonometric than the legacy pointer based interface.
There are equivalent benefits for exception_ptr_cast and define_static_object .
Wording
23.3.16 Class template inplace_vector [inplace.vector]
23.3.16.1 Overview [inplace.vector.overview]
…
(5.3.3) — If
…
…
template< class… Args >
constexpr pointerconst optional<T&> try_emplace_back( Args&&… args );
constexpr pointerconst optional<T&> try_push_back( const T& value );
constexpr pointerconst optional<T&> try_push_back( T&& value );
…
…
23.3.16.5 Modifiers [inplace.vector.modifiers]
…
template<class… Args>
constexpr pointerconst optional<T&> try_emplace_back(Args&&… args);
constexpr pointerconst optional<T&> try_push_back(const T& x);
constexpr pointerconst optional<T&> try_push_back(T&& x);
8 Let vals denote a pack:
(8.1) — std::forward<Args>(args)... for the first overload,
(8.2) — x for the second overload,
(8.3) — std::move(x) for the third overload.
9 Preconditions: value_type is Cpp17EmplaceConstructible into inplace_vector from vals....
10 Effects: If size() < capacity() is true, appends an object of type T direct-non-list-initialized with vals.... Otherwise, there are no effects.
11 Returns: nullptr if size() == capacity() is true, otherwise addressof(back()).
12 Throws: Nothing unless an exception is thrown by the initialization of the inserted element.
13 Complexity: Constant.
14 Remarks: If an exception is thrown, there are no effects on *this.
Impact on the standard
Other than inplace_vector, Inspecting exception_ptr and define_static_{string,object,array} which are all three completely new to the standard, there are no other changes to the library standard and since C++26 has not be released, this tweak is not expected to cause any problems.
The proposed changes are relative to the current working draft N5008 .
Frequently Asked Questions
Wasn't the return type of try_push_back and try_emplace_back settled in R6 and R7?
According to NB-Commenting is Not a Vehicle for Redesigning inplace_vector, R6 discussed optional<T>, not optional<T&>. According to NB-Commenting is Not a Vehicle for Redesigning inplace_vector, R7 discussed bool and pointer but not optional<T&>. Using optional<T&> was discussed during the March 2024 WG21 meeting in Tokyo, Japan at the Library Working Group during the consideration of R11 . It was known that optional<&> was coming at some point and when it does that it would need to be considered revising the return parameters of try_push_back and try_emplace_back. optional<T&> did come and it does need to be considered on both safety and on internal C++26 consistency grounds.
Why const <optional&>?
Either const <optional&> or <optional&> will mitigate the spatial memory errors such as null pointer dereference, pointer arithmetic and pointer indexing associated with superfluous pointer usage. So removing const doesn't negate the safety advantages of providing <optional&>. So why const? We are talking about references which essentially are const pointers. Consequently, const <optional&> should be the default usage when one is expecting reference semantics. Why do many programmers prefer reference semantics over pointer semantics! Besides the spatial safety benefits already mentioned, reference semantics also helps temporal safety because once a reference aliases an object with global, stack or heap lifetime it always does so, which makes it easier for programmers to reason about lifetimes at compile time. Due to the mutability of non const pointers, reasoning about lifetimes are runtime in nature. Further, no functionality is lost by having reference semantics because const optional<&> is implicitly convertible to optional<&> just as const pointers are implicitly convertible to non const pointers.
Doesn't optional<T&> have issues?
Sure and programmers are raising safety issues with inplace_vector<T>. National Body Comments is about fixing issues.
LWG4299 and LWG4300 are tiny clarifications.
LWG4299 , LWG4300 and LWG4300 does not impact the public interface of optional<&> nor does it have safety impacts. Further these issues could be fixed in C++29 without it being a breaking change. Waiting till C++29 to correct the return type of try_emplace_back and try_push_back would be a breaking change, which is why it needs to be addressed now.
Isn't optional<T&> NOT trivially copyable?
That is true. While that should be fixed now, it is not a breaking change if it was fixed early in C++29. There is even a paper that requests this, Make optional<T&> trivially copyable . What would be even better if the second design decision was chosen; "Additionally constrain its layout so it actually becomes a wrapper for a T*". This could be done simply by removing "// exposition only" and by adding a constructor from a pointer to the optional<&> proposal. This would allow programmers to opt into optional safety when working with legacy API(s) that are still superfluously using pointers, thus improving the safety benefits of C++26 further via minimal corrections.
Shouldn't try_*_back mostly only be used in boolean context?
According to NB-Commenting is Not a Vehicle for Redesigning inplace_vector , yes! optional<&> was specifically designed to be used in a boolean context as seen by the multiple examples in the comparison table section at the start of that proposal.
Shouldn’t we not be adding any more overhead to an object that is likely to be used briefly as a temporary and thrown away?
YES, that is why optional<&> should eventually be made trivially copyable and more important "additionally constrain its layout so it actually becomes a wrapper for a T*".
Why const T& instead of const T*?
If the referent is non null, as it should be when dealing with objects of both static storage duration and automatic storage duration, then a reference should be used instead of a pointer. Beside that, const T& is ready to be consumed as input arguments.
|
C++ Core Guidelines
F.16: For "in" parameters, pass cheaply-copied types by value and others by reference to const
|
We can have functions like define_static_object dereference the pointer a constant number of times, once per return, or we can have all programmers dereference the return every time the function is used as an input argument. In other words, a very large number that grows with time. The default choice seems clear.
References
Jarrad J. Waterloo <descender76 at gmail dot com>
Standard Library Hardening - using std::optional<T&>
Table of contents
Changelog
R3
Frequently Asked QuestionssectionR2
Inspecting exception_ptr[1] [2]define_static_{string,object,array}[3]constAbstract
Utilize
std::optional<T&>[4] andT&in theC++26standard instead ofT*order to harden it against null pointer dereference errors for similar memory safety reasons asStandard Library Hardening[5] andMinor additions to C++26 standard library hardening[6].This was discussed during the March 2024 WG21 meeting in Tokyo, Japan at the Library Working Group during the standardization of
inplace_vector[7] but could not be adopted sincestd::optional<T&>[4:1] was not sufficiently along in theC++26standardization process. Now thatstd::optional<T&>[4:2] was adopted during the June 2025 Sophia meeting, it would be ideal thatinplace_vector[8] and other superfluous instances ofT*underwent minor revisions to better align it with the rest ofC++26and consequently provide a more robust interface.The first request is that 3 of
inplace_vector's[8:1] modifiers be revised to return aconst std::optional<T&>[4:3] instead of a pointer.template< class… Args > constexpr
pointerconst optional<T&> try_emplace_back( Args&&… args );constexpr
pointerconst optional<T&> try_push_back( const T& value );constexpr
pointerconst optional<T&> try_push_back( T&& value );The second request is that
Inspecting exception_ptr[1:1] [2:1] be similarly revised.template <class E> constexpr
const E*const optional<const T&> exception_ptr_cast(const exception_ptr& p) noexceptThe third request is that
define_static_{string,object,array}[3:1] be similarly revised.template <class T>
consteval auto define_static_object(T&& v) ->
remove_reference_t<T> const*const T&;Motivation
Returning a pointer from these three methods are less than an ideal interface. The primary problem is that it leads to null pointer dereference errors. It is essentially a 100% unsafe interface. Now contrast that with the different iterations of
optional.While
optional<T>can't be used as an alternative to pointer, inC++17when it was standardized,optionalhad an equal number of unsafe and safe modifiers. The unsafe ones wereoperator->andoperator*and the safe modifiers werevalueandvalue_or. In other words,optionalwas born with 50% null pointer dereference safety.In
C++23, theand_then,transformandor_elsemodifiers were added. This brought the total of modifiers to 5 of 7. The result is that 71% of all modifiers are safe from null pointer dereference errors.However, only in
C++26with the advent ofstd::optional<T&>[4:4] doesoptionalbecomes a viable substitute for a nullable pointer. Also in this release isstd::optional range support[9] which turnsoptionalis a range of 0 or 1. This opens the door for 125 range algorithms and 34 range adapters to become null pointer deference safe modifiers foroptional. Those range algorithms and adapters are growing at a much faster rate than new modifiers being added to theoptionalclass itself. This brings the modifer safety rating up to 98%. The original 2 unsafe modifiers and hence the remaining 2% are being addressed byStandard Library Hardening[5:1].Besides the safety benefit, the monadic and range usage are more ergonometric than the legacy pointer based interface.
There are equivalent benefits for
exception_ptr_cast[1:2] [2:2] anddefine_static_object[3:2].Wording
23.3.16 Class template inplace_vector [inplace.vector]
23.3.16.1 Overview [inplace.vector.overview]
…
(5.3.3) — If
…
…
template< class… Args >
constexpr
pointerconst optional<T&> try_emplace_back( Args&&… args );constexpr
pointerconst optional<T&> try_push_back( const T& value );constexpr
pointerconst optional<T&> try_push_back( T&& value );…
…
23.3.16.5 Modifiers [inplace.vector.modifiers]
…
template<class… Args>
constexpr
pointerconst optional<T&> try_emplace_back(Args&&… args);constexpr
pointerconst optional<T&> try_push_back(const T& x);constexpr
pointerconst optional<T&> try_push_back(T&& x);8 Let
valsdenote a pack:(8.1) —
std::forward<Args>(args)...for the first overload,(8.2) —
xfor the second overload,(8.3) —
std::move(x)for the third overload.9 Preconditions:
value_typeisCpp17EmplaceConstructibleintoinplace_vectorfromvals....10 Effects: If
size() < capacity()istrue, appends an object of typeTdirect-non-list-initialized withvals....Otherwise, there are no effects.11 Returns:
nullptrifsize() == capacity()istrue, otherwiseaddressof(back()).12 Throws: Nothing unless an exception is thrown by the initialization of the inserted element.
13 Complexity: Constant.
14 Remarks: If an exception is thrown, there are no effects on
*this.Impact on the standard
Other than
inplace_vector[8:2],Inspecting exception_ptr[1:3] [2:3] anddefine_static_{string,object,array}[3:3] which are all three completely new to the standard, there are no other changes to the library standard and sinceC++26has not be released, this tweak is not expected to cause any problems.The proposed changes are relative to the current working draft
N5008[10].Frequently Asked Questions
Wasn't the return type of
try_push_backandtry_emplace_backsettled in R6 and R7?According to
NB-Commenting is Not a Vehicle for Redesigning inplace_vector, [11]R6[12] discussedoptional<T>, notoptional<T&>. According toNB-Commenting is Not a Vehicle for Redesigning inplace_vector, [11:1]R7[13] discussed bool and pointer but notoptional<T&>. Usingoptional<T&>was discussed during the March 2024 WG21 meeting in Tokyo, Japan at the Library Working Group during the consideration ofR11[7:1]. It was known that optional<&> was coming at some point and when it does that it would need to be considered revising the return parameters oftry_push_backandtry_emplace_back.optional<T&>did come and it does need to be considered on both safety and on internalC++26consistency grounds.Why
const <optional&>?Either
const <optional&>or<optional&>will mitigate the spatial memory errors such as null pointer dereference, pointer arithmetic and pointer indexing associated with superfluous pointer usage. So removingconstdoesn't negate the safety advantages of providing<optional&>. So whyconst? We are talking about references which essentially areconstpointers. Consequently,const <optional&>should be the default usage when one is expecting reference semantics. Why do many programmers prefer reference semantics over pointer semantics! Besides the spatial safety benefits already mentioned, reference semantics also helps temporal safety because once a reference aliases an object with global, stack or heap lifetime it always does so, which makes it easier for programmers to reason about lifetimes at compile time. Due to the mutability of nonconstpointers, reasoning about lifetimes are runtime in nature. Further, no functionality is lost by having reference semantics becauseconstoptional<&> is implicitly convertible to optional<&> just asconstpointers are implicitly convertible to nonconstpointers.Doesn't
optional<T&>have issues?Sure and programmers are raising safety issues with inplace_vector<T>. National Body Comments is about fixing issues.
LWG4299[14] andLWG4300[15] are tiny clarifications.LWG4299[14:1],LWG4300[15:1] andLWG4300[15:2] does not impact the public interface ofoptional<&>nor does it have safety impacts. Further these issues could be fixed inC++29without it being a breaking change. Waiting tillC++29to correct the return type oftry_emplace_backandtry_push_backwould be a breaking change, which is why it needs to be addressed now.Isn't optional<T&> NOT trivially copyable?
That is true. While that should be fixed now, it is not a breaking change if it was fixed early in
C++29. There is even a paper that requests this,Make optional<T&> trivially copyable[16]. What would be even better if the second design decision was chosen; "Additionally constrain its layout so it actually becomes a wrapper for a T*". [16:1] This could be done simply by removing "// exposition only" and by adding a constructor from a pointer to theoptional<&>[4:5] proposal. This would allow programmers to opt into optional safety when working with legacy API(s) that are still superfluously using pointers, thus improving the safety benefits ofC++26further via minimal corrections.Shouldn't
try_*_backmostly only be used in boolean context?According to
NB-Commenting is Not a Vehicle for Redesigning inplace_vector[11:2], yes!optional<&>[4:6] was specifically designed to be used in a boolean context as seen by the multiple examples in the comparison table section at the start of that proposal.Shouldn’t we not be adding any more overhead to an object that is likely to be used briefly as a temporary and thrown away?
YES, that is why
optional<&>[4:7] should eventually be madetrivially copyable[16:2] and more important "additionally constrain its layout so it actually becomes a wrapper for a T*". [16:3]Why
const T&instead ofconst T*?If the referent is non null, as it should be when dealing with objects of both static storage duration and automatic storage duration, then a reference should be used instead of a pointer. Beside that,
const T&is ready to be consumed as input arguments.C++ Core Guidelines F.16: For "in" parameters, pass cheaply-copied types by value and others by reference to const [17]
We can have functions like
define_static_objectdereference the pointer a constant number of times, once per return, or we can have all programmers dereference the return every time the function is used as an input argument. In other words, a very large number that grows with time. The default choice seems clear.References
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2927r3.html ↩︎ ↩︎ ↩︎ ↩︎
https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3748R0.html ↩︎ ↩︎ ↩︎ ↩︎
https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/p3491r3.html ↩︎ ↩︎ ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2988r12.pdf ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3471r4.html ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3697r0.html ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0843r11.html ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0843r14.html ↩︎ ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3168r2.html ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5008.pdf ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3830r0.pdf ↩︎ ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0843r6.html ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0843r7.html ↩︎
https://cplusplus.github.io/LWG/issue4299 ↩︎ ↩︎
https://cplusplus.github.io/LWG/issue4300 ↩︎ ↩︎ ↩︎
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3836r0.html ↩︎ ↩︎ ↩︎ ↩︎
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-in ↩︎