(all tests are performed on Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x86)
consider this minimal example:
struct myString
{
operator const char *( ) const { return &dummy; }
char& operator[]( unsigned int ) { return dummy; }
const char& operator[]( unsigned int ) const { return dummy; }
char dummy;
};
int main()
{
myString str;
const char myChar = 'a';
if( str[(int) 0] == myChar ) return 0; //error, multiple valid overloads
}
according to overload resolution rules (from cppreference)
F1 is determined to be a better function than F2 if implicit conversions for all arguments of F1 are not worse than the implicit conversions for all arguments of F2, and
1) there is at least one argument of F1 whose implicit conversion is better than the corresponding implicit conversion for that argument of F2
2) or. if not that, (only in context of non-class initialization by conversion), the standard conversion sequence from the return type of F1 to the type being initialized is better than the standard conversion sequence from the return type of F2
char& operator[]( unsigned int )
should be better, according to 1).
Of the two arguments (this = myString) do not need to be converted at all while operator const char *( ) const
converts it to const char* and const char& operator[]( unsigned int ) const
converts it to const myString, therefore there is one argument without any implicit conversion, which happens to be the best conversion
However my compiler yells the following error:
1> [///]\sandbox\sandbox\sandbox.cpp(29): error C2666: 'myString::operator []': 3 overloads have similar conversions
1> [///]\sandbox\sandbox\sandbox.cpp(19): note: could be 'const char &myString::operator [](unsigned int) const'
1> [///]\sandbox\sandbox\sandbox.cpp(18): note: or 'char &myString::operator [](unsigned int)'
1> [///]\sandbox\sandbox\sandbox.cpp(29): note: while trying to match the argument list '(myString, int)'
also note that using if( str[0u] == myChar ) return 0;
or removing operator const char *( ) const
resolve the error
why is there an error here and what am I getting wrong about overload resolution rules?
edit: it might be a visual C++ bug in this version, any definitive confirmation on this?
Here's a minified version of the problem, that reproduces on all compilers I threw at it.
#include <stddef.h>
struct myString
{
operator char *( );
char& operator[]( unsigned ptrdiff_t );
};
int main()
{
myString str;
if( str[(ptrdiff_t) 0] == 'a' ) return 0; //error, multiple valid overloads
}
Basically, you have two candidate functions to get the char
for bool operator==(char,char)
: [over.match.oper]/3
char& myString::operator[]( unsigned ptrdiff_t )
([over.match.oper]/3.1 => [over.sub])char& operator[]( char*, ptrdiff_t)
([over.match.oper]/3.3 => [over.built]/14)Note that if myString::operator[]
took a ptrdiff_t
instead of an unsigned ptrdiff_t
, then it would have hidden the built-in operator per [over.built]/1. So if all you want to do is avoid issues like this, simply ensure any operator[]
overload that takes an integral value, takes a ptrdiff_t
.
I'll skip the viability check [over.match.viable], and go straight to conversion ranking.
char& myString::operator[]( unsigned ptrdiff_t )
For overloading, this is considered to have a leading implicit object paramter, so the signature to be matched is
(myString&, unsigned ptrdiff_t)
myString&
=> myString&
Standard conversion sequence: Identity (Rank: Exact match) - directly bound reference
ptrdiff_t
=> unsigned ptrdiff_t
Standard conversion sequence: Lvalue Transformation -> Integral conversion (Rank: Conversion) - signed lvalue to unsigned prvalue
char& operator[]( char*, ptrdiff_t)
myString&
=> char*
User-defined conversion sequence: Identity + operator char*(myString&)
Note that per [over.match.oper]/7 we don't get a second standard conversion sequence.
ptrdiff_t
=> ptrdiff_t
Standard conversion sequence: Identity (Rank: Exact match)
Standard Conversion Sequence is better than User-defined conversion sequence ([over.ics.rank]/2.1)
Rank Conversion is worse than Rank Exact Match ([over.ics.rank]/3.2.2)
We cannot satisfy the requirement
if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2)
so neither function is a better function.
Hence, per [over.match.best]/2 it's ambiguous.
Well, the easiest solution is to never let the parameter to an operator[]
overload be something that could be converted to from ptrdiff_t
by something other than an Exact Match-ranked conversion. Looking at the conversions table that appears to mean that you should always declare your operator[]
member function as X& T::operator[]( ptrdiff_t )
. That covers the usual use-case of "Act like an array". As noted above, using precisely ptrdiff_t
will suppress even searching for an operator T*
candidate by taking the built-in subscript operator off the table.
The other option is to not have both T1 operator[]
and operator T2*
defined for the class, where T1
and T2
may both fulfill the same parameter of a (possibly implicit) function call. That covers cases where you are using operator[]
for clever syntactic things, and end up with things like T T::operator[](X)
. If X::operator ptrdiff_t()
exists for example, and so does T::operator T*()
, then you're ambiguous again.
The only use-case for T::operator T*()
I can imagine is if you want your type to implicitly convert into a pointer to itself, like a function. Don't do that...