The following example from the C++ standard is described as being IFNDR because it violates the "functionally equivalent but not equivalent" rule:
template <unsigned N> constexpr bool Atomic = true;
template <unsigned N> concept C = Atomic<N>;
template <unsigned N> concept Add1 = C<N + 1>;
template <unsigned N> void f2()
requires Add1<2 * N>;
template <unsigned N> int f2()
requires Add1<N * 2> && true;
void h2() {
f2<0>(); // ill-formed, no diagnostic required:
// requires determination of subsumption between atomic constraints that are
// functionally equivalent but not equivalent
}
It seems like what's going on here is that we eventually have to check whether the atomic constraints Atomic<2 * N + 1>
and Atomic<N * 2 + 1>
(formed by repeatedly replacing each concept-id by the result of substituting its template arguments into the definition of the concept, i.e., constraint normalization) are identical. If these two atomic constraints originated from lexically different expressions, they would not be identical, but in this case they originate from a single expression, lexically speaking (i.e. the "Atomic<N>
" appearing at the end of the second line) so, under [temp.constr.atomic]/2, we then have to check whether A<2 * N + 1>
and A<N * 2 + 1>
are equivalent, where A
is a hypothetical template whose declaration might be something like
template <unsigned N> class A;
Since these two template-ids are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.
That brings me to the example I'm actually concerned about:
template <class T>
inline constexpr bool trait1_v = /* ... */;
template <class T>
inline constexpr bool trait2_v = /* ... */;
namespace ns1 {
template <class T>
void foo(T) requires trait1_v<T> {}
}
namespace ns2 {
template <class T>
void foo(T) requires trait1_v<T> && trait2_v<T> {}
}
Suppose the program contains some call to foo
such that ADL finds both foo
overloads, and partial ordering by constraints kicks in. I know that the two occurrences of trait1_v<T>
don't subsume each other because they're not lexically the same expression. Does this mean that the template equivalence test never kicks in, and therefore we never run into "functionally equivalent but not equivalent" so we just get an ambiguous overload resolution instead of IFNDR?
[temp.over.link]/7 says:
If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.
In this case, the two atomic constraints are formed from different expressions, and are not identical regardless of (functional) equivalence of their parameter mappings. Consequently, the latter does not affect the validity or meaning of this program, and [temp.over.link]/7 does not apply. Whether the template test is actually performed is immaterial.
(Also, in this specific example the parameter mappings of all three atomic constraints appearing in the program are equivalent anyway.)