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.
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:
and your usage becomes:
The arguments do not exactly match either call:
foo
isFoo&
, notconst Foo&
v.crbegin()
returnvector::const_reverse_iterator
which is just a different instantiation of the samestd::reverse_iterator
template asvector::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:I.e. there is a viable candidate converting
std::reverse_iterator<T>
tostd::reverse_iterator<U>
between anyT-U
pairs. In this case forT=vector::const_iterator
,U=vector::iterator
. But of course the template instantiation fails later because it cannot convertconst int*
toint*
.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:
Solution
As pointed in the comments by @Eljay, forcing
const Foo&
at the call site is one option, one can use C++17std::as_const
: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:It works because now it is a better (exact) match for non-
const
Foo
s than the still consideredvector::reverse_iterator
which would not compile anyway.