简体   繁体   中英

Ambiguous call when overloaded methods take reverse iterators in arguments

I'm trying to write an overloaded method that returns non-const result only when both the object on which it is called is non-const and iterator passed in the argument is non-const. (Think of it like the standard methods begin() and begin() const that additionally take an iterator argument.)

I made a version for normal iterators with no problems. However, for some reason, when I'm trying to do the same for reverse iterators, I get a compilation error about an ambiguous function call.

Here's a minimal example:

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

For some reason it compiles if I remove const from the second method baz . It also works in C++20 but currently I cannot use that version.

live demo

How can I make the function baz work in analogous way to the function bar ?

Oh the joys of overloading resolution rules and SFINAE.

The methods are equivalent to free functions:

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

and your usage becomes:

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

The arguments do not exactly match either call:

  • foo is Foo& , not const Foo&
  • v.crbegin() return vector::const_reverse_iterator which is just a different instantiation of the same std::reverse_iterator template as vector::reverse_iterator .
    • reverse_iterator -> std::reverse_iterator<vector::iterator>
    • const_reverse_iterator -> std::reverse_iterator<vector::const_iterator>

Cause of ambiguity

Now, the issue is that std::reverse_iterator 's ctor is not SFINAE-friendly until C++20:

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

Ie there is a viable candidate converting std::reverse_iterator<T> to std::reverse_iterator<U> between any TU pairs. In this case for T=vector::const_iterator , U=vector::iterator . But of course the template instantiation fails later because it cannot convert const int* to int* .

Since that happens in the template function's body, not the signature, it is too late for SFINAE and overloading considers it a viable candidate function, hence the ambiguity since both calls require one implicit conversion - although only the second one would compile.

This is explained in these answers , making this one essentially a duplicate of that question but it would be IMHO cruel to mark it as such without an explanation which I cannot fit into a comment.

C++20 fixes this omission and SFINAEs that ctor - cppreference :

This overload participates in overload resolution only if U is not the same type as Iter and std::convertible_to<const U&, Iter> is modeled (since C++20)

Solution

As pointed in the comments by @Eljay, forcing const Foo& at the call site is one option, one can use C++17 std::as_const :

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

Fixing this at definition is more tricky, you could use SFINAE to actually force these overloads but that might be a hassle. @fabian 's solution with adding a third overload without const method qualifier seems easiest to me:

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

It works because now it is a better (exact) match for non- const Foo s than the still considered vector::reverse_iterator which would not compile anyway.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM