I have an incredibly exciting library that can translate points: it should work with any point types
template
auto translate_point(T &p, int x
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:
template
auto translate_point(T &p, int x, int y) -> decltype(p.x += x, p.y += y, void()) { ... };
template
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 an int& 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 either int or ...?), or simply disallow that case. That's a separate design decision.