The reason why we recommend ERC20Votes is that the "snapshot" mechanisms were abstracted away in multiple layers. This is its current inheritance graph:
The main difference is that the contract inherits from Votes, where all the logic for snapshotting happens. Given that the contract is built for governance purposes, I see there are a few important differences that makes it behave differently to ERC20Snapshot:
- The "snapshots" (now called checkpoints) depend on the EIP-6372 contract clock, as opposed to an snapshotId
- The Votescontract counts delegated tokens (by default to the address 0)
To replace the ERC20Snapshot behavior, you'll need to configure the contract clock (EIP-6372) to use an _snapshotId instead, and override Votes's delegates(address account) function to make each address delegate to themselves:
function delegates(address account) public view override returns (address) {
  return account;
}
Here's an example:
Although I recognize there are a few extra things you may not need (eg. EIP5805, EIP712, Votes), the gas overhead you mention will be only during deployment. If the contract remains used only for snapshotting purposes, I'd expect around the same gas costs in runtime.
If you really want to reimplement ERC20Snapshot, that's possible using the Checkpoints but I'd keep recommending using EIP-6372 for identifying your contract clock.
Here's an example of a reimplementation:
 WARNING: The referenced code is not audited and is only presented as a pointer for you to evaluate the options.
 WARNING: The referenced code is not audited and is only presented as a pointer for you to evaluate the options.
