A Language-Design Puzzle in Operator Overloading Resolving an overloaded function call involves finding a single possibility that is strictly better than all the others.



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.