@SeanMiddleTech: do you have an example how this would work? - how do I convert my struct to a void * (in the called function), or do I need another type?)
It's Middle*ditch*. Yes, my name is just as stupid-sounding as it looks. Thanks, parents! :P
In any case, I'm not sure what you're asking about specifically. You can convert any struct into a void*.
Your use case of constant buffers still isn't quite at the root. If you just want to memcpy a struct into a mapped buffer, you don't need to know which struct is passed in. Just take the void* address of the struct and its size, since that's all memcpy itself requires. The template approach others advocated helps to remove programmer error, e.g.:
template <typename T>
auto simple_update_buffer(ConstantBuffer* buffer, T const& value) -> enable_if_t<is_object_v<T>>>
{
void* address = buffer->map();
memcpy(address, &value, sizeof(value));
buffer->unmap();
}
The advantage being that you can't accidentally pass in the wrong size. The enable_if bit is an extra simple-ish check to make sure you don't accidentally pass in a pointer (because otherwise the function would bind to a reference-to-pointer).
Though in this case, I might argue that if you have different kinds of constant buffers that you should use the type system and make actually different constant buffer types, to ensure that you create your buffers with the right size and only update them with the proper values, e.g.
// a constant buffer that holds a copy of struct T
template <typename T>
class ConstantBuffer
{
Buffer* _buffer = nullptr;
public:
void allocate()
{
// guarantee that the buffer is allocated for the size of T
_buffer = device.CreateConstantBuffer(sizeof(T));
}
void update(T const& value)
{
// guarantee that the buffer can only be updated with a copy of T
void* mapped = _buffer->map();
memcpy(mapper, &value, sizeof(value));
_buffer->unmap();
}
};
...
ConstantBuffer<PerFrameData> perFrameCB;
ConstantBuffer<PerModelData> modelCB;
PerFrameData data;
perFrameCB.update(data); // OK
modelCB.update(data); // COMPILE ERROR - saved you from a stupid mistake
The above of course is a simplistic example. You can make the wrapper support arrays of the given type with the proper size calculations, partial updates, etc. if required (e.g. for updating individual instances of a streaming per-instance buffer), to more intelligently use mapped addresses, etc.
The point isn't to use templates to do fancy meta-programming tricks (which are, 90% of the time, a stupid idea - they just make your code harder to read, harder to debug, and slower to compile) but to augment the types in your code so that the compiler enforces rules like "only the per-frame structure can be copied into the per-frame constant buffer."
Use the type system to make your life easier and less bug-prone.
(And as an aside, I'm not saying that this is the best way to do a constant buffer. It's just one of many ways to do things.)