Wake up Brazil
Wake up Brazil

Reputation: 3411

How come an user defined conversion is a better match than a standard integer conversion?

You can find the text below in Appendix B of the book "C++ Templates The Complete Guide" by David Vandevoorde and Nicolai Josuttis.

B.2 Simplified Overload Resolution

Given this first principle, we are left with specifying how well a given argument matches the corresponding parameter of a viable candidate. As a first approximation we can rank the possible matches as follows (from best to worst):

  • Perfect match. The parameter has the type of the expression, or it has a type that is a reference to the type of the expression (possibly with added const and/or volatile qualifiers).
  • Match with minor adjustments. This includes, for example, the decay of an array variable to a pointer to its first element, or the addition of const to match an argument of type int** to a parameter of type int const* const*.
  • Match with promotion. Promotion is a kind of implicit conversion that includes the conversion of small integral types (such as bool, char, short, and sometimes enumerations) to int, unsigned int, long or unsigned long, and the conversion of float to double.
  • Match with standard conversions only. This includes any sort of standard conversion (such as int to float) but excludes the implicit call to a conversion operator or a converting constructor.
  • Match with user-defined conversions. This allows any kind of implicit conversion.
  • Match with ellipsis. An ellipsis parameter can match almost any type (but non-POD class types result in undefined behavior).

A few pages later the book shows the following example and text (emphasis mine):

class BadString {
    public:
    BadString(char const*);
    ...
    // character access through subscripting:
    char& operator[] (size_t); // (1)
    char const& operator[] (size_t) const;
    // implicit conversion to null-terminated byte string:
    operator char* (); // (2)
    operator char const* ();
    ...
};
int main()
{
    BadString str("correkt");
    str[5] = 'c'; // possibly an overload resolution ambiguity!
}

At first, nothing seems ambiguous about the expression str[5]. The subscript operator at (1) seems like a perfect match. However, it is not quite perfect because the argument 5 has type int, and the operator expects an unsigned integer type (size_t and std::size_t usually have type unsigned int or unsigned long, but never type int). Still, a simple standard integer conversion makes (1) easily viable. However, there is another viable candidate: the built-in subscript operator. Indeed, if we apply the implicit conversion operator to str (which is the implicit member function argument), we obtain a pointer type, and now the built-in subscript operator applies. This built-in operator takes an argument of type ptrdiff_t, which on many platforms is equivalent to int and therefore is a perfect match for the argument 5. So even though the built-in subscript operator is a poor match (by user-defined conversion) for the implied argument, it is a better match than the operator defined at (1) for the actual subscript! Hence the potential ambiguity.

Upvotes: 1

Views: 278

Answers (2)

Spock77
Spock77

Reputation: 3325

str.operator[](size_t(5));

Write code in definite way and you'll not need to bother about all this stuff :) There is much more to thing about.

Upvotes: -1

Steve Jessop
Steve Jessop

Reputation: 279265

Note that the first list is Simplified Overload Resolution.

To remove the "possibly" and "potential" about whether or not int is the same as ptrdiff_t, let's change one line:

str[(ptrdiff_t)5] = 'c'; // definitely an overload resolution ambiguity!

Now the message I get from g++ is:

warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second: [enabled by default]

and --pedantic-errors promotes that warning to an error.

So without diving into the standard, this tells you that the simplified list is only part of the story. That list tells you which of several possible routes to prefer when going from A to B, but it does not tell you whether to prefer a trip from A to B over a trip from C to D.

You can see the same phenomenon (and the same g++ message) more obviously here:

struct S {
    explicit S(int) {}
    operator int() { return 0; }
};

void foo(const S&, long) { }
void foo(int, int) { }

int main() {
    S s(0);
    foo(s, 1);
}

Again the call to foo is ambiguous, because when we have a choice of which argument to implicitly convert in order to choose an overload, the rules don't say, "pick the more lightweight conversion of the two and convert the argument requiring that conversion".

Now you're going to ask me for the standard citation, but I shall use the fact I need to hit the hay as an excuse for not looking it up ;-)

In summary it is not true here that "a user defined conversion is a better match than a standard integer conversion". However in this case the two possible overloads are defined by the standard to be equally good and hence the call is ambiguous.

Upvotes: 0

Related Questions