Date: Mon, 4 Sep 2023 06:13:14 +0000
Abstract
This paper proposes a new syntax for noexcept-specification, noexcept(auto), which deduces the exception specification of a function from its body or member initializer list. This feature aims to simplify the use of conditional noexcept and avoid the redundancy and ugliness of manually writing noexcept expressions.
Motivation
Since C++11 introduced the noexcept specifier, conditional noexcept has been widely used in the standard library. It is undeniable that noexcept can avoid the compiler generating code to handle exceptions and improve performance (both at compile time and run time). Therefore, [P2517] and [P2401] added conditional noexcept for some simple functions. However, there are still many functions that do not use conditional noexcept, but potentially-throwing, such as ranges algorithms, std::min, std::greater, etc.
I noticed that Microsoft STL orginally added conditional noexcept for std::greater::operator()<https://github.com/microsoft/STL/blob/6c69a73911b33892919ec628c0ea5bbf0caf8a6a/stl/inc/xutility#L409>:
export template <class _Ty = void>
struct greater {
[[nodiscard]] constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const
noexcept(noexcept(_Fake_copy_init<bool>(_Left > _Right))) /* strengthened */ {
return _Left > _Right;
}
};
and std::invoke<https://github.com/microsoft/STL/blob/6c69a73911b33892919ec628c0ea5bbf0caf8a6a/stl/inc/type_traits#L1728C1-L1728C1> has the following implementation:
export template <class _Callable>
constexpr auto invoke(_Callable&& _Obj) noexcept(noexcept(static_cast<_Callable&&>(_Obj)()))
-> decltype(static_cast<_Callable&&>(_Obj)()) {
return static_cast<_Callable&&>(_Obj)();
}
// and other overloaded versions
These uses of the noexcept operator are undoubtedly redundant and ugly: noexcept(_Fake_copy_init<bool>(_Left > _Right)) and noexcept(static_cast<_Callable&&>(_Obj)()).
The cause of this problem is an unfortunate historical issue: C++11 invented the noexcept specifier, but until C++17, the noexcept-specification became part of the function type. Therefore, before C++17, we did not have a good way to propagate this information, which caused some confusion.
Also, manually writing noexcept specifications is a painful thing: if a function needs to conditionally throw exceptions but makes an incomplete check, it will cause the program abort, which is very dangerous.
Proposal
I propose to add noexcept(auto) as a placeholder for noexcept-specification, which is equivalent to noexcept(false), except in the following cases where it is equivalent to noexcept(true):
* It is not a non-throwing function as specified by the standard, and
* Any function call within the function body or member initializer list does not throw exceptions, or
* The function body is a try block, and the last catch catches all exceptions, or
* Within the function body, any function call that throws an exception is wrapped by a try block, and the last catch catches all exceptions
Furthermore, in constexpr if and consteval if, the discarded branch does not participate in this deduction.
Design Decisions
The motivations for this proposal is to simplify the use of conditional noexcept in the standard library and user code. However, there is another proposal, P0709, also known as Herbceptions, which uses a throws specifier to specify the possible thrown. This proposal also has a similar problem.
For example:
template<class In, class Out, class Op>
Out transform( In first, In last, Out out, Op op ) throws (throws(op(*first)));
If P0709 makes further progress in the future, then throws(auto) should also be considered.
Impact on the standard
This proposal is a pure syntactic extension, it does not conflict with any existing code, and it is foreseeable that it will not in the future. It does not force the standard library to make any updates, nor does it cause any API or ABI breaks.
Acknowledgements
I would like to thank Xiaoyu Yan for providing the information about std::max.
References
* P2517<https://markdown.lovejade.cn/wg21.link/p2517>
* P2401<https://markdown.lovejade.cn/wg21.link/p2401>
* P0709<https://markdown.lovejade.cn/wg21.link/p0709>
* std::greater::operator()<https://github.com/microsoft/STL/blob/6c69a73911b33892919ec628c0ea5bbf0caf8a6a/stl/inc/xutility#L409>
* std::invoke<https://github.com/microsoft/STL/blob/6c69a73911b33892919ec628c0ea5bbf0caf8a6a/stl/inc/type_traits#L1728C1-L1728C1>
* comparisons.greater<https://eel.is/c++draft/comparisons.greater>
This paper proposes a new syntax for noexcept-specification, noexcept(auto), which deduces the exception specification of a function from its body or member initializer list. This feature aims to simplify the use of conditional noexcept and avoid the redundancy and ugliness of manually writing noexcept expressions.
Motivation
Since C++11 introduced the noexcept specifier, conditional noexcept has been widely used in the standard library. It is undeniable that noexcept can avoid the compiler generating code to handle exceptions and improve performance (both at compile time and run time). Therefore, [P2517] and [P2401] added conditional noexcept for some simple functions. However, there are still many functions that do not use conditional noexcept, but potentially-throwing, such as ranges algorithms, std::min, std::greater, etc.
I noticed that Microsoft STL orginally added conditional noexcept for std::greater::operator()<https://github.com/microsoft/STL/blob/6c69a73911b33892919ec628c0ea5bbf0caf8a6a/stl/inc/xutility#L409>:
export template <class _Ty = void>
struct greater {
[[nodiscard]] constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const
noexcept(noexcept(_Fake_copy_init<bool>(_Left > _Right))) /* strengthened */ {
return _Left > _Right;
}
};
and std::invoke<https://github.com/microsoft/STL/blob/6c69a73911b33892919ec628c0ea5bbf0caf8a6a/stl/inc/type_traits#L1728C1-L1728C1> has the following implementation:
export template <class _Callable>
constexpr auto invoke(_Callable&& _Obj) noexcept(noexcept(static_cast<_Callable&&>(_Obj)()))
-> decltype(static_cast<_Callable&&>(_Obj)()) {
return static_cast<_Callable&&>(_Obj)();
}
// and other overloaded versions
These uses of the noexcept operator are undoubtedly redundant and ugly: noexcept(_Fake_copy_init<bool>(_Left > _Right)) and noexcept(static_cast<_Callable&&>(_Obj)()).
The cause of this problem is an unfortunate historical issue: C++11 invented the noexcept specifier, but until C++17, the noexcept-specification became part of the function type. Therefore, before C++17, we did not have a good way to propagate this information, which caused some confusion.
Also, manually writing noexcept specifications is a painful thing: if a function needs to conditionally throw exceptions but makes an incomplete check, it will cause the program abort, which is very dangerous.
Proposal
I propose to add noexcept(auto) as a placeholder for noexcept-specification, which is equivalent to noexcept(false), except in the following cases where it is equivalent to noexcept(true):
* It is not a non-throwing function as specified by the standard, and
* Any function call within the function body or member initializer list does not throw exceptions, or
* The function body is a try block, and the last catch catches all exceptions, or
* Within the function body, any function call that throws an exception is wrapped by a try block, and the last catch catches all exceptions
Furthermore, in constexpr if and consteval if, the discarded branch does not participate in this deduction.
Design Decisions
The motivations for this proposal is to simplify the use of conditional noexcept in the standard library and user code. However, there is another proposal, P0709, also known as Herbceptions, which uses a throws specifier to specify the possible thrown. This proposal also has a similar problem.
For example:
template<class In, class Out, class Op>
Out transform( In first, In last, Out out, Op op ) throws (throws(op(*first)));
If P0709 makes further progress in the future, then throws(auto) should also be considered.
Impact on the standard
This proposal is a pure syntactic extension, it does not conflict with any existing code, and it is foreseeable that it will not in the future. It does not force the standard library to make any updates, nor does it cause any API or ABI breaks.
Acknowledgements
I would like to thank Xiaoyu Yan for providing the information about std::max.
References
* P2517<https://markdown.lovejade.cn/wg21.link/p2517>
* P2401<https://markdown.lovejade.cn/wg21.link/p2401>
* P0709<https://markdown.lovejade.cn/wg21.link/p0709>
* std::greater::operator()<https://github.com/microsoft/STL/blob/6c69a73911b33892919ec628c0ea5bbf0caf8a6a/stl/inc/xutility#L409>
* std::invoke<https://github.com/microsoft/STL/blob/6c69a73911b33892919ec628c0ea5bbf0caf8a6a/stl/inc/type_traits#L1728C1-L1728C1>
* comparisons.greater<https://eel.is/c++draft/comparisons.greater>
Received on 2023-09-04 06:13:20