Document number | P2517R0 |
Date | 2022-01-01 |
Audience | LEWG |
Reply-to | Hewill Kang <hewillk@gmail.com> |
noexcept
specification to std::apply
This paper proposes to add a noexcept
-specification to std::apply
.
With the introduction of the C++23 zip
family,
apply
is making a comeback and appeared in a lot of its function implementations.
For example, in [range.zip.transform.iterator],
zip_transform_view::iterator::operator*()
's equivalent Effect is defined as:
return apply([&](const auto&... iters) -> decltype(auto) {
return invoke(*parent_->fun_, *iters...);
}, inner_.current_);
which uses apply
to extract the elements of the iterator tuple
and forwards them into the callable.
However, strictly speaking, this operator*()
should not be done through apply
.
The reason is that it still has a noexcept(see below)
specification which is equivalent to
noexcept(invoke(*parent_->fun_, *std::get<Is>(inner_.current_)...))
,
where Is
is the pack 0
, 1
, …, (sizeof...(Views)-1)
.
And in view of the fact that standard is very conservative with the noexcept
specifications in the library specification,
this makes apply
lacks the noexcept
specification and becomes a non-noexcept
function.
Fortunately, the standard also defines the
semantics of apply
in terms of another equivalent-to Effects, this part constitutes the effective noexcept
specification of operator*()
.
But if we look at apply
closely, according to its Effects in [tuple.apply]:
template<class F, class Tuple, size_t... I>
constexpr decltype(auto) apply-impl(F&& f, Tuple&& t, index_sequence<I...>) {
// exposition only
return INVOKE(std::forward<F>(f), get<I>(std::forward<Tuple>(t))...)
}
It just simply uses index_sequence
to expand get
to extract the elements of tuple
and then forward them to INVOKE
together with callable. And since get
is a noexcept
function
(except for the subrange
-overload, but the standard does not specify whether apply
can be applied to subrange
)
and invoke
is conditional noexcept
, I think there is no reason not to make apply
"transparently" become conditional noexcept
.
In my opinion, invoke(f, args...)
should be completely equivalent to apply(f, forward_as_tuple(args...))
,
adding noexcept
to apply
can easily achieve this and make it more consistent with invoke
.
Since it is a pure change for apply
, there will be no impact.
For the zip
family, apply
can "indeed" be used for its implementation.
This also allows users to freely add noexcept
specification to the functions implemented through apply
in the future.
std::get
never throws.
MSVC-STL is consistent with standard and does not add noexcept
specification to apply
.
Edit 20.5.5 [tuple.apply] as indicated:
template<class F, class Tuple> constexpr decltype(auto) apply(F&& f, Tuple&& t) noexcept(see below);-1- Effects: Given the exposition-only function:
-2- Remarks: Let I be the pack 0, 1, …, (tuple_size_v<remove_namespace std { template<class F, class Tuple, size_t... I> constexpr decltype(auto) apply-impl(F&& f, Tuple&& t, index_sequence<I...>) { // exposition only return INVOKE(std::forward<F>(f), get<I>(std::forward<Tuple>(t))Equivalent to:...); } } return apply-impl(std::forward<F>(f), std::forward<Tuple>(t), make_index_sequence<tuple_size_v<remove_reference_t< Tuple>>>{});
zip
. URL: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2321r2.html
apply
implementation in libc++. URL: https://github.com/llvm/llvm-project/blob/d2b0df35afb7184f5a68f67d6ed0c6230688df7f/libcxx/include/tuple#L1576
apply
implementation in libstdc++. URL: https://github.com/llvm/llvm-project/blob/d2b0df35afb7184f5a68f67d6ed0c6230688df7f/libcxx/include/tuple#L1576
apply
implementation in Microsoft STL. URL: https://github.com/microsoft/STL/blob/205aed72533849619a6dadbef44eab541a75c549/stl/inc/tuple#L978