当重载方法在参数中采用反向迭代器时出现不明确的调用

NO_*_*AME 5 c++ overloading ambiguous reverse-iterator c++17

我正在尝试编写一个重载方法,仅当调用它的对象是非常量并且参数中传递的迭代器是非常量时才返回非常量结果。(可以将其视为标准方法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
}
Run Code Online (Sandbox Code Playgroud)

由于某种原因,如果我const从第二种方法中删除它,它就会编译baz。它也适用于 C++20,但目前我无法使用该版本。

现场演示

如何使该函数baz以与该函数类似的方式工作bar

Qui*_*mby 7

哦,超载解析规则和 SFINAE 的乐趣。

这些方法相当于自由函数:

void bbaz(Foo&,std::vector<int>::reverse_iterator){}
void bbaz(const Foo&,std::vector<int>::const_reverse_iterator){}
Run Code Online (Sandbox Code Playgroud)

你的用法变成:

int main()
{
    std::vector<int> v;
    Foo foo;
    bbaz(foo,v.crbegin());
}
Run Code Online (Sandbox Code Playgroud)

参数与任一调用都不完全匹配:

  • foo不是Foo&const Foo&
  • v.crbegin()returnvector::const_reverse_iterator只是与 相同std::reverse_iterator模板vector::reverse_iterator的不同实例。
    • reverse_iterator->std::reverse_iterator<vector::iterator>
    • const_reverse_iterator-> std::reverse_iterator<vector::const_iterator>

歧义的原因

现在的问题是 的std::reverse_iteratorctor 在 C++20 之前对 SFINAE 不友好:

template< class U >
std::reverse_iterator( const std::reverse_iterator<U>& other );
Run Code Online (Sandbox Code Playgroud)

即有一个可行的候选者可以std::reverse_iterator<T>std::reverse_iterator<U>任何T-U对之间进行转换。在这种情况下,对于T=vector::const_iterator, U=vector::iterator. 但当然,模板实例化稍后会失败,因为它无法转换const int*int*.

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

这些答案对此进行了解释,使得这个问题本质上是该问题的重复,但恕我直言,在没有解释的情况下将其标记为这样是残酷的,我无法将其纳入评论。

C++20 修复了此遗漏以及该 ctor - cppreference的 SFINAE :

仅当 U 与 U 的类型不同且Iterstd::convertible_to<const U&, Iter>建模时,此重载才参与重载决策 (C++20 起)

解决方案

正如 @Eljay 的评论所指出的,const Foo&在调用站点强制是一种选择,可以使用 C++17 std::as_const

#include <utility>
std::as_const(foo).baz(v.crbegin());
Run Code Online (Sandbox Code Playgroud)

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

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

const Foo它之所以有效,是因为现在它比仍然考虑的非 s 更好(精确)匹配,vector::reverse_iterator无论如何都不会编译。