Document number |
P3739R2 |
Date |
2025-08-10 |
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
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 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
.
References
Jarrad J. Waterloo <descender76 at gmail dot com>
Standard Library Hardening - using std::optional<T&>
Table of contents
Changelog
R2
Inspecting exception_ptr
[1] [2]define_static_{string,object,array}
[3]const
Abstract
Utilize
std::optional<T&>
[4] andT&
in theC++26
standard 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 standardization of
inplace_vector
[7] but could not be adopted sincestd::optional<T&>
[4:1] was not sufficiently along in theC++26
standardization process. Now thatstd::optional<T&>
[4:2] was adopted during the June 2025 Sophia meeting, it would be ideal thatinplace_vector
[7:1] and other superfluous instances ofT*
underwent minor revisions to better align it with the rest ofC++26
and consequently provide a more robust interface.The first request is that 3 of
inplace_vector's
[7:2] 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++17
when it was standardized,optional
had an equal number of unsafe and safe modifiers. The unsafe ones wereoperator->
andoperator*
and the safe modifiers werevalue
andvalue_or
. In other words,optional
was born with 50% null pointer dereference safety.In
C++23
, theand_then
,transform
andor_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 ofstd::optional<T&>
[4:4] doesoptional
becomes a viable substitute for a nullable pointer. Also in this release isstd::optional range support
[8] which turnsoptional
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 foroptional
. Those range algorithms and adapters are growing at a much faster rate than new modifiers being added to theoptional
class 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
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
isCpp17EmplaceConstructible
intoinplace_vector
fromvals....
10 Effects: If
size() < capacity()
istrue
, appends an object of typeT
direct-non-list-initialized withvals....
Otherwise, there are no effects.11 Returns:
nullptr
ifsize() == 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
[7:3],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++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
[9].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/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 ↩︎