GET
function in variant
rather than respecify in terms of get_if
string_view
rather than exposition interface when accessing non-this
objectsfill_n
and swap_ranges
from <algorithm>
optional
now specified in terms of **this
instead of .value()
variant
now specified in terms of get_if
instead of get
string_view
find / search operations now specified in terms of data_
instead of at()
size()
call in string_view::ends_with
// freestanding
// hosted
vs. // freestanding-deleted
// freestanding-delete
to // freestanding-deleted
bad_optional_access
per LWG's request for consistencystring_view::starts_with
and string_view::ends_with
in terms of freestanding functions// freestanding-deleted
// mostly freestanding
header markerstring_view::contains
All of the added classes are fundamentally compatible with freestanding, except for a few methods that throw (e.g. array::at). We explicitly =delete these undesirable methods.
The main driving factor for these additions is the immense usefulness of these types in practice.
Since we aren’t changing the semantics of any of the classes (except deleted non-critical methods), it is fair to say that all of the (implementer and user) experience gathered as part of hosted applies the same to freestanding.
The only question is, whether these classes are compatible with freestanding. To which the answer is yes! For example, the [Embedded Template Library] offers direct mappings of the std types. Even in kernel-level libraries, like Serenity’s [AK] use a form of these utilities.
Our decision to delete methods we can’t mark as freestanding was made to keep overload resolution the same on freestanding as hosted.
An additional benefit here is, that users of these classes, who might expect to use a throwing method, which was not provided by the implementation, will get a more meaningful error than the method simply missing. This also means we can keep options open for reintroducing the deleted functions into freestanding. (e.g. operator<<(ostream, string_view), should <ostream> be added).
The predecessor to this paper used //freestanding, partial
to mean a class (template) is only required to be partially implemented, in conjunction with //freestanding, omit
meaning a declaration is not in freestanding.
In this paper, we mark not fully freestanding classes templates as // partially freestanding
, and use P2338's // freestanding-deleted
to mark which pieces of the class should be omitted.
We no longer annotate all the class members, favoring terseness over explicitness.
In this paper, we mark std::visit as freestanding, even though it is theoretically throwing. However, the conditions for std::visit to throw are as follows:
It is possible for a variant to hold no value if an exception is thrown during a type-changing assignment or emplacement.
This means a variant will only throw on visit if a user type throws (library types don’t throw on freestanding). In this case, std::visit throwing isn’t a problem, since the user’s code is already using, and (hopefully) handling exceptions.
This however has the unfortunate side-effect that we need to keep bad_variant_access freestanding.
By getting rid of std::get, we force users to use std::get_if. Since std::get_if returns a pointer, one can only access the value of a variant by dereferencing said pointer, obtaining an lvalue, discarding the value category of the held object. This is unlikely to have an impact on application code, but might impact highly generic library code.
std::forward_like can help in these cases. The value category of the variant can be transferred to the dereferenced pointer returned from set::get_if.
<algorithm>
inclusionsstd::array::fill
is specified in terms of std::fill_n
.
std::array::swap
is specified in terms of std::swap_ranges
.
Both fill_n
and swap_ranges
are reasonable inclusions in freestanding.
Rather than respecify array
in terms of other freestanding facilities, we have chosen to pull in the functions from <algorithm>
that we need.
A later paper will likely add many more facilities from <algorithm>
.
An email on the LEWG reflector in June 2023 asked to add std::fill_n
and std::swap_ranges
.
The request received eleven +1's, and no opposition to this change.
The authors did not vote on the reflector poll.
Unless otherwise specified, the requirements on freestanding items for a freestanding implementation are the same as the corresponding requirements for a hosted implementation, except that not all of the members ofthe namespacesthose items are required to be present.[Note 1:This implies that freestanding item enumerations have the same enumerators on freestanding implementations and hosted implementations.Furthermore, class types have the same members and class templates have the same deduction guides on freestanding implementations and hosted implementations.— end note]Function declarations and function template declarations followed by a comment that include freestanding-deleted are freestanding deleted functions.On freestanding implementations, it is implementation defined whether eachfunction definitionentity introduced by a freestanding deleted function isa freestanding item ora deleted function ([dcl.fct.def.delete]) or whether the requirements are the same as the corresponding requirements for a hosted implementation.[ Note: Deleted definitions reduce the chance of overload resolution silently changing when migrating from a freestanding implementation to a hosted implementation. -end note][ Example:double abs(double j); // freestanding-deleted
-end example]A declaration in aheadersynopsis is a freestanding item if
- it is followed by a comment that includes freestanding,
or- it is followed by a comment that includes freestanding-deleted, or
- the header synopsis begins with a comment that includes
allfreestanding and the declaration is not followed by a comment that includes hosted.[ Note: Declarations followed by hosted in freestanding headers are not freestanding items. As a result, looking up the name of such functions can vary between hosted and freestanding implementations. -end note]An entity, deduction guide, or typedef-name is a freestanding item if it is:
- introduced by a declaration that is a freestanding item,
- a member of a freestanding item other than a namespace,
- an enumerator of a freestanding item,
- a deduction guide of a freestanding item,
- an enclosing namespace of a freestanding item,
- a friend of a freestanding item,
- denoted by a typedef-name that is a freestanding item, or
- denoted by an alias template that is a freestanding item.
A macro is a freestanding item if it is defined in a header synopsis and
- the definition is followed by a comment that includes freestanding, or
- the header synopsis begins with a comment that includes
allfreestanding and the definition is not followed by a comment that includes hosted.[ Note: Freestanding annotations follow some additional exposition conventions that do not impose any additional normative requirements. Header synopses that begin with a comment containing "all freestanding" contain no hosted items and no freestanding deleted functions. Header synopses that begin with a comment containing "mostly freestanding" contain at least one hosted item or freestanding deleted function. Classes and class templates followed by a comment containing "partially freestanding" contain at least one hosted item or freestanding deleted function. -end note][ Example:template <class T, size_t N> struct array; //partially freestanding template<class T, size_t N> struct array { constexpr reference operator[](size_type n); constexpr const_reference operator[](size_type n) const; constexpr reference at(size_type n); //freestanding-deleted constexpr const_reference at(size_type n) const; //freestanding-deleted };-end example]
Subclause | Header(s) | |
---|---|---|
[…] | […] | […] |
?.? [optional] | Optional objects | <optional> |
?.? [variant] | Variants | <variant> |
?.? [string.view] | String view classes | <string_view> |
?.? [array] | Class template array |
<array> |
?.? [algorithms] | Algorithms library | <algorithm> |
[…] | […] | […] |
#define __cpp_lib_freestanding_algorithm 20XXXXL // freestanding, also in <algorithm> #define __cpp_lib_freestanding_array 20XXXXL // freestanding, also in <array> #define __cpp_lib_freestanding_optional 20XXXXL // freestanding, also in <optional> #define __cpp_lib_freestanding_string_view 20XXXXL // freestanding, also in <string_view> #define __cpp_lib_freestanding_variant 20XXXXL // freestanding, also in <variant>
Please insert a // mostly freestanding
comment at the beginning of the [optional.syn] synopsis.
// partially freestanding
comment to the following declaration:
optional
Instructions to the editor:
Please append a // freestanding-deleted
comment to every overload of value
.
Please modify the functions in [optional.monadic] so that they don't reference the freestanding-deleted method value()
.
template<class F> constexpr auto and_then(F&& f) &; template<class F> constexpr auto and_then(F&& f) const &;
Let U be invoke_result_t<F, decltype (value()**this)>.Mandates: remove_cvref_t<U> is a specialization of optional.Effects: Equivalent to: if (*this) { return invoke(std::forward<F>(f),value()**this); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto and_then(F&& f) &&; template<class F> constexpr auto and_then(F&& f) const &&;
Let U be invoke_result_t<F, decltype(std::move(value()**this))>.Mandates: remove_cvref_t<U> is a specialization of optional.Effects: Equivalent to: if (*this) { return invoke(std::forward<F>(f), std::move(value()**this)); } else { return remove_cvref_t<U>(); }template<class F> constexpr auto transform(F&& f) &; template<class F> constexpr auto transform(F&& f) const &;
Let U be remove_cv_t<invoke_result_t<F, decltype(value()**this)>>.Mandates: U is a non-array object type other than in_place_t or nullopt_t.The declaration U u(invoke(std::forward<F>(f),value()**this)); is well-formed for some invented variable u.[Note 1:There is no requirement that U is movable ([dcl.init.general]).— end note]Returns: If *this contains a value, an optional<U> object whose contained value is direct-non-list-initialized with invoke(std::forward<F>(f),value()**this); otherwise, optional<U>().template<class F> constexpr auto transform(F&& f) &&; template<class F> constexpr auto transform(F&& f) const &&;
Let U be remove_cv_t<invoke_result_t<F, decltype(std::move(value()**this))>>.Mandates: U is a non-array object type other than in_place_t or nullopt_t.The declaration U u(invoke(std::forward<F>(f), std::move(value()**this))); is well-formed for some invented variable u.[Note 2:There is no requirement that U is movable ([dcl.init.general]).— end note]Returns: If *this contains a value, an optional<U> object whose contained value is direct-non-list-initialized with invoke(std::forward<F>(f), std::move(value()**this)); otherwise, optional<U>().
LWG note: four GET
overloads are added, because we can't use get
in the specification anymore since it could be =delete
d, and we still need the right return types, value categories, and mandates of GET
to deal with visit
and the relops.
Add a new paragraph to [variant.general].
In subclause [variant],GET
denotes a set of exposition only function templates ([variant.get]).
// mostly freestanding #include <compare> // see [compare.syn] namespace std {...
// [variant.get], value access template<class T, class... Types> constexpr bool holds_alternative(const variant<Types...>&) noexcept; template<size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>>& get(variant<Types...>&); // freestanding-deleted template<size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>>&& get(variant<Types...>&&); // freestanding-deleted template<size_t I, class... Types> constexpr const variant_alternative_t<I, variant<Types...>>& get(const variant<Types...>&); // freestanding-deleted template<size_t I, class... Types> constexpr const variant_alternative_t<I, variant<Types...>>&& get(const variant<Types...>&&); // freestanding-deleted template<class T, class... Types> constexpr T& get(variant<Types...>&); // freestanding-deleted template<class T, class... Types> constexpr T&& get(variant<Types...>&&); // freestanding-deleted template<class T, class... Types> constexpr const T& get(const variant<Types...>&); // freestanding-deleted template<class T, class... Types> constexpr const T&& get(const variant<Types...>&&); // freestanding-deleted
constexpr variant(const variant& w);
constexpr variant(variant&& w) noexcept(see below);
constexpr variant& operator=(const variant& rhs);
Effects:
Otherwise, if *this holds a value but rhs does not, destroys the value contained in *this and sets *this to not hold a value. Otherwise, if either is_nothrow_copy_constructible_v<> is true or is_nothrow_move_constructible_v<> is false, equivalent to emplace<j>(getGET<j>(rhs)). Otherwise, equivalent to operator=(variant(rhs)).constexpr variant& operator=(variant&& rhs) noexcept(see below);
Effects:
constexpr void swap(variant& rhs) noexcept(see below);
Remarks: If an exception is thrown during the call to function swap(getGET<i>(*this),getGET<i>(rhs)), the states of the contained values of *this and of rhs are determined by the exception safety guarantee of swap for lvalues of with i being index().If an exception is thrown during the exchange of the values of *this and rhs, the states of the values of *this and of rhs are determined by the exception safety guarantee of variant's move constructor.The exception specification is equivalent to the logical and of is_nothrow_move_constructible_v<> && is_nothrow_swappable_v<> for all i.
Replace every instance of get<i> in [variant.relops] with GET<i>.
template<size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>>& GET(variant<Types...>& v); // exposition only template<size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>>&& GET(variant<Types...>&& v); // exposition only template<size_t I, class... Types> constexpr const variant_alternative_t<I, variant<Types...>>& GET(const variant<Types...>& v); // exposition only template<size_t I, class... Types> constexpr const variant_alternative_t<I, variant<Types...>>&& GET(const variant<Types...>&& v); // exposition only
Preconditions: v.index() is I.template<size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>>& get(variant<Types...>& v); template<size_t I, class... Types> constexpr variant_alternative_t<I, variant<Types...>>&& get(variant<Types...>&& v); template<size_t I, class... Types> constexpr const variant_alternative_t<I, variant<Types...>>& get(const variant<Types...>& v); template<size_t I, class... Types> constexpr const variant_alternative_t<I, variant<Types...>>&& get(const variant<Types...>&& v);
Changes in [variant.visit]
For each valid pack m, let e(m) denote the expression: INVOKE(std::forward<Visitor>(vis),getGET<m>(std::forward<V>(vars))...) // see [func.require] for the first form and INVOKE<R>(std::forward<Visitor>(vis),getGET<m>(std::forward<V>(vars))...) // see [func.require] for the second form.Changes in [string.view.synop]
Instructions to the editor:
Please insert a
Please append a// mostly freestanding
comment at the beginning of the [string.view.synop] synopsis.
// hosted
comment to the following declaration:Please append a
operator<<
// partially freestanding
comment to the following declaration:
basic_string_view
Changes in [string.view.template.general]
Instructions to the editor:
Please append a// freestanding-deleted
to the following functions:
at
copy
substr
- The following overloads of compare:
compare(size_type pos1, size_type n1, basic_string_view s)
compare(size_type pos1, size_type n1, basic_string_view s, size_type pos2, size_type n2)
compare(size_type pos1, size_type n1, const charT* s)
compare(size_type pos1, size_type n1, const charT* s, size_type n2)
Note that the
compare(basic_string_view str) const
andcompare(const charT* s) const
overloads are intentionally not freestanding-deleted.Changes in [string.view.ops]
Please modify thebasic_string_view
overload ofstarts_with
so that it doesn't reference freestanding-deleted methods.Please modify theconstexpr bool starts_with(basic_string_view x) const noexcept;
basic_string_view
overload ofends_with
so that it doesn't reference freestanding-deleted methods.constexpr bool ends_with(basic_string_view x) const noexcept;
Changes in [string.view.find]
Please avoid using the the freestanding-deleted method
at()
.Instructions to the editor:
- Replace every instance of at(xpos + I) in [string.view.find] with data_[xpos + I].
- Replace every instance of at(xpos) in [string.view.find] with data_[xpos].
- Replace every instance of str.at(I) in [string.view.find] with str[I].
Changes in [array.syn]
Instructions to the editor:
Please insert a
// mostly freestanding
comment at the beginning of the [array.syn] synopsis.
Please append a
// partially freestanding
comment toarray
Changes in [array.overview]
Instructions to the editor:
Please append a// freestanding-deleted
comment to every overload ofat
.Changes in [algorithm.syn]
Instructions to the editor:
Please append a
// freestanding
comment to the following functions, so thatstd::array
can be specified in terms of freestanding functions:
fill_n(OutputIterator first, Size n, const T& value)
swap_ranges(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2)
References
[AK] Andreas Kling. Serenity OS AK Library.
https://github.com/SerenityOS/serenity/tree/master/AK
[Embedded Template Library] John Wellbelove. Embedded Template Library.
https://www.etlcpp.com/
[N4928] Thomas Köppe. 2022-12-18. Working Draft, Standard for Programming Language C++.
https://wg21.link/n4928
[P2198R6] Ben Craig. 2022-12-06. Freestanding Feature-Test Macros and Implementation-Defined Extensions.
https://wg21.link/P2198R6
[P2268R0] Ben Craig. 2020-12-10. Freestanding Roadmap.
https://wg21.link/p2268r0
[P2338R4] Ben Craig. 2023-02-09. Freestanding Library: Character primitives and the C library.
https://wg21.link/P2338R4