Search code examples
c++constantsvoid-pointerstype-erasure

Storing a pointer to T in a void* but T may be const or nonconst - use void const* or just void*?


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*?


Solution

  • 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.