The following class has four overloads of function f. When T!=int, all overloads have unique parameter lists, but when T=int, all overloads have the same parameter lists. To make it compile even when int and T are identical, I tried to disable three of the functions using a concept. Despite that, the compiler generates an error because the three disabled overloads have identical signatures (https://godbolt.org/z/8aeWdrTs6):
#include <concepts>
template <class T>
struct S {
void f(T, T) { /*...*/ }
void f(T, int) requires (!std::same_as<int, T>) { /*...*/ }
void f(int, T) requires (!std::same_as<int, T>) { /*...*/ }
void f(int, int) requires (!std::same_as<int, T>) { /*...*/ }
};
int main() {
S<int> s;
}
clang says error: multiple overloads of 'f' instantiate to the same signature 'void (int, int)', and gcc says something similar.
I can work around this by introducing artificial syntactic differences in the constraints, e.g. (https://godbolt.org/z/TajvPYPo4):
void f(int, T) requires (!std::same_as<int, T> && true) { /*...*/ }
void f(int, int) requires (!std::same_as<int, T> && true && true) { /*...*/ }
For the "why" ? I don't know if it is required by the standard or if it is even intended.
As a workaround, you can add a template specialization for the int type so that the conflicting overloads do not appear at all.
Moreover, it removes the need of the requires clauses completely:
template <class T>
struct S
{
void f(T, T) { /*...*/ }
void f(T, int) { /*...*/ }
void f(int, T) { /*...*/ }
void f(int, int) { /*...*/ }
};
template <>
struct S<int>
{
void f(int, int) { /*...*/ }
};
Live demo
Unelegant and crude, but we could also use SFINAE tricks to disable certain overloads:
#include <concepts>
#include <string>
template <class T>
struct S {
void f(T, T) { /*...*/ }
template<typename E = T>
auto f(E, int) -> std::enable_if_t<std::is_convertible_v<E, T> && !std::is_same_v<int, T>, void> { /*...*/ }
template<typename E = T>
auto f(int, E) -> std::enable_if_t<std::is_convertible_v<E, T> && !std::is_same_v<int, T>, void> { /*...*/ }
template<typename E = T>
auto f(int, int) -> std::enable_if_t<std::is_convertible_v<E, T> && !std::is_same_v<int, T>, void> { /*...*/ }
};
int main() {
S<int> s;
s.f(0, 0);
S<std::string> sd;
sd.f("hello", "world");
sd.f("hello", 0);
sd.f(0, "world");
sd.f(0, 0);
}
This works because the overloads using E are templates and are not instantiated until overload.
https://godbolt.org/z/Pdq3MYPnc
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