我试图写一个重载的方法,只有当它被调用的对象是非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());
}
参数不完全匹配两个调用:
foo
是Foo&
,不是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与
Iter
和std::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);
}
它可以工作,因为现在它是一个更好的(精确)匹配非const
的Foo
,而不是仍然被认为无法编译的vector::reverse_iterator
。