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

C++ 如何使用SFINAE解决重载函数中的歧义,c++,c++11,sfinae,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(

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

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;
}
我的应用程序使用这两个库,如下所示:

int main(int argc, char **argv)
{
    StupidPoint stupid { 8, 3 };
    translate_point(stupid, 5, 2);
    return EXIT_SUCCESS;
}
但这让GCC(和叮当声)不高兴:


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

您可以为
StupdPoint
提供一个重载:

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


另一个解决方案:

由于
运算符[]
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;
}

对于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&p,int x,int y)->decltype(p[0],void())
{
p[0]+=x;
p[1]+=y;
}
模板
自动转换点(T&p,int x,int y)->decltype(p.x,p.y,void())
{
p、 x+=x;
p、 y+=y;
}
通过这样做,当T=(以
StupidPoint
作为基类的类)时,将调用第二个重载


但是使用简单的重载更容易,正如m.s.

所指出的,在这种情况下,两个重载都受到约束。您不能使用
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
模板
自动转换_点(T&p,intx,inty)->decltype(p.x+=x,p.y+=y,void()){…};
模板
自动转换_点(T&p,int x,int y)->decltype(p[1]+=y,void()){…}//首选检查1,因此不允许运算符[]使用指针

现在,如果
operator[]
返回一个
int&
,这仍然是不明确的。在这种情况下,您需要一种方法对这两个重载进行排序(可能需要一个额外的参数
int
?),或者干脆不允许这种情况。这是一个单独的设计决策。

如果您想优先考虑具有公共
x
/
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
可以假定任何值。

在这种情况下,您希望调用哪一个值?在这种情况下,是第一个版本,因为它(至少在理论上)要快一点。另外,
操作符[]
也有一个非常量重载,返回一个
int&
,我在我的简单测试用例中省略了它。这实际上非常聪明,但是如果你有3个(或更多)选项可供选择,有没有办法让它工作呢?我以前从未见过这个
decltype(void())
,这很好。这对我来说很奇怪,这里是“默认构造一个空”吗?很高兴它能工作。@jaymmer是的,您可以使用模板类来解决重载问题。你想让我在答案中添加一个例子吗?那太好了,我不确定我是否知道你的意思-你所说的与m.s.的建议不同?@Jaymer在答案中添加了一个新的部分。如果你有疑问,请告诉我。
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;
}
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
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);
}
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);
}