TITLE: Overloading resolution with built-in operators [SM] - sdm@cs.brown.edu (Scott Meyers) [JK] - kanze@us-es.sel.de (James Kanze) [JA] - jamshid@emx.cc.utexas.edu (Jamshid Afshar) ............................................................................ [SM] class Widget {}; class WidgetCollection { public: Widget operator[](long) const; operator char*() const; }; main() { WidgetCollection wc; Widget w = wc[1]; // ambiguous? } [JA] James raises some very valid points which I include at the end of this article -- hopefully they'll start some discussion in comp.std.c++. First, let's simplify Scott's problem to the following which I believe is a valid analogy: class WidgetCollection { public: operator char*() const; }; void foo( char*, int ); // foo-1: built-in operator[] for char*'s void foo( const WidgetCollection&, long ); // foo-2 main() { WidgetCollection wc; foo( wc, 1 ); } Overloading resolution is performed in three steps. First, determine the set of "best-matching" functions for *each* parameter in the function call *independently* of the other parameters. Then take the intersection of these (possibly one-element) sets. If the intersection of these sets is exactly one function, that function is the winner. The final, more subtle, requirement is that the winning function must be a strictly better match than any other function for at least one argument. See ARM 13.2 or CPL2 r13.2 for a full explanation. Actual parameter 1 is a `WidgetCollection'. The type of function `foo-1's first parameter is a `char*'. The type of `foo-2's first parameter is a `const WidgetCollection&'. The argument matching scale from 13.2 is used to "rank" conversions: exact with with standard with user-defined with match promotions conversions conversions ellipses 1 2 3 4 5 eg: T=>T& char=>int int=>long char*=>String T*=> short=>int int=>double const T* enum=>int 0=>T* float=>double Derived*=>Base* Matching `foo-1's first parameter requires a user-defined conversion. Matching `foo-2's first parameter is an "exact match". Therefore, the best-matching function for the first parameter is `foo-2'. The second actual parameter is an `int' (remember, literals are not `const'). It is an exact match for `foo-1's second parameter, but would require a "standard conversion" to match `foo-2's second parameter of type `long'. So, `foo-1' is the best-matching function for the second parameter. The intersection of the sets { foo-1 } and { foo-2 } is the empty set. Therefore, the call `foo(wc,1)' is ambigous. [JK] My interpretation of the ARM is that the first conversion is correct, but this interpretation is based on some rather vague premises. First, in section 13.2, the ARM describes argument matching for functions. Nowhere does it say that this argument matching is valid for built-in operators. I will only suppose that for the purposes of analysing the above expression, it is the intention of the ARM to act as if there was a function definition for the built-in operator[]. This is a very big exterpolation on my part; there are no words to this effect in the ARM. I do so only because I can find no other basis on which to evaluate the above. The question then comes up concerning the signature of the built-in operator[]. If the signature were the intuitive 'T* operator[]( T* , int )', then there would be no doubt that the above would be ambiguous. But in section 5.2.1, it states (concerning subscripting) that "one of the expresssions must have the type "pointer to T" and the other must be of integral type." This would seem to imply that there are in fact two operator[]'s to be considered here), one with an int as second parameter, and one with a long. (More generally, there would be one for every integral type as second parameter.) And since the one with the long is an exact match for all of the parameters, it is the best match. [JA] I agree that more needs to be said about overloading resolution with respect to built-in operators. I also agree with your analysis and interpretation, up to the last sentence. Other functions that should be considered in my example are: void foo( char*, long ); // built-in operator[](char*,long) void foo( char*, unsigned ); // built-in operator[](char*,unsigned) void foo( char*, unsigned long ); // ... //... same for `const char*' But I still believe my analysis is correct because these functions are never better matches for `foo(wc,1)'. Based on your last sentence above, you seem to think that the call in the original example used a `long' index instead of an `int'. [late-breaking news: James just posted a correction to his article but I'm too tired to rewrite mine] I'm not sure whether, for argument matching purposes, versions of `operator[]' taking `short' (and enums and bitfields) are considered to exist. Maybe only the `int' version is considered to exist and shorts, enums, etc. must match by promotion. I'm also not sure whether it could make a difference in overloading resolution. I can't wait until the Working Document I ordered arrives; has this been discussed in committee meetings?