What's the right way to overload operator== for a class hierarchy?

For this sort of hierarchy I would definitely follow the Scott Meyer's Effective C++ advice and avoid having any concrete base classes. You appear to be doing this in any case.

I would implement operator== as a free functions, probably friends, only for the concrete leaf-node class types.

If the base class has to have data members, then I would provide a (probably protected) non-virtual helper function in the base class (isEqual, say) which the derived classes' operator== could use.

E.g.

bool operator==(const B& lhs, const B& rhs)
{
    return lhs.isEqual( rhs ) && lhs.bar == rhs.bar;
}

By avoiding having an operator== that works on abstract base classes and keeping compare functions protected, you don't ever get accidentally fallbacks in client code where only the base part of two differently typed objects are compared.

I'm not sure whether I'd implement a virtual compare function with a dynamic_cast, I would be reluctant to do this but if there was a proven need for it I would probably go with a pure virtual function in the base class (not operator==) which was then overriden in the concrete derived classes as something like this, using the operator== for the derived class.

bool B::pubIsEqual( const A& rhs ) const
{
    const B* b = dynamic_cast< const B* >( &rhs );
    return b != NULL && *this == *b;
}

I was having the same problem the other day and I came up with the following solution:

struct A
{
    int foo;
    A(int prop) : foo(prop) {}
    virtual ~A() {}
    virtual bool operator==(const A& other) const
    {
        if (typeid(*this) != typeid(other))
            return false;

        return foo == other.foo;
    }
};

struct B : A
{
    int bar;
    B(int prop) : A(1), bar(prop) {}
    bool operator==(const A& other) const
    {
        if (!A::operator==(other))
            return false;

        return bar == static_cast<const B&>(other).bar;
    }
};

struct C : A
{
    int baz;
    C(int prop) : A(1), baz(prop) {}
    bool operator==(const A& other) const
    {
        if (!A::operator==(other))
            return false;

        return baz == static_cast<const C&>(other).baz;
    }
};

The thing I don't like about this is the typeid check. What do you think about it?