如何使用SFINAE解决重载函数的歧义

时间:2016-07-09 14:58:26

标签: c++ c++11 sfinae

我有一个非常令人兴奋的库,可以翻译点:它应该适用于任何点类型

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适用于拥有公开xy成员的积分,它也适用于xy的元组/可索引容器分别由第一个和第二个元素表示。

问题是,另一个库定义了一个包含公共xy的点类,但也允许建立索引:

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";
    }

};

使用这两个库的我的应用程序如下:

int main(int argc, char **argv)
{
    StupidPoint stupid { 8, 3 };
    translate_point(stupid, 5, 2);
    return EXIT_SUCCESS;
}

但是这让GCC(和clang)感到不快:

error: call of overloaded ‘translate_point(StupidPoint&, int, int)’ is ambiguous

现在我可以看到为什么会发生这种情况,但我想知道如何解决这个问题(假设我无法更改StupidPoint的内部结构),并且,如果没有简单的解决方法,我将如何作为库实现者使这更容易处理。

4 个答案:

答案 0 :(得分:8)

您可以为StupidPoint提供重载:

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

live example

另一种解决方案:

由于operator[]StupidPoint的常量,因此您可以在SFINAE条件下进行检查:

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

您还可以使用不同的基于类型特征的方法来选择适当的translate_point函数:

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

答案 1 :(得分:5)

如果您想优先考虑公开y / 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); } 的情况,您可以这样做:

N

不言而喻,通过切换第一个参数的类型给出了相反的配置。

如果您有三个或更多选项(说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); } ),您可以使用基于模板的技巧 以上是一旦切换到这样的结构上面的例子:

N

如您所见,现在require可以承担任何价值。

答案 2 :(得分:2)

在这种情况下,两个重载都受到限制。你不能真正用windowSz = 3; h = ones(windowSz, 1); tallard(:, 3) = filter(h, 1, tallard(:, 2)); 调用第二个,但是在重载解析时这是不可观察的。如果你正确约束两者,你将在这种情况下消除歧义:

StupidPoint

现在,如果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 返回了operator[],那么这仍然是不明确的。在这种情况下,你需要一种方法来订购两个重载(可能有一个额外的参数,int&int?),或者只是不允许这种情况。这是一个单独的设计决定。

答案 3 :(得分:1)

使用SFINAE,我会做这样的事情:

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;
}

通过这样做,当T =(以StupidPoint为基类的类)时,将调用第二个重载。

但是,如 m.s。

所指出的那样,使用简单的重载会更容易。