My approach to forests is that they are emergent patterns. They appear out of the properties of the different tree classes. A tree class may use a density mask to tell the system where this type of tree does well. Altitude is one clear example. These masks, however, are a very broad brush. Several species may share the same mask values for very large areas, still their distribution within that area has to be realistic.
To achieve this, I use a simulation of the forest evolution over several centuries. This is something I described in an earlier post. There are two key parameters: tree maturity, which is the average age at which the tree starts producing seed, and average lifespan, which is for how long a tree is expected to live.
The whole world (12km x 12km in my tests) is covered by a single forest, which takes less than a minute to simulate. Even if it is a single forest to the simulation, it looks like different forests and types of biomes. By using the masks you can make sure trees gradually disappear in deserts, or that some species never crossed certain range of mountains.
You can choose to simulate much smaller areas while you are still tweaking the tree classes, the results are consistent with a wider simulation. It creates a very quick workflow.
In the following screenshots you can see a quick test I did with seven different types of trees. This can be improved a lot with more tree classes and more detailed masks, hopefully you get the idea: