The ones for ex are clear. The ones for numeric are for speed, not just for the call itself but also when it's put into another function where the numeric return type may yield another speedup. But that is not entirely undisputable and we've discussed this before, without clear outcome. The ones for general types T are for disambiguation purposes.
Another option is to have just the "pow" class and construct everything just using it's constructor. I find it much less complex than having 1 class plus 3 functions. But this issue is not that important.
Allocating all objects on the free store and having to delete them manually (and maybe even managing refcounts manually) wouldn't make everything simpler, I suppose. Class ex came into existence since there are so many operations where we know very little about the general result type. Consequently, we cannot juggle the objects themselves on the stack, but have to deal with basic pointers. A std::vector of objects of various types (as in a general, unexpanded polynomial) would really have to be a vector of such pointers, too. Such raw pointers are better wrapped. There is passing mention to this in the FAQ <http://www.ginac.de/FAQ.html#evaluation>.
It seems to me that easiest way is to have one parent class ("basic") and derive everything from it (which ginac does in fact). And you return just a "basic". Wheter it is in fact "add", or "mul", or something else, will be determined at runtime. i.e. basic e=(a+b)^2 is an instance of "pow". if you expand it, one would call e.expand(). and you don't need any non class functions like expand. it will be just implemented in "pow", because only pow should know, how to expand powers. because you may want to call expand() of the basic class without retyping it to pow, it can be also mentioned in "basic" class declaration as an empty method (basically all the methods of ex, like expand or series can be implemented in basic). Cheers, Ondrej