当重载方法在实参中使用反向迭代器时,会产生歧义调用



我试图写一个重载的方法,只有当它被调用的对象是非const的,并且在参数中传递的迭代器是非const的时候才返回非const的结果。(可以把它想象成标准方法begin()begin() const,它们附加了一个迭代器参数。)

我为普通迭代器做了一个没有问题的版本。然而,由于某种原因,当我试图对反向迭代器做同样的事情时,我得到了一个关于模糊函数调用的编译错误。

这里有一个最小的示例:

#include <vector>
class Foo
{
public:
void bar(std::vector<int>::iterator x) {}
void bar(std::vector<int>::const_iterator x) const {}
void baz(std::vector<int>::reverse_iterator x) {}
void baz(std::vector<int>::const_reverse_iterator x) const {}
};
int main()
{
std::vector<int> v;
Foo foo;
foo.bar(v.cbegin());  // OK
foo.baz(v.crbegin()); // ambiguous
}

由于某种原因,如果我从第二个方法baz中删除const,它就会编译。它也可以在c++ 20中工作,但目前我不能使用该版本。

现场演示

如何使函数baz以类似于函数bar的方式工作?

重载解析规则和SFINAE的乐趣。

方法等价于自由函数:

void bbaz(Foo&,std::vector<int>::reverse_iterator){}
void bbaz(const Foo&,std::vector<int>::const_reverse_iterator){}

和你的用法变成:

int main()
{
std::vector<int> v;
Foo foo;
bbaz(foo,v.crbegin());
}

参数不完全匹配两个调用:

  • fooFoo&,不是const Foo&
  • v.crbegin()返回vector::const_reverse_iterator,其中只是与vector::reverse_iterator相同的std::reverse_iterator模板的不同实例化
    • reverse_iterator->std::reverse_iterator<vector::iterator>
    • const_reverse_iterator->std::reverse_iterator<vector::const_iterator>

歧义原因

现在,问题是std::reverse_iterator的函数在c++ 20之前不是sfinae友好的:

template< class U >
std::reverse_iterator( const std::reverse_iterator<U>& other );

。在任何T-U对之间都有一个可行的候选将std::reverse_iterator<T>转换为std::reverse_iterator<U>。在本例中,对于T=vector::const_iterator,U=vector::iterator。当然,模板实例化之后会失败,因为它无法将const int*转换为int*

由于这发生在模板函数体中,而不是签名中,因此对于SFINAE来说为时已晚,重载将其视为可行的候选函数,因此产生了歧义,因为两个调用都需要一个隐式转换-尽管只有第二个可以编译。

这在这些答案中都有解释,使得这个问题基本上是那个问题的副本,但是如果没有解释就把它标记为这样是很残忍的,因为我无法在评论中解释。

c++ 20修复了这个遗漏,并且SFINAEs:

此重载仅在U与Iterstd::convertible_to<const U&, Iter>不同的类型时参与重载解析(从c++ 20开始)

解决方案正如@Eljay在评论中指出的那样,在调用现场强制const Foo&是一种选择,可以使用c++ 17std::as_const:

#include <utility>
std::as_const(foo).baz(v.crbegin());

在定义时修复此问题更棘手,您可以使用SFINAE实际强制这些过载,但这可能是一个麻烦。@fabian的解决方案是添加第三个没有const方法限定符的重载,对我来说似乎是最简单的:

void Foo::baz(std::vector<int>::const_reverse_iterator x) { 
return std::as_const(*this).baz(x); 
}

它可以工作,因为现在它是一个更好的(精确)匹配非constFoo,而不是仍然被认为无法编译的vector::reverse_iterator