When template code needs to store a T*
in a variable of type void*
so that it can be retrieved and cast back to a T*
later, but it needs to handle both T
and T const*
, should it use void*
or a void const*
for the type erased storage? (Either choice requires at least one const cast somewhere.)
(Assume that the program tracks at runtime what was put into the void* to ensure it only retrieves the same type as what was put in, but it doesn't know at compile time what type is going to be stored in the variable.)
Edit: Also assume that the un-typed storage is hidden behind a wrapper class that has templated set() and get() methods, which record what T was used for the set() call and throw an exception if a different T is used for the get(). So the question is really about how should the wrapper class store it. (Note: the wrapper class is not itself a template; just its methods set() and get() are templates.)
In the past, I've used a union { void* p_nc; void const* p_c; };
when this problem came up. However I'm wondering if that's overkill compared to just using a const_cast.
The question is, which way is better (if any): cast the const off T const*
upon storage in a void*
, or use a void* const
for storage and cast the const off if retreiving a nonconst T*
?
From the point of view of somebody who is going to read your code (even you in the future, 6 months from now) it is better to express the idea as clearly as possible instead of trying to minimize the number of times you cast.
In your case using a union
says "it can be either one" but it doesn't put any checks on actually enforcing its usage.
OTOH having a clear interface that implements both const and non-const access functions (including cast functions) allows you to perform runtime-check and act accordingly (error message? exception?).
A clear interface has an added benefit of encapsulating the implementation so that your client code doesn't depend on the internal structure of the implementation - your client code will just use your template as const or non-const (depending on the usage) and the access/cast functions will take care of making sure the usage is actually valid.