One pattern (not necessarily a good one, but it's definitely used out there) is to define your "generic" code in macros, and then expose a macro such as MAKE_LIST(int) that in this case instantiates a list of int. Then you get a 'type-safe' interface, or at least more type-safe than void*, and also less annoying.
And then you just abuse arrays as much as possible. For a small number of items, e.g. <100 on a modern laptop, that linear and cache-friendly search is going to beat std::map and std::unordered_map and friends.
And then you just abuse arrays as much as possible. For a small number of items, e.g. <100 on a modern laptop, that linear and cache-friendly search is going to beat std::map and std::unordered_map and friends.