constexpr exception types
This paper is mechanical wording change; making all exception types associated with constexpr compatible functionality marked constexpr
. This is needed for consistency across library since allowing exception handling during constant evaluation by P3068.
As every exception type is just an ordinary type, they should be make constexpr compatible.
Changes
- R0 → R1: Updated status of
<exception>
's exceptions, added note for library test macro (also in ...)
TL;DR
Now when we can throw exceptions during constant evaluation we should make sure all constexpr compatible functionality (eg. vector
) is able to throw its exception types (eg. out_of_range
) and allow users to recover from errors gracefully.
This proposal fixes the lag between constexpr exception support and library constexpr support and all future constexprification papers should make their exception types constexpr.
Implementation experience
Implemented in libc++ as part of implementation of P3068, with source code available on my github and with compiler + library available on the compiler explorer.
constexpr bool check(const char * msg) {
try {
auto vec = std::vector<int>{0,1,2,3};
return vec.at(4) == 4; // out-of-range
} catch (const std::out_of_range & exc) {
return std::string_view{exc.what()} == msg;
} catch (...) {
return false;
}
return false;
}
static_assert(check("vector"));
assert(check("vector"));
libc++
In libc++ most of exception types have implementation of what()
function members and destructors inside .cpp
files. Most of them are in form:
constexpr const char* EXCEPTION_TYPE::what() const noexcept { return "EXCEPTION_MESSAGE"; }
constexpr ~EXCEPTION_TYPE() noexcept { /* do nothing */ }
This code needs to be moved to header files.
Reference counted string
One obstacle was a reference counted string inside logic_error
and runtime_error
which allocates sizeof(_Rep_base) + strlen(_string) + 1
byte storage, and is using reinterpret_cast
to access the _Rep_base
. Also it uses atomic operations for refcounting.
This can be avoided by not doing this during constant evaluation and just copy the string everytime underlying exception is copied or assigned.
libc++abi
By moving exception member functions implementation to header files out of .cpp
files we are loosing existing symbol emitted inside shared library of libc++ and libc++abi. This needs to be carefully fixed by providing same symbol explicitly to keep compatibility.
Dependency on string
All library generic errors (based on logic_error
and runtime_error
) are using also std::string
constructor. But <stdexcept>
is also required by <string>
. This creates a cycle of dependency and fix is moving implementation of <stdexcept>
constructors using std::string
after definition of std::basic_string<CharT>
template.
Existing exception types
Following table shows in detail what is proposed and what is not. Also it shows which standard library implementation has exception types implemented in headers and which not. In addition to it you can see if functionality throwing the exception is already constant evaluatable or not. Allowing associated exception types to be constant evaluatable will allow users to recover from errors.
exception type | defined in | constexpr | implemented in header | note | ||||
---|---|---|---|---|---|---|---|---|
exception itself | associated functionality | libc++ | libstdc++ | STL | ||||
language | exception | <exception> | ✔︎ | ✔︎ | x | x | ✔︎ | P3068, base class for all standard exceptions |
bad_alloc | <new> | ✔︎ | ✔︎ | x | x | ✔︎ | P3068 | |
bad_array_new_length | <new> | ✔︎ | ✔︎ | x | x | ✔︎ | P3068 | |
bad_cast | <typeinfo> | ✔︎ | ✔︎ | x | x | ✔︎ | P3068 | |
bad_exception | <exception> | ✔︎ | ✔︎ | x | x | ✔︎ | P3068 | |
bad_typeid | <typeinfo> | ✔︎ | ✔︎ | x | x | ✔︎ | P3068 | |
library generic errors | domain_error | <stdexcept> | proposed | – | x | x | ✔︎ | |
invalid_argument | <stdexcept> | proposed | – | x | x | ✔︎ | ||
length_error | <stdexcept> | proposed | – | x | x | ✔︎ | ||
logic_error | <stdexcept> | proposed | – | x | x | ✔︎ | base class for others | |
out_of_range | <stdexcept> | proposed | – | x | x | ✔︎ | ||
overflow_error | <stdexcept> | proposed | – | x | x | ✔︎ | ||
range_error | <stdexcept> | proposed | – | x | x | ✔︎ | ||
runtime_error | <stdexcept> | proposed | – | x | x | ✔︎ | base class for others | |
underflow_error | <stdexcept> | proposed | – | x | x | ✔︎ | ||
library errors | bad_any_cast | <any> | not proposed | x | x | ✔︎ | ✔︎ | |
bad_expected_access<T> | <expected> | proposed | ✔︎ | x | ✔︎ | ✔︎ | ||
bad_function_call | <functional> | not proposed | x | x | x | ✔︎ | ||
bad_optional_access | <optional> | proposed | ✔︎ | x | ✔︎ | ✔︎ | ||
bad_variant_access | <variant> | proposed | ✔︎ | x | ✔︎ | ✔︎ | ||
bad_weak_ptr | <memory> | in progress | in progress | x | x | ✔︎ | P3037 | |
ios_base::failure | <ios> | not proposed | x | x | x | ✔︎ | ||
filesystem_error | <filesystem> | not proposed | x | x | x | ✔︎ | ||
future_error | <future> | not proposed | x | x | x | ✔︎ | ||
chrono::nonexistent_local_time | <chrono> | not proposed | x | x | ✔︎ | ✔︎ | uses ostringstream | |
chrono::ambiguous_local_time | <chrono> | not proposed | x | x | ✔︎ | ✔︎ | uses ostringstream | |
regex_error | <regex> | not proposed | x | x | x | ✔︎ | ||
format_error | <format> | proposed | ✔︎ | x | ✔︎ | ✔︎ | ||
system_error | <system_error> | not proposed | x | x | x | ✔︎ |
error_code
and error_category
.Should all exception types be constexpr?
In general, yes. But I'm not proposing it. But in future every constexprification proposal should make associated exception types constexpr compatible.
std::runtime_error
Yes, this exception should be constexpr
too, as it's used as base classes for other exception types (eg. out_of_range
). Maybe the name sounds funny in constant evaluation context, but it is really needed to be constexpr compatible.
std::error_code
and std::error_category
depending exception types
In future to make all exception types constant evaluatable we will need to make parts of std::error_code
and std::error_category
constexpr. List of these types and their dependencies:
filesystem_error
—error_code
onlysystem_error
— botherror_code
anderror_category
ios_base::failure
—error_code
only
Impact on existing code
Pure extension, previously types weren't compatible with constant evaluation. For standard libraries it needs to be implemented carefully to not break ABI.
Intention for wording changes
Mark all function members, constructors, and destructors of all following exception types with constexpr
:
- logic_error,
- domain_error,
- invalid_argument,
- length_error,
- out_of_range,
- runtime_error,
- range_error,
- overflow_error,
- underflow_error,
- bad_optional_access,
- bad_variant_access,
- bad_expected_access,
- format_error
In addition to this change, some .what()
members mentions they are returning implementation-defined NTBS and these strings needs to be in ordinary literal encoding (as recommended by SG16 for P3068: Allowing exception throwing in constant-evaluation).
Proposed changes to wording
19.2.3 Class logic_error [logic.error]
constexpr logic_error(const string& what_arg);
constexpr logic_error(const char* what_arg);
19.2.4 Class domain_error [domain.error]
constexpr domain_error(const string& what_arg);
constexpr domain_error(const char* what_arg);
19.2.5 Class invalid_argument [invalid.argument]
constexpr invalid_argument(const string& what_arg);
constexpr invalid_argument(const char* what_arg);
19.2.6 Class length_error [length.error]
constexpr length_error(const string& what_arg);
constexpr length_error(const char* what_arg);
19.2.7 Class out_of_range [out.of.range]
constexpr out_of_range(const string& what_arg);
constexpr out_of_range(const char* what_arg);
19.2.8 Class runtime_error [runtime.error]
constexpr runtime_error(const string& what_arg);
constexpr runtime_error(const char* what_arg);
19.2.9 Class range_error [range.error]
constexpr range_error(const string& what_arg);
constexpr range_error(const char* what_arg);
19.2.10 Class overflow_error [overflow.error]
constexpr overflow_error(const string& what_arg);
constexpr overflow_error(const char* what_arg);
19.2.11 Class underflow_error [underflow.error]
constexpr underflow_error(const string& what_arg);
constexpr underflow_error(const char* what_arg);
22.5.5 Class bad_optional_access [optional.bad.access]
constexpr const char* what() const noexcept override;
22.6.11 Class bad_variant_access [variant.bad.access]
constexpr const char* what() const noexcept override;
22.8.4 Class template bad_expected_access [expected.bad]
constexpr explicit bad_expected_access(E e);
constexpr const E& error() const & noexcept;
constexpr E& error() & noexcept;
constexpr E&& error() && noexcept;
constexpr const E&& error() const && noexcept;
constexpr const char* what() const noexcept override;
22.8.5 Class template specialization bad_expected_access<void> [expected.bad.void]
constexpr const char* what() const noexcept override;
22.14.10 Class format_error [format.error]
constexpr format_error(const string& what_arg);
constexpr format_error(const char* what_arg);
Feature test macro
17.3.2 Header <version> synopsis [version.syn]
#define __cpp_lib_constexpr_exception_types 2024??L // also in <stdexcept>