Fixes: https://github.com/llvm/llvm-project/issues/45563
template<class T> concept True = true; template <class T> concept C1 = requires (T) { requires True<typename T::value> || True<T>; }; template <class T> constexpr bool foo() requires True<typename T::value> || True<T> { return true; } static_assert(C1<double>); // Previously failed due to SFINAE error static_assert(foo<int>()); // but this works fine.
The issue here is the discrepancy between how a nested requirement is evaluated Vs how a non-nested requirement is evaluated.
In non-nested cases, we explicitly handle Binary operators transforming, substituting and evaluating each branch separately while for a nested requirement we substituting the complete the constraint expr leading to SFINAE failure for the complete expression. Restricting the SFINAE failure to the atomic constraint and marking the constraint only as "not-satisfied" solves the issue.
Substitution of template arguments into a nested-requirement does not result in substitution into the constraint-expression other than as specified in [temp.constr.constr].
https://eel.is/c++draft/expr.prim.req#nested-example-1
[Example 1: template<typename U> concept C = sizeof(U) == 1; template<typename T> concept D = requires (T t) { requires C<decltype (+t)>; }; D<T> is satisfied if sizeof(decltype (+t)) == 1 ([temp.constr.atomic]). — end example]
To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied.
Therefore in case of substitution failure in a constraint expression, the corresponding atomic constraint should be failed and not the entire constraint expression/nested requires expression.
TODO: I want to properly propagate the substitution failure.
Does this really belong here instead of as a part of the ConceptSpecializationDecl?