Document #: | P3471R2 |
Date: | 2024-12-14 |
Project: | Programming Language C++ |
Audience: |
Library Evolution, SG23 |
Reply-to: |
Konstantin Varlamov <varconst@apple.com> Louis Dionne <ldionne@apple.com> |
This paper proposes introducing standard library hardening into the C++ Standard. Hardening allows turning some instances of undefined behavior in the standard library into a contract violation. This proposal is based on our experience implementing hardening in libc++ and deploying it widely.
There has been significantly increased attention to safety and security in C++ over the last few years, as exemplified by the well-known White House report and numerous recent security-related proposals.
While it is important to explore ways to make new code safer, we believe that the highest priority to deliver immediate real-world value should be to make existing code safer with minimal or no effort on behalf of users. Indeed, the amount of existing security-critical C++ code is so large that rewriting it or modifying it is both economically unviable and dangerous given the risk of introducing new issues.
There have been a few proposals accepted recently that eliminate some cases of undefined behavior in the core language. The standard library also contains many instances of undefined behavior, some of which is a direct source of security vulnerabilities; addressing those is often trivial, can be done with low overhead and almost no work on behalf of users.
In fact, at the moment all three major library implementations have some notion of a hardened or debug mode. This clearly shows interest, both from users and from implementers, in having a safer mode for the standard library. However, we believe these efforts would be vastly more useful if they were standardized and provided portable, cross-platform guarantees to users; as it stands, implementations differ in levels of coverage, performance guarantees and ways to enable the safer mode.
Finally, leaving security of the library to be a pure vendor extension fails to position ISO C++ as providing a credible solution for code bases with formal security requirements. We believe that formally requiring the basic safety guarantees that most implementations already provide in one way or another could make a significant difference from the point of view of anyone writing or following safety and security coding standards and guidelines.
All three major implementations provide vendor-specific ways of enabling library assertions as proposed in this paper, today.
Overall, standard library hardening has been a huge success, in fact we never expected so much success. The reception has been overwhelmingly positive and while the quality of implementation will never be perfect, we are working hard to expand the scope of hardening in libc++, to improve its performance and the user experience.
At a high level, this proposal consists of two parts:
There are a few important aspects to the proposed design:
ignore
semantic. Since we specify
that violating a hardened precondition is a contract violation,
we are already past the point where
ignore
could bypass the check.vector::operator[]
only has implicit preconditions via iterator validity).To reiterate the last point, an important design principle is that hardening needs to be lightweight enough for production use by a wide variety of real-world programs. In our experience in libc++, a small set of checks that is widely used delivers far more value than a more extensive set of checks that is only enabled by select few users. Thankfully, many of the most valuable checks, such as checking for out-of-bounds access in standard containers, also happen to be relatively cheap.
To specify hardening in the Standard, this proposal introduces the notion of a hardened precondition. A hardened precondition is a precondition that results in a contract violation in a hardened implementation. Adding hardening to the library largely consists of turning some of the existing preconditions into hardened preconditions in the specification. For example:
(23.7.2.2.6)
Element access [span.elem]
constexpr reference operator[](size_type idx) const;
1
Hardened
Ppreconditions:
idx < size()
is true
.
In the initial proposal, we decide to focus on hardened preconditions that prevent out-of-bounds memory access, i.e., compromise the memory safety of the program. These are some of the most valuable for the user since they help prevent potential security vulnerabilities; many of them are also relatively cheap to implement. More hardened preconditions can potentially be added in the future, but the intent is for their number to be limited to keep hardening viable for production use. Specifically, the proposal is to add hardened preconditions to:
std::span
,
std::mdspan
,
std::string
,
std::string_view
and other similar classes that might attempt to access non-existent
elements
(e.g. back()
on an empty container or operator[]
with an invalid index).pop_back()
).optional
and
expected
that expect the object to
be non-empty.In our experience, hardening all of these operations is trivial to implement and provides significant security value.
Much like a freestanding implementation, the way to request a
hardened implementation is left for the implementation to
define. For example, similarly to -ffreestanding
, we
expect that most toolchains would provide a compiler flag like
-fhardened
,
but other alternatives like a -D_LIBCPP_HARDENING_MODE=<mode>
macro would also be conforming. If this proposal gets accepted, we
expect implementations to converge on a portable mechanism. Other
details like whether hardened implementations and non-hardened
implementations can be mixed in different translation units are
intentionally left unspecified, to avoid overconstraining the
implementations.
Contracts is a new (in-progress) language feature that allows expressing preconditions and much more, with a lot of flexibility on what happens when an assertion is not satisfied. In the latest revision of this paper, we decided to base hardened preconditions on contract violations for several reasons:
observe
semantics
can be extremely useful to allow deploying this at scale.It is useful to note that we don’t require implementations to actually implement these checks as contract assertions. Implementations are free to implement precondition checking however they see fit (e.g. a macro), however they are required to employ the contract violation handling mechanism when a precondition is not satisfied.
Note that if Contracts were to not be pursued anymore, this feature could easily be reworded in terms of a guaranteed termination in an implementation-defined way, or using Erroneous Behavior. We are not strongly attached to the exact mechanism used to implement this, but we find that Contracts is a nearly perfect fit.
The various Profiles proposals introduce a framework to specify sets of safety guarantees (such as a type safety profile or an invalidation profile). If profiles become a part of the Standard in the future, hardening can most likely be formulated as an additional profile; this would formalize how hardening is turned on and off.
However, we feel strongly that a hardening mode as specified in this paper standardizes existing practice and delivers value today without waiting for a larger and still experimental language feature.
While Erroneous Behavior is a way to clearly mark some code as
incorrect in the specification, it does not clearly specify
what should happen in case of EB. For example, a conforming
behavior for vector::operator[]
being called out-of-bounds under EB would be to return the last element
of the vector instead. While that is well-defined behavior, we feel that
it is not especially useful behavior and that is certainly not what our
users are looking for. In contrast, the Contracts facilities provide a
well-defined and flexible framework to handle this.
erase
member functionsMost containers have an erase
member function that takes an iterator into the container and erases the
element it points to. These functions have an implicit precondition that
comes from the fact that the iterator argument is defined to denote “a
valid dereferenceable constant iterator to [the container]”.
Unfortunately, this precondition can only be checked for some
containers and not others. For a contiguous container, the iterator can
be lowered to a pointer; the pointer can then be tested to check whether
it points within the bounds of the container’s underlying storage (using
std::less
and the related function object classes). There is, however, no feasible
way to do a similar check for a non-contiguous container (without
requiring an ABI break and significant overhead). In this paper, we
would prefer to focus on preconditions that can be checked in a clear
and straightforward way across all applicable classes; less obvious
cases would be better served by a separate dedicated paper.
Most preconditions of associative containers do not map clearly to spatial safety; moreover, associative containers have a significantly different interface and implementation compared to the containers covered by this paper. Since specifying preconditions that are relevant for memory safety in the associative containers is less straightforward than for sequence containers and since we don’t have sufficient implementation experience hardening those, we prefer to explore hardening for associative containers in a dedicated paper.
All algorithms (in fact, all library functions) that operate on ranges have an implicit precondition that the ranges are valid. Unfortunately, that precondition can only be checked partially and for contiguous iterators, where it is trivial to check that the end of the range is reachable from the beginning. In the general case, it is also impossible to check that both iterators point into the same container, which doesn’t appear to be covered by the “valid range” requirement but is required in practice for these algorithms to do something sensible. Moreover, since “valid range” is a blanket requirement effectively covering all library functions, we have limited implementation experience with hardening algorithms, our current approach having focused on containers and container-like classes so far.
Some algorithms taking one range as an input and another as an output have a precondition that the two ranges do not overlap. This precondition also cannot be checked in the general case (for example, the iterators might be input iterators, meaning they cannot be “rewinded” after being incremented which would be required to perform a check).
For all of these reasons, we decided to leave hardening of the algorithms to a dedicated paper.
mdspan
extents and layout classesClasses representing the extents and the layout mapping of an
mdspan
(extents
,
layout_left
,
layout_right
,
layout_stride
) contain several
preconditions that can be considered for hardening. However, these are
large, complicated classes with non-trivial interactions with the
mdspan
class itself; it would
require dedicated effort and research to make sure all relevant cases
are hardened and there is no redundancy leading to unnecessary overhead.
As such, we would prefer to keep the initial proposal focused on
preconditions that are trivial to check and reason about and defer
mdspan
to a separate paper.
valarray
Similarly to mdspan
,
valarray
is non-trivial to harden
properly. Its API is effectively spread across several helper classes
that interact in non-trivial ways. After surverying our implementation,
we believe that our implementation experience with
valarray
hardening is not sufficient
at this time to include more than basic hardening of operator[]
in this paper. We would prefer to leave a thorough exploration of
valarray
to a dedicated paper.
Before this proposal, several member functions of sequence containers
(specifically, front
,
back
and operator[]
)
do not have an explicit precondition. Rather, their preconditions are
formulated implicitly via a Returns or an Effects
element of the function description (for example, “Returns:
*a.begin()
”
implies that a.begin()
returns a dereferenceable iterator). It might seem that the most natural
way to express the precondition would be to assume the container is not
empty (as is the case for some other functions defined in the same
section, such as pop_back
):
a.front()
69 Result:
reference
;const_reference
for constanta
.70 Hardened preconditions:
!a.empty()
.71 Returns:
*a.begin()
However, this formulation would be subtly different from the existing
implicit precondition. Consider an implementation that defines, let’s
say, a deque iterator type such that the singular iterator returned by
a.end()
is dereferenceable and returns a reference to some sentinel value
(perhaps as an attempt to be “user-friendly”). There doesn’t seem to be
anything in the current text of the Standard that rules out such an
implementation; thus, under the current specification, calling a.front()
on an empty deque
would be perfectly
well-defined when using that implementation. However, if we were to
formulate the precondition to assume that the container is not empty,
that would make this previously-valid code run into undefined behavior
in a non-hardened implementation (and terminate in a hardened
implementation).
For this reason, we try to stay as close as possible to the existing specification and formulate the precondition in terms of iterator validity:
a.front()
69 Result:
reference
;const_reference
for constanta
.70 Hardened preconditions:
a.begin()
is dereferenceable.71 Returns:
*a.begin()
While expressing a precondition this way might make it seem complicated, in practice an implementation would know exactly when the iterator is dereferenceable, making this check easy to implement (most implementations can simply check that the container is not empty).
Add a new paragraph to 4.1 [intro.compliance] after paragraph 7 as indicated:
7 Two kinds of implementations are defined: a hosted implementation and a freestanding implementation. A freestanding implementation is one in which execution may take place without the benefit of an operating system. A hosted implementation supports all the facilities described in this document, while a freestanding implementation supports the entire C++ language described in
[lex]
through[cpp]
and the subset of the library facilities described in[compliance]
.
8 Additionally, an implementation can be a hardened implementation. A hardened implementation is one in which violating a hardened precondition is a contract violation.
Add a new element to 16.3.2.4 [structure.specifications] after element 3.3 as indicated:
(3.3) Preconditions: the conditions that the function assumes to hold whenever it is called; violation of any preconditions results in undefined behavior.
(3.4) Hardened preconditions: the conditions that the function assumes to hold whenever it is called; violation of any hardened preconditions results in a contract violation in a hardened implementation, and undefined behavior otherwise.
span
span::operator[]
Modify 23.7.2.2.6 [span.elem] paragraph 1 as indicated:
constexpr reference operator[](size_type idx) const;
1 Hardened
Ppreconditions:idx < size()
istrue
.2 Returns:
*(data() + idx)
.3 Throws: Nothing.
span::front
Modify 23.7.2.2.6 [span.elem] paragraph 6 as indicated:
constexpr reference front() const;
6 Hardened
Ppreconditions:empty()
isfalse
.7 Returns:
*data()
.8 Throws: Nothing.
span::back
Modify 23.7.2.2.6 [span.elem] paragraph 9 as indicated:
constexpr reference back() const;
9 Hardened
Ppreconditions:empty()
isfalse
.10 Returns:
*(data() + (size() - 1))
.11 Throws: Nothing.
span::first
Modify 23.7.2.2.4 [span.sub] paragraph 2 as indicated:
template<size_t Count> constexpr span<element_type, Count> first() const;
1 Mandates:
Count <= Extent
istrue
.2 Hardened
Ppreconditions:Count <= size()
istrue
.3 Effects: Equivalent to:
return R{data(), Count};
whereR
is the return type.
Modify 23.7.2.2.4 [span.sub] paragraph 11 as indicated:
constexpr span<element_type, dynamic_extent> first(size_type count) const;
11 Hardened
Ppreconditions:count <= size()
istrue
.12 Effects: Equivalent to:
return {data(), count};
span::last
Modify 23.7.2.2.4 [span.sub] paragraph 5 as indicated:
template<size_t Count> constexpr span<element_type, Count> last() const;
4 Mandates:
Count <= Extent
istrue
.5 Hardened
Ppreconditions:Count <= size()
istrue
.6 Effects: Equivalent to:
return R{data() + (size() - Count), Count};
whereR
is the return type.
Modify 23.7.2.2.4 [span.sub] paragraph 13 as indicated:
constexpr span<element_type, dynamic_extent> last(size_type count) const;
13 Hardened
Ppreconditions:count <= size()
istrue
.14 Effects: Equivalent to:
return {data() + (size() - count), count};
span::subspan
Modify 23.7.2.2.4 [span.sub] paragraph 8 as indicated:
template<size_t Offset, size_t Count = dynamic_extent> constexpr span<element_type, see below> subspan() const;
7 Mandates:
Offset <= Extent && (Count == dynamic_extent || Count <= Extent - Offset)
is
true
.8 Hardened
Ppreconditions:Offset <= size() && (Count == dynamic_extent || Count <= size() - Offset)
is
true
.
Modify 23.7.2.2.4 [span.sub] paragraph 15 as indicated:
constexpr span<element_type, dynamic_extent> subspan( size_type offset, size_type count = dynamic_extent) const;
15 Hardened
Ppreconditions:offset <= size() && (count == dynamic_extent || count <= size() - offset)
is
true
.
span
constructorsModify 23.7.2.2.2 [span.cons] around paragraph 4 as indicated:
template<class It> constexpr explicit(extent != dynamic_extent) span(It first, size_type count);
[…]
4 Preconditions:
(4.1) -
[first, first + count)
is a valid range.(4.2) -
It
modelscontiguous_iterator
.(4.3) - If
extent
is not equal todynamic_extent
, thencount
is equal toextent
.5 Hardened preconditions:
(5.1) - If
extent
is not equal todynamic_extent
, thencount
is equal toextent
.? Effects: Initializes
data_
withto_address(first)
andsize_
withcount
.? Throws: Nothing.
Modify 23.7.2.2.2 [span.cons] around paragraph 8 as indicated:
template<class It, class End> constexpr explicit(extent != dynamic_extent) span(It first, End last);
[…]
8 Preconditions:
(8.1) - If
extent
is not equal todynamic_extent
, thenlast - first
is equal toextent
.(8.?) -
[first, last)
is a valid range.(8.?) -
It
modelscontiguous_iterator
.(8.?) -
End
modelssized_sentinel_for<It>
.9 Hardened preconditions:
(9.1) - If
extent
is not equal todynamic_extent
, thenlast - first
is equal toextent
.? Effects: Initializes
data_
withto_address(first)
andsize_
withlast - first
.? Throws: When and what
last - first
throws.
Modify 23.7.2.2.2 [span.cons] around paragraph 15 as indicated:
template<class R> constexpr explicit(extent != dynamic_extent) span(R&& r);
[…]
15 Preconditions:
(15.1) - If
extent
is not equal todynamic_extent
, thenranges::size(r)
is equal toextent
.(15.?) -
R
modelsranges::contiguous_range
andranges::sized_range
.(15.?) - If
is_const_v<element_type>
isfalse
,R
modelsranges::borrowed_range
.16 Hardened preconditions:
(16.1) - If
extent
is not equal todynamic_extent
, thenranges::size(r)
is equal toextent
.? Effects: Initializes data_ with
ranges::data(r)
andsize_
withranges::size(r)
.? Throws: What and when
ranges::data(r)
andranges::size(r)
throw.
Modify 23.7.2.2.2 [span.cons] paragraph 19 as indicated:
constexpr explicit(extent != dynamic_extent) span(std::initializer_list<value_type> il);
18 Constraints:
is_const_v<element_type>
istrue
.19 Hardened
Ppreconditions: Ifextent
is not equal todynamic_extent
, thenil.size()
is equal toextent
.20 Effects: Initializes
data_
withil.begin()
andsize_
withil
.size().
Modify 23.7.2.2.2 [span.cons] paragraph 23 as indicated:
template<class OtherElementType, size_t OtherExtent> constexpr explicit(see below) span(const span<OtherElementType, OtherExtent>& s) noexcept;
22 Constraints:
(22.1) -
extent == dynamic_extent || OtherExtent == dynamic_extent || extent == OtherExtent
istrue
, and(22.2) -
is_convertible_v<OtherElementType(*)[], element_type(*)[]>
istrue
.[Note 6: The intent is to allow only qualification conversions of the
OtherElementType
toelement_type
. — end note]23 Hardened
Ppreconditions: Ifextent
is not equal todynamic_extent
, thens.size()
is equal toextent
.24 Effects: Constructs a
span
that is a view over the range[s.data(), s.data() + s.size())
.
basic_string_view
basic_string_view::operator[]
Modify 27.3.3.6 [string.view.access] paragraph 1 as indicated:
constexpr const_reference operator[](size_type pos) const;
1 Hardened
Ppreconditions:pos < size()
.2 Returns:
data_[pos]
.3 Throws: Nothing.
4 [Note 1: Unlike
basic_string::operator[]
,basic_string_view::operator[](size())
has undefined behaviorviolates a hardened precondition instead of returningcharT()
. —end note
]
basic_string_view::front
Modify 27.3.3.6 [string.view.access] paragraph 7 as indicated:
constexpr const_reference front() const;
7 Hardened
Ppreconditions:!empty()
.8 Returns:
data_[0]
.9 Throws: Nothing.
basic_string_view::back
Modify 27.3.3.6 [string.view.access] paragraph 10 as indicated:
constexpr const_reference back() const;
10 Hardened
Ppreconditions:!empty()
.11 Returns:
data_[size() - 1]
.12 Throws: Nothing.
basic_string_view::remove_prefix
Modify 27.3.3.7 [string.view.modifiers] paragraph 1 as indicated:
constexpr void remove_prefix(size_type n);
1 Hardened
Ppreconditions:n <= size()
.2 Effects: Equivalent to:
data_ += n; size_ -= n;
basic_string_view::remove_suffix
Modify 27.3.3.7 [string.view.modifiers] paragraph 3 as indicated:
constexpr void remove_suffix(size_type n);
3 Hardened
Ppreconditions:n <= size()
.4 Effects: Equivalent to:
size_ -= n;
a.front()
Modify 23.2.4 [sequence.reqmts] around paragraph 69 as indicated:
a.front()
69 Result:
reference
;const_reference
for constanta
.70 Hardened preconditions:
a.begin()
is dereferenceable.71 Returns:
*a.begin()
72 Remarks: Required for
basic_string
,array
,deque
,forward_list
,inplace_vector
,list
, andvector
.[Note ?: In most cases, the hardened precondition is equivalent to
!a.empty()
. — end note]
a.back()
Modify 23.2.4 [sequence.reqmts] around paragraph 72 as indicated:
a.back()
72 Result:
reference
;const_reference
for constanta
.73 Hardened preconditions:
std::prev(a.end())
is dereferenceable.74 Effects: Equivalent to:
auto tmp = a.end(); --tmp; return *tmp;
75 Remarks: Required for
basic_string
,array
,deque
,inplace_vector
,list
, andvector
.[Note ?: In most cases, the hardened precondition is equivalent to
!a.empty()
. — end note]
a.pop_front()
Modify 23.2.4 [sequence.reqmts] paragraph 110 as indicated:
a.pop_front()
109 Result:
void
110 Hardened
Ppreconditions:a.empty()
isfalse
.111 Effects: Destroys the first element.
112 Remarks: Required for
deque
,forward_list
, andlist
.
a.pop_back()
Modify 23.2.4 [sequence.reqmts] paragraph 114 as indicated:
a.pop_back()
113 Result:
void
114 Hardened
Ppreconditions:a.empty()
isfalse
.115 Effects: Destroys the last element.
116 Remarks: Required for
basic_string
,deque
,inplace_vector
,list
, andvector
.
a[n]
Modify 23.2.4 [sequence.reqmts] around paragraph 117 as indicated:
a[n]
117 Result:
reference
;const_reference
for constanta
118 Hardened preconditions:
a.begin() + n
is dereferenceable.119 Effects: Equivalent to:
return *(a.begin() + n);
120 Remarks: Required for
basic_string
,array
,deque
,inplace_vector
, andvector
.
[Note ?: In most cases, the hardened precondition is equivalent to
n < a.size()
. — end note]
basic_string
basic_string::operator[]
Modify 27.4.3.8.1 [string.accessors] paragraph 1 as indicated:
constexpr const_reference operator[](size_type pos) const; constexpr reference operator[](size_type pos);
1 Hardened
Ppreconditions:pos <= size()
.2 Returns:
*(begin() + pos)
ifpos < size()
. Otherwise, returns a reference to an object of typecharT
with valuecharT()
, where modifying the object to any value other thancharT()
leads to undefined behavior.3 Throws: Nothing.
4 Complexity: Constant time.
basic_string::front
Modify 27.4.3.8.1 [string.accessors] paragraph 7 as indicated:
constexpr const charT& front() const; constexpr charT& front();
7 Hardened
Ppreconditions:!empty()
.8 Effects: Equivalent to:
return operator[](0);
basic_string::back
Modify 27.4.3.8.1 [string.accessors] paragraph 9 as indicated:
constexpr const charT& back() const; constexpr charT& back();
9 Hardened
Ppreconditions:!empty()
.10 Effects: Equivalent to:
return operator[](size() - 1);
basic_string::pop_back
Modify 27.4.3.7.5 [string.erase] paragraph 12 as indicated:
constexpr void pop_back();
12 Hardened
Ppreconditions:!empty()
.13 Effects: Equivalent to
erase(end() - 1)
.14 Throws: Nothing.
mdspan
mdspan::operator[]
Modify 23.7.3.6.3 [mdspan.mdspan.members] paragraph 3 as indicated:
template<class... OtherIndexTypes> constexpr reference operator[](OtherIndexTypes... indices) const;
[…]
2 Let
I
beextents_type::index-cast(std::move(indices))
.3 Hardened
Ppreconditions:I
is a multidimensional index inextents()
.[Note 1: This implies that
map_(I) < map_.required_span_size()
istrue
. — end note]
mdspan
constructorModify 23.7.3.6.2 [mdspan.mdspan.cons] around paragraph 21 as indicated:
template<class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherAccessor> constexpr explicit(see below) mdspan(const mdspan<OtherElementType, OtherExtents, OtherLayoutPolicy, OtherAccessor>& other);
[…]
21 Preconditions:
(21.1) - For each rank index
r
ofextents_type
,static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r)
istrue
.(21.?) -
[0, map_.required_span_size())
is an accessible range ofptr_
andacc_
for values ofptr_
,map_
, andacc_
after the invocation of this constructor.
bitset
bitset::operator[]
Modify 22.9.2.3 [bitset.members] paragraph 30 as indicated:
constexpr bool operator[](size_t pos) const;
30 Hardened
Ppreconditions:pos
is valid.31 Returns:
true
if the bit at positionpos
in*this
has the value one, otherwisefalse
.32 Throws: Nothing.
Modify 22.9.2.3 [bitset.members] paragraph 33 as indicated:
constexpr bitset::reference operator[](size_t pos);
33 Hardened
Ppreconditions:pos
is valid.34 Returns: An object of type
bitset::reference
such that(*this)[pos] == this->test(pos)
, and such that(*this)[pos] = val
is equivalent tothis->set(pos, val)
.35 Throws: Nothing.
valarray
valarray::operator[]
Modify 29.6.2.4 [valarray.access] paragraph 1 as indicated:
const T& operator[](size_t n) const; T& operator[](size_t n);
1 Hardened
Ppreconditions:n < size()
istrue
.2 Returns: A reference to the corresponding element of the array.
optional
optional::operator->
Modify 22.5.3.7 [optional.observe] paragraph 1 as indicated:
constexpr const T* operator->() const noexcept;
constexpr T* operator->() noexcept;
1 Hardened
Ppreconditions:*this
contains a value.2 Returns:
*val
.3 Remarks: These functions are
constexpr
functions.
optional::operator*
Modify 22.5.3.7 [optional.observe] paragraph 4 as indicated:
constexpr const T& operator*() const & noexcept;
constexpr T& operator*() & noexcept;
4 Hardened
Ppreconditions:*this
contains a value.5 Returns:
*val
.6 Remarks: These functions are
constexpr
functions.
Modify 22.5.3.7 [optional.observe] paragraph 7 as indicated:
constexpr T&& operator*() && noexcept;
constexpr const T&& operator*() const && noexcept;
7 Hardened
Ppreconditions:*this
contains a value.8 Effects: Equivalent to:
return std::move(*val);
expected
expected::operator->
Modify 22.8.6.6 [expected.object.obs] paragraph 1 as indicated:
constexpr const T* operator->() const noexcept;
constexpr T* operator->() noexcept;
1 Hardened
Ppreconditions:has_value()
istrue
.2 Returns:
addressof(val)
.
expected::operator*
Modify 22.8.6.6 [expected.object.obs] paragraph 3 as indicated:
constexpr const T& operator*() const & noexcept;
constexpr T& operator*() & noexcept;
3 Hardened
Ppreconditions:has_value()
istrue
.4 Returns:
val
.
Modify 22.8.6.6 [expected.object.obs] paragraph 5 as indicated:
constexpr T&& operator*() && noexcept;
constexpr const T&& operator*() const && noexcept;
5 Hardened
Ppreconditions:has_value()
istrue
.6 Returns:
std::move(val)
.
expected::error()
Modify 22.8.6.6 [expected.object.obs] paragraph 14 as indicated:
constexpr const E& error() const & noexcept;
constexpr E& error() & noexcept;
14 Hardened
Ppreconditions:has_value()
isfalse
.15 Returns:
unex
.
Modify 22.8.6.6 [expected.object.obs] paragraph 16 as indicated:
constexpr E&& error() && noexcept;
constexpr const E&& error() const && noexcept;
16 Hardened
Ppreconditions:has_value()
isfalse
.17 Returns:
std::move(
unex
)
.
(Authors’ note: we are not attached to any particular way of defining the feature-test macros. The proposed wording adds a macro for each class that would now contain hardened preconditions, but we are very open to suggestions on how these macros may be combined or split further, as well as naming)
Modify 17.3.2 [version.syn] as indicated:
#define __cpp_lib_generator 202207L // also in <generator>
#define __cpp_lib_generic_associative_lookup 201304L // also in <map>, <set>
#define __cpp_lib_generic_unordered_lookup 201811L // also in <unordered_map>, <unordered_set>+ #define __cpp_lib_hardened_array 20????L // also in <array>
+ #define __cpp_lib_hardened_basic_string 20????L // also in <string>
+ #define __cpp_lib_hardened_basic_string_view 20????L // also in <string_view>
+ #define __cpp_lib_hardened_bitset 20????L // also in <bitset>
+ #define __cpp_lib_hardened_deque 20????L // also in <deque>
+ #define __cpp_lib_hardened_expected 20????L // also in <expected>
+ #define __cpp_lib_hardened_forward_list 20????L // also in <forward_list>
+ #define __cpp_lib_hardened_inplace_vector 20????L // also in <inplace_vector>
+ #define __cpp_lib_hardened_list 20????L // also in <list>
+ #define __cpp_lib_hardened_mdspan 20????L // also in <mdspan>
+ #define __cpp_lib_hardened_optional 20????L // also in <optional>
+ #define __cpp_lib_hardened_span 20????L // also in <span>
+ #define __cpp_lib_hardened_valarray 20????L // also in <valarray>
+ #define __cpp_lib_hardened_vector 20????L // also in <vector>
#define __cpp_lib_hardware_interference_size 201703L // freestanding, also in <new>
#define __cpp_lib_has_unique_object_representations 201606L // freestanding, also in <type_traits>
#define __cpp_lib_hazard_pointer 202306L // also in <hazard_pointer> #define __cpp_lib_hypot 201603L // also in <cmath>