Nutz, virtual links and GRE solve different problems, IMO.
Generally speaking, tunnels (esp. when implemented in the OS as a virtual interface, as they are in IOS) are BAD for routing, they create all sorts of possible inside-out-isms and ways your network can be functional but a lot less than optimal - tunnels, like intelligent L2 protocols, are bad because they hide the physical topology from the L3 routing protocol, and thus make it difficult or impossible for the L3 routing protocol to come up with optimal routing for the real topology. Remember - when you use GRE, you need to make darn sure that forwarding traffic doesn't decide to go that way. Unless that's really what you want, of course.
Virtual links are simply OSPF running multi-hop, with some protocol restrictions most notably about where you can transit. (rules that are intended to keep things simple and prevent very odd topology problems)
As a general rule, when I see people talking about doing very complex things with an OSPF network, that tells me that either (a) somebody's a white-board jockey, teacher/student, etc... not really doing it or (b) somebody's got a network that might be unnecessarily complex. Yes, there are times when you need to get into the really advanced features, but I personally see that as a level of complexity to be avoided wherever possible. Simple networks are happy networks! Almost every OSPF network I've seen could be fit into one area and maybe a few ASBRs (outside world/default route redistribution, and maybe some static routes redistributed into your LSDB). When you get to the complexity level where you really need a bunch of areas and different types and virtual links, you need high-end network engineering clues.