This works and outputs "1", because the function's constraints are partially ordered and the most constrained overload wins:
template<class T>
struct B {
int f() requires std::same_as<T, int> {
return 0;
}
int f() requires (std::same_as<T, int> && !std::same_as<T, char>) {
return 1;
}
};
int main() {
std::cout << B<int>{}.f();
}
This works as well, because explicit instantiation doesn't instantiate member functions with unsatisfied constraints:
template<class T>
struct B {
int f() requires std::same_as<T, int> {
return 0;
}
int f() requires (!std::same_as<T, int>) {
return 1;
}
};
template struct B<int>;
So what should this do?
template<class T>
struct B {
int f() requires std::same_as<T, int> {
return 0;
}
int f() requires (std::same_as<T, int> && !std::same_as<T, char>) {
return 1;
}
};
template struct B<int>;
Currently this crashes trunk gcc because it compiles two functions with the same mangling. I think it would make sense to compile only the second function, so that the behavior is consistent with the regular overload resolution. Does anything in the standard handle this edge case?
C++20 recognizes that there can be different spellings of the same effective requirements. So the standard defines two concepts: "equivalent" and "functionally equivalent".
True "equivalence" is based on satisfying the ODR (one-definition rule):
Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression.
There's more to it, but that's not an issue here.
Equivalence for template heads includes that all constraint expressions are equivalent (template headers include constraints).
Functional equivalence is (usually) about the results of expressions being equal. For template heads, two template heads that are not ODR equivalent can be functionally equivalent:
Two template-heads are functionally equivalent if they accept and are satisfied by ([temp.constr.constr]) the same set of template argument lists.
That's based in part on the validity of the constraint expressions.
Your two template heads in versions 1 and 3 are not ODR equivalent, but they are functionally equivalent, as they both accept the same template parameters. And the behavior of that code will be different from its behavior if they were ODR equivalent. Therefore, this passage kicks in:
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.
As such, all of the compilers are equally right because your code is wrong. Obviously a compiler shouldn't straight-up crash (and that should be submitted as a bug), but "ill-formed, no diagnostic required" often carries with it unforeseen consequences.