A Language-Design Puzzle in Operator Overloading
My note last week about argument-dependent lookup reminded me of another problem from the early days of C++. The solution to this problem was a rule that few people even know exists — but without it, a lot of code would break.
Consider an expression such as a+b, where a and b are both class objects. The compiler treats this expression as if it might mean either a.operator+(b) or operator+(a, b), and considers all of the possibilities for each treatment as one big collection in deciding how to resolve the overloaded +.
Resolving an overloaded function call involves finding a single possibility that is strictly better than all the others. More specifically, it involves finding a possibility that is a better match than all the others in at least one argument, and no worse in any argument. For example:
void f(std::string, int);
void f(std::string, double);
f("foo", 42);
Here, the call to f resolves to the first alternative, because int is a better match than double to the value 42, and the other argument (i.e., the first) requires a user-defined conversion in both cases. However, if instead we had written
void g(std::string, int);
void g(const char*, double);
g("foo", 42);
the call would be ambiguous because the first g is a better match for the second argument and the second g is a better match for the first argument.
Now let's make the example a little more complicated:
struct Thing {
void operator+(std::string);
};
void operator+(Thing&, const char*);
Thing t;
and let's think about what happens to the expression t+"s". This expression might be interpreted as t.operator+("s") or as operator+(t, "s"), so the compiler must decide which of these two interpretations it prefers. In both cases, the left argument t has type Thing, so there is no reason to prefer either function to the other based on the first argument. However, the member operator+ has to convert its second argument to string, which means that the nonmember operator+ is a better match for the second argument. Therefore, the compiler will choose the nonmember.
Now let's complicate matters still further:
struct Blob: public Thing { };
Blob b;
and look at the expression b+"s". This is your chance to play language designer: See if you can figure out why the obvious way of interpreting this expression leads to trouble, and then see if you can figure out a language rule that offers a clean way to avoid this trouble.
I'll reveal the answer next week.

