It is not possible to get a list of ERC20 token holders directly from a contract.
You are correct in that you cannot do this because you cannot get a list of keys for a mapping in Solidity, therefore it is impossible without external intervention.
With that said, there are many people who need this functionality and perform tasks to achieve this. The biggest example I can think of is airdropping tokens to various accounts based on their holdings of another token. The way that most people do this is to read all of the token holders from the blockchain and store it in a local database. From there, they will implement a gas-efficient function that takes in the addresses as a parameter and performs actions on them that way.
It is not possible to accomplish what you desire using only the blockchain, but using a combination of on-chain/off-chain logic can achieve your goals.