Your intuition might be better if you think about the case where there is more than one derived class. Here is an example where there are two derived classes, each with different ideas of how the protected member (x) should be used. (Pretend there are other members that have been omitted for simplicity.)
class Base {
protected:
// Base will reset x to zero under certain circumstances.
// Otherwise, it is for derived classes to use.
int x = 0;
};
struct Derived : public Base {
// Derived chooses to use negative values for x.
Derived() : Base() { x--; }
void update(Derived& other) {
other.x = x;
}
};
struct Alternative : public Base {
// Alternative chooses to use positive values for x.
Alternative() : Base() { x++; }
void update(Alternative& other) {
other.x = x;
}
};
This much is good. It compiles, because unlike the question's code, Derived accesses x only in Derived objects, not in Base objects. Derived objects never have a positive x, and Alternative objects never have a negative x.
What happens if we change the type of update's parameter to Base&? Compilation fails, as in the question. Why? Well, you do not want a situation like the following:
Derived negative;
Alternative positive;
negative.update(positive); // <-- problem!
If Derived::update() were to accept a Base& instead of a Derived&, then the above would also compile. An object can be converted to a reference to an unambiguous base class, so positive would be an acceptable argument. However, update would proceed to set positive.x to negative.x, breaking the assumption Alternative makes about x never being negative. Breaking another class is Bad.
This example is contrived for simplicity. yet the principle is there. Sibling classes are not friends; they often are not even aware of each other's existence. A class cannot be allowed to mess with a different class. Even though a derived class might have some protected access to its base, it can only do so when it is known that the object in question is actually the same class, not some other derived class.