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<class T>
auto translate_point(T &p, int x, int y) -> decltype(p.x += x, p.y += 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 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.
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.
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(p.x, p.y, void())
{
p.x += x;
p.y += y;
}
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.
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