The Solution to Last Week's Language-Design Puzzle
Last week, I asked a question about overloaded operators that I can summarize as follows:
struct Thing {
void operator+(std::string);
};
struct Blob: Thing { };
void operator+(Thing&, const char*);
Thing t;
Blob b;
t + "s"; // OK – calls nonmember
b + "s"; // What does this do?
The first of the two commented expressions, t+"s", might mean either t.operator+("s") or operator+(t, "s"). The first of these meanings requires converting "s" to type std::string; the second requires no conversions. As a result, the second meaning is clearly a better match for the expression, so that's the one the compiler uses.
The second commented expression, b+"s", is more interesting. Analogously to the first, it might mean b.operator+("s") or operator+(b, "s"). This time, however, the call to operator+(b, "s") requires converting b from Blob& to Thing&. If operator+ is an ordinary member of Blob by inheritance, then the call is ambiguous: One alternative requires a conversion on the first argument; the other requires a conversion on the second argument.
Here is how a language designer might think about the situation. Language designers try to define general rules from which specific language behaviors follow. One such rule might be that when we derive one class from another, the members of the base class become members of the derived class. Another such rule might be that if the derived class does nothing to change the behavior of the base class, the derived class should behave as much as possible like the base class.
What this example shows is that these two general rules conflict: They cannot both be universally true at the same time. As a result, one of them must give way to the other. One of the hard parts of language design is to figure out which rule should take precedence in cases such as this.
The key to this particular example is in what happens if we call b.operator+("s"). In particular, while the member operator+ is executing, the this pointer will have type Thing*, not Blob*. This observation leads to the following subtle language rule:
Calling a base-class member of a derived-class object
counts as a conversion from Derived& to Base&
for the purpose of resolving overloaded operators.
With this rule, the call to b.operator+("s") converts the "first argument" (i.e., the expression b) from Blob& to Thing&, and converts the second argument from const char* to std::string. The call to operator+(b, "s") also converts the first argument to Thing&, but does not convert the second argument at all. Therefore, operator+(b, "s") wins.
I would guess that most C++ programmers are not consciously aware of this language rule. However, if the rule were not there, most programmers would notice its absence because of anomalies such as this example. Such anomalies, and design strategies for avoiding them, are among the many reasons that programming-language design is harder than it looks.

