Virtual functions are inappropriate here because the subclass member functions are specific to those subclasses (for example the CourseStudent has a list of units, whereas a ResearchStudent does not, so a getUnits() function implementation in ResearchStudent would make no sense at all)
I had a bit of a read up on dynamic and static casts ( cplusplus.com typecasting ), and in this instance I think a static cast is more appropriate.
The general disadvantage of a static_cast is that it does not perform any checking at runtime to ensure that the object being cast to a subtype is in fact that subtype and not some other. In this case I am specifically checking the type before I perform the type (using a private data member that is set in the subclass constructor and has no mutator), so as long as my checking is good there should be no problem with a static cast. A static cast is more efficient since a dynamic cast requires more runtime resources to perform the type checking.
Where there is any chance of a member not being the expected type, static casting would not be appropriate so I would go for dynamic casting (this is an assignment so once it has been submitted the code won't need to be maintained, so there's no risk of someone messing it up later).