I use no templated data in my classes, all the texture assignments and materials are already stored with the shader parameters they will be set on. So they are stored as a list of pairs in the render data in my case.
To manage textures I have a texture manager that maintains only one copy of the requested texture, stored by a pair of a hash of the texture name and the texture itself.
This management is all up to your own personal liking same as the way that I store a direct link to the shader parameter and it's data.
That's pretty much exactly what I do. DrawOperation is the class that contains all data needed for rendering: pointer to vb / ib, pointers to a vertex / index shader pair, pointer to a data structure that stores shader information (constants and textures), and some other data. I have a special draw op pool allocator that creates them in preallocated space and deletes all of them at the end of the frame - when traversing the scene multiple drawops are created for each mesh (i.e. multipass rendering) and passed into a draw queue class which sorts them into buckets based on priority, shader, texture, distance, etc.
I see sprites as models to be honest, they just consist of a on screen quad, and a texture atlas with a current page from that atlas
Exactly what I was saying, at the lowest level all render operations are just a vb / ib, shader, and shader data.
I'm not sure what the visitor pattern is giving you over simple C-style compile time polymorphism in which you have one .h file with different .cpp implementations for each output graphics library. You know, you have protypes like void RenderSprite(SpriteType ... ) and then have implementations for OpenGl and DirectX or whatever in separate cpp's.
The visitor solves a different problem. What you describe is how I support multiple rendering apis. The visitor pattern makes it so that the renderer doesn't need to care about wether its drawing a sprite, a static model, and animated model, a particle system, or anything else. It just draws "DrawOperations". You can have an octree, a bsp, a list of sprites, and each has and enqueue(DrawQueue dq) method, which simply generates a list of DrawOperations from whatever data it manages. Its not a true visitor but that's how I think of it