At work, I ran into a problem with overload sets. I had something similar to this setup (different types - I was dealing with platform-specific intrinsics):
bool IsCircle(Circle c);
bool IsCircle(Rectangle r);
template<typename F, typename G>
bool Process(Circle c, Rectangle r, F &&callback1, G &&callback2)
{
return callback1(c) && callback2(r);
}
int main()
{
// ...
Process(circle, rectangle, IsCircle, IsCircle);
}
Unfortunately, C++ cannot deduce the correct overload to use here. We'll get an error that says something similar to no matching overload found
. This is because C++ unfortunately doesn't have overload sets (at the time of this writing - C++23).
There are some other methods to work around overload sets such as this one, but I wasn't able to get it to work. I tried this:
#include <functional>
#define OVERLOAD_MF(fun) \
[](auto&& self, auto&&... args) -> decltype(auto) { \
return (std::forward<decltype(self)>(self).fun)( \
std::forward<decltype(args)>(args)...); \
}
struct Circle {
int a;
};
struct Rectangle {
int b;
};
struct OverloadSet {
bool IsCircle(Circle c);
bool IsCircle(Rectangle r);
};
template<typename F, typename G>
bool Process(Circle c, Rectangle r, F &&callback1, G &&callback2)
{
return callback1(c) && callback2(r);
}
int main()
{
Circle circle;
Rectangle rectangle;
OverloadSet o;
auto c = OVERLOAD_MF(OverloadSet::IsCircle);
auto r = OVERLOAD_MF(OverloadSet::IsCircle);
std::invoke(Process, circle, rectangle, c, r);
}
I got this error:
<source>(41): error C2672: 'std::invoke': no matching overloaded function found
Z:/compilers/msvc/14.41.33923-14.41.33923.0/include\type_traits(1695): note: could be 'unknown-type std::invoke(_Callable &&,_Ty1 &&,_Types2 ...) noexcept(<expr>)'
<source>(41): note: 'unknown-type std::invoke(_Callable &&,_Ty1 &&,_Types2 ...) noexcept(<expr>)': could not deduce template argument for '_Callable'
Z:/compilers/msvc/14.41.33923-14.41.33923.0/include\type_traits(1689): note: or 'unknown-type std::invoke(_Callable &&) noexcept(<expr>)'
<source>(41): note: 'unknown-type std::invoke(_Callable &&) noexcept(<expr>)': expects 1 arguments - 5 provided
Compiler returned: 2
This method doesn't work for my case, unfortunately (or maybe I'm not enough of a C++ magician to get this way to work).
Workaround
We can use if constexpr
and templates to work around this issue. Both of the functions have the same name and they take the same amount of parameters. This means we can turn the type into a template parameter and use if constexpr
to decide what to do with the types passed in:
template<typename T>
bool IsCircle(T shape) {
if constexpr (std::is_same_v<T, Circle>) {
return true;
} else if constexpr(std::is_same_v<T, Rectangle>) {
return true;
} else {
static_assert("type not supported");
}
}
Now, at the callsite to Process
we can specify the template parameter:
template<typename F, typename G>
bool Process(Circle c, Rectangle r, F &&callback1, G &&callback2)
{
return callback1(c) && callback2(r);
}
int main()
{
Circle circle;
Rectangle rectangle;
Process(circle, rectangle, IsCircle<Circle>, IsCircle<Rectangle>);
}
This will satisfy the overload resolution and make it work. It's not ideal - you have to write out all the types instead of having the compiler deducing it - but it still looks pretty clean, and we get to avoid the macro.