How to resolve ambiguity in overloaded functions using SFINAE

感情迁移 提交于 2019-12-03 12:42:16

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.

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

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.

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.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!