This program struct B { const bool b = true; }; template <typename T> concept C = T::b; static_assert( !C<B> ); is accepted in Clang. And I think correctly accepted because of substitution failure per https://eel.is/c++draft/temp.constr.constr#temp.constr.op-5 > [...] Furthermore, if substitution to determine whether an atomic constraint is satisfied ([temp.constr.atomic]) encounters a substitution failure, the constraint is not satisfied, regardless of the presence of a negation operator. But GCC emits a weird error (at least the error text must be adjusted): error: '*(B*)(void)0' is not a constant expression Online demo: https://gcc.godbolt.org/z/xKf13cb6v
It seems we're issuing a hard error during satisfaction here because substitution failure didn't occur, and the substituted constraint is non-constant which renders the program ill-formed as per [temp.constr.atomic]/3: > If substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion is performed if necessary, and E shall be a constant expression of type bool. One might argue substitution failure should occur for T::b with T=B, but this seems to be permitted by [expr.prim.id.general]/3 since constraint-expressions are unevaluated contexts. Note that if we use B::b directly in the concept definition then Clang also rejects the program due to non-constant satisfaction rule: struct B { const bool b = true; }; template <typename T> concept C = B::b; static_assert( !C<B> ); error: substitution into constraint expression resulted in a non-constant expression So GCC might be correct to reject the original program.
Just some observations: There was a similar discussion in https://stackoverflow.com/questions/75443227/static-assert-on-an-ill-formed-no-diagnostic-required-expression Note that if we use anything else which leads to the ill-formed expression for T::b itself, e.g. B&, B*, int - gcc compiles without errors: struct B { const bool b = true; }; template <typename T> concept C = T::b; static_assert( !C<B*> ); static_assert( !C<B&> ); static_assert( !C<int> ); https://gcc.godbolt.org/z/9c6YW1vWe I would expect (at least as a user) the same behaviour for static_assert(!C<B>) too, in that case. So gcc seems to consider T::b (with T=b) in this context as a well-formed expression, but not a constant expression. Looks strange.