Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve ambiguity in overloaded functions using SFINAE

Tags:

c++

c++11

sfinae

I have an incredibly exciting 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(p.x, p.y, void())
{
    p.x += x;
    p.y += 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 x and y members, and it will also work with tuples/indexable containers where x and y are represented by the first and second element, respectively.

The problem is, another library defines a point class with 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 the following:

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 see why this is happening, but I want to know how to fix this (assuming I can't change the internals of StupidPoint), and, if there is no easy workaround, how I might as a library implementer make this easier to deal with.

like image 774
jaymmer - Reinstate Monica Avatar asked Jul 09 '16 14:07

jaymmer - Reinstate Monica


People also ask

How to fix ambiguous call to overloaded function?

There are two ways to resolve this ambiguity: Typecast char to float. Remove either one of the ambiguity generating functions float or double and add overloaded function with an int type parameter.

Where can an ambiguity occur in overloading a function?

If the compiler can not choose a function amongst two or more overloaded functions, the situation is -” Ambiguity in Function Overloading”. The reason behind the ambiguity in above code is that the floating literals 3.5 and 5.6 are actually treated as double by the compiler.

What is ambiguity in function overloading?

When the compiler is unable to decide which function it should invoke first among the overloaded functions, this situation is known as function overloading ambiguity. The compiler does not run the program if it shows ambiguity error.


2 Answers

If you want to give precedence to the case having public x/y, you can do this:

template<class T>
auto translate_point_impl(int, T &p, int x, int y) -> decltype(p.x, p.y, void())
{
    p.x += x;
    p.y += 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 given by switching the types of the first parameter.


If you have three or more options (says N), you can use a trick based on templates.
Here is the example above once 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(p.x, p.y, void()) {
    p.x += x; p.y += 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 assume any value.

like image 112
skypjack Avatar answered Sep 27 '22 00:09

skypjack


You could provide an overload for StupidPoint:

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

live example


Another solution:

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

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

live example


You could also use a different type-traits based approach 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)
{
    p.x += x;
    p.y += 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;
}

live example

like image 44
m.s. Avatar answered Sep 26 '22 00:09

m.s.