This is an example from the C++20 Standard (ISO/IEC 14882:2020), Section 13.5.4 ([temp.constr.normal]), Paragraph 1 (emphasis mine):
The normal form of a concept-id C<A1 , A2 , ..., An> is the normal form of the constraint-expression of C, after substituting A1 , A2 , ..., An for C’s respective template parameters in the parameter mappings in each atomic constraint. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.
template<typename T> concept A = T::value || true;
template<typename U> concept B = A<U*>;
template<typename V> concept C = B<V&>;
Normalization of B’s constraint-expression is valid and results in
T::value(with the mappingT -> U*) Vtrue(with an empty mapping), despite the expressionT::valuebeing ill-formed for a pointer typeT. Normalization of C’s constraint-expression results in the program being ill-formed, because it would form the invalid typeV&*in the parameter mapping.
I understand that C makes a program ill-formed (and why). However, it is not clear to me if B would result in the program being ill-formed or not. The text states that B's normalization is valid, but at the same time it states that the expression T::value is ill-formed due to that pointer type (which I understand). Does it mean that only the normalization part of the process is valid but the program itself is ill-formed in a later stage when checking T::value? Or is the program valid in any case and the check of T::value is skipped/avoided somehow?
I checked with Godbolt's Compiler Explorer and both GCC and Clang seem to be fine with this. Nevertheless, since the Standard says "no diagnostic is required", this does not help much.
The concept B is valid, as you can pass a pointer to the concept A. Inside A itself the pointer cannot access ::value but this, according to the spec, [temp.constr.atomic], would not be considered as an error but rather as false, then the || true on concept A would make the entire expression true.
Note that if we pass int& to concept B then our code would be IFNDR, as B would try to pass to A an invalid type (int&*).
The concept C is IFNDR as is, since it passes a reference to B, that tries to pass a pointer to this reference to A, and again a V&* type is invalid.
Trying to evaluate an ill-formed-no-diagnostic-required expression using static_assert will not necessarily help in answering the question whether the expression is valid or not, as the compiler is not required to fail a static_assert on an ill-formed-no-diagnostic-required expression.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With