Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are disabled overloads required to be unique?

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) { /*...*/ }
  1. Why is the language specified so that disabled overloads must have unique signatures? I understand that all viable overloads must be unique because the compiler must know which one to invoke. But non-viable overloads are not invoked, so requiring uniqueness seems unnecessary in this case.
  2. Is there a better/nicer/more elegant workaround, for example, one that is easier to scale when there are many overloads?
like image 613
Sven Sandberg Avatar asked Dec 04 '25 10:12

Sven Sandberg


2 Answers

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

like image 58
Fareanor Avatar answered Dec 07 '25 00:12

Fareanor


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

like image 45
Captain Hatteras Avatar answered Dec 07 '25 00:12

Captain Hatteras



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!