In general, to navigate through object-oriented types, use a dynamic down-cast or a dynamic cross cast.
Languages like C++, java or eiffel have limited support for object-oriented programming (they are very unlike smalltalk or self). For example, a static member function is not polymorphic at run time; virtual function signatures cannot be different (other than for co-variance of return types). Very often, if we attempt pure oops in these languages, we are forced to fall back on a dubious 'CObject' or 'object' as the base class of almost everything. Which we then use in much the same way as a C programmer uses void* - if a function returns an 'object', to use the result intelligently we need to look at the implementation of the function, like for a C function that returns a void*.
Even if we have an object-oriented hierarchy of types, if we need polymorphic behaviour for any thing other than virtual functions, the only flexible solution available is compile-time polymorphism - ergo CRTP and friends.