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.
If you want to give precedence to the case having public
x
/y
, you can do this: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:
As you can see, now
N
can assume any value.You could provide an overload for
StupidPoint
:live example
Another solution:
Since
operator[]
is const forStupidPoint
, you can check this in your SFINAE condition:live example
You could also use a different type-traits based approach to select the appropriate
translate_point
function:live example
With SFINAE, I would do something like this:
By doing this, when T = (class with
StupidPoint
as base class), the second overload will be called.But it is easier with a simple overload, as pointed out by m.s.
In this case, both of your overloads are under-constrained. You can't really call the second one with
StupidPoint
, but that isn't observable at the point of overload resolution. If you constrain both properly, you'll remove the ambiguity in this case:Now, if
operator[]
returned anint&
instead, this would still be ambiguous. In that case, you'd need a way to order the two overloads (maybe with an extra argument that is eitherint
or...
?), or simply disallow that case. That's a separate design decision.