How to resolve ambiguity in overloaded functions with SFINAE

I have an incredibly interesting library that can translate points: it should work with any point types

template<class T> auto translate_point(T &p, int x, int y) -> decltype(px, py, void()) { px += x; py += y; } template<class T> auto translate_point(T &p, int x, int y) -> decltype(p[0], void()) { p[0] += x; p[1] += y; } 

translate_point will work with points that have public members x and y , and it will also work with tuples / indexable containers, where x and y represented by the first and second elements, respectively.

The problem is that another library defines a class of points with the public x and y , but also allows indexing:

 struct StupidPoint { int x, y; int operator[](int i) const { if(i == 0) return x; else if(i == 1) return y; else throw "you're terrible"; } }; 

My application using both libraries is as follows:

 int main(int argc, char **argv) { StupidPoint stupid { 8, 3 }; translate_point(stupid, 5, 2); return EXIT_SUCCESS; } 

but this makes GCC (and clang) unhappy:

 error: call of overloaded 'translate_point(StupidPoint&, int, int)' is ambiguous 

Now I can understand why this is happening, but I want to know how to fix it (assuming that I can not change the internals of StupidPoint), and if there is no easy way around it, how can I implement the library to deal with this more easily.

+7
c ++ c ++ 11 sfinae
source share
4 answers

If you want to give priority to a case that has public x / y , you can do this:

 template<class T> auto translate_point_impl(int, T &p, int x, int y) -> decltype(px, py, void()) { px += x; py += y; } template<class T> auto translate_point_impl(char, T &p, int x, int y) -> decltype(p[0], void()) { p[0] += x; p[1] += y; } template<class T> void translate_point(T &p, int x, int y) { translate_point_impl(0, p, x, y); } 

It goes without saying that the opposite configuration is specified by switching the types of the first parameter.


If you have three or more options (says N ), you can use a template-based trick.
Below is the example above, as soon as he switched to such a structure:

 template<std::size_t N> struct choice: choice<N-1> {}; template<> struct choice<0> {}; template<class T> auto translate_point_impl(choice<1>, T &p, int x, int y) -> decltype(px, py, void()) { px += x; py += y; } template<class T> auto translate_point_impl(choice<0>, T &p, int x, int y) -> decltype(p[0], void()) { p[0] += x; p[1] += y; } template<class T> void translate_point(T &p, int x, int y) { // use choice<N> as first argument translate_point_impl(choice<1>{}, p, x, y); } 

As you can see, now N can take any value.

+3
source share

You can provide overload for StupidPoint :

 auto translate_point(StupidPoint &p, int x, int y) { px += x; py += y; } 

living example


Another solution:

Since operator[] is const for StupidPoint , you can check this in your SFINAE state:

 template<class T> auto translate_point(T &p, int x, int y) -> decltype(p[0] += 0, void()) { p[0] += x; p[1] += y; } 

living example


You can also use a different approach based on the types of objects to select the appropriate translate_point function:

 template<typename T, typename = void> struct has_x_y : std::false_type { }; template<typename T> struct has_x_y<T, decltype(std::declval<T>().x, std::declval<T>().y, void())> : std::true_type { }; template<typename T, typename = void> struct has_index : std::false_type { }; template<typename T> struct has_index<T, decltype(std::declval<T>().operator[](0), void())> : std::true_type { }; template<class T> std::enable_if_t<has_x_y<T>::value> translate_point(T &p, int x, int y) { px += x; py += y; } template<class T> std::enable_if_t<!has_x_y<T>::value && has_index<T>::value> translate_point(T &p, int x, int y) { p[0] += x; p[1] += y; } 

living example

+7
source share

In this case, both of your overloads are limited. You cannot call the second with StupidPoint , but this is not observed at the point of overload resolution. If you hold back the correctness, you will eliminate the ambiguity in this case:

 template<class T> auto translate_point(T &p, int x, int y) -> decltype(px += x, py += y, void()) { ... }; template<class T> auto translate_point(T &p, int x, int y) -> decltype(p[1] += y, void()) { ... } // prefer checking 1, so you don't allow an operator[] that takes a pointer 

Now, if operator[] returned int& , it would still be ambiguous. In this case, you will need a way to order two overloads (perhaps with an additional argument, which is either int or ... ?), Or just ban this case. This is a separate design decision.

+2
source share

With SFINAE, I would do something like this:

 template<class T, bool> auto translate_point(T &p, int x, int y) -> decltype(p[0], void()) { p[0] += x; p[1] += y; } template<class T, bool = std::is_base_of<StupidPoint, T>::value> auto translate_point(T &p, int x, int y) -> decltype(px, py, void()) { px += x; py += y; } 

Thus, when T = (a class with StupidPoint as the base class), a second overload is called.

But easier with simple overload as ms indicated

+1
source share

All Articles