SFINAEable constexpr exceptions
The language should interpret a constexpr exception inside a constraint evaluation as making the constraint evaluate to false
. Such a failed overload can now cause other overloads to be selected, making those programs well-defined, and allowing constexpr exceptions to be used to fail constraints. If the failed constraint causes the program to not be valid, the exception's error message is a much better error to report than the instantiation path.
Motivation
Following example shows how currently (C++26) exceptions thrown during evaluation of constraint check results in an error to compile whole program. And what would be result if constexpr exception would result only in SFINAE.
template <typename... Ts> constexpr bool throw_reason(std::format_string<Ts...> f, Ts &&... args) {
throw exception{std::format(f, std::forward<Ts>(args)...)};
}
template <typename T> concept fits_into_four_bytes = sizeof(T) <= 4 || throw_reason("provided type `{}` is larger than 4 bytes!", display_string_of(^^T));
int overload(special_type value); // special match
int overload(fits_into_four_bytes auto value);
int main() {
overload(special_type{}); // totally fine
overload(42); // also fine
overload(new int);
// BEFORE: error: substitution into constraint
// expression resulted in a non-constant expression
// AFTER: can't select any overload from candidates:
// `int overload(special_type value);`
// reason: not matching type
// `int overload(fits_into_four_bytes auto value);`
// reason: provided type `void*` is larger than 4 bytes!
}
Sink example
Similar example as previous, but with additional overload. Notice how previously throwing in constraint check makes the whole overload set unusable. With this paper, it will compile as one would expect.
int overload(special_type value); // special match
int overload(fits_into_four_bytes auto value);
int overload(auto &&); // sink with lowest priority
overload(ptr);
// BEFORE: substitution into constraint expression resulted in a non-constant expression
// AFTER: ok, it selected `int overload(auto &&)` "sink" overload
Iterator inside multiple layers of concepts example
struct hana_super_awesome_container {
// ...
hana_iterator begin();
hana_sentinel end();
};
my_container my_obj{...};
std::ranges::reverse(obj);
// BEFORE: a multiple pages long error messages which
// will cut the important reason due error depth limit.
// AFTER: simple error: provided iterator `hana_iterator` is not decrementable
I'm aware some compilers are getting better in error reporting with concepts. Point is if a library author can provide a custom simple explanation why something is not working it will probably be always better than anything compiler can do automatically.
Design
All outermost constraint are evaluated like they are wrapped in try-catch
which returns false
for any exception thrown from the body of the constraint.
template <template-arguments> concept name = [] {
try {
return <EXPR>;
} catch (...) {
store-error-message(exception.what());
return false;
}
};
This makes programs valid in presence of alternative overload candidate. This is useful to introduce sink overloads and also provide error messages in case there is no alternative overload candidate as a reason why specific constraint check failed or overload candidate failed.
Implementation
Implemented as an extension for constexpr exception in Clang.
Language impact
Allows previously not allowed evaluation of constraints checks (which lead compilation failure).
Proposed changes to wording
13.5.2.3 Atomic constraints [temp.constr.atomic]
Feature test macro
15.11 Predefined macro names [cpp.predefined]
__cpp_constexpr_exceptions 202411L202???L