Search code examples
c++reinterpret-cast

How can a POD type add support for reinterpret_cast'ing it?


I'm trying to replace a type, typedef'ed from a built-in integral type, used in a large project with a custom class, which would implement some additional functionality like avoiding truncation on assignment etc. But the project extensively uses a conversion like reinterpret_cast<void*>(value). I've done a naive attempt at implementing an operator void*() in my new class, but apparently this doesn't make it possible to reinterpret_cast, only allows to static_cast to void*. Here's the code:

#include <cstdlib>
#include <type_traits>

#define USE_NEW_VALUE_TYPE
#ifdef USE_NEW_VALUE_TYPE
struct Value
{
    size_t value;
    operator void* () { return reinterpret_cast<void*>(value); }
};
static_assert(std::is_pod<Value>::value,"Value is not POD");
#else
typedef size_t Value;
#endif

int main()
{
    Value v{5};
    void* p=reinterpret_cast<void*>(v); // This is how the project uses it
}

I thought that if the class is POD, this would allow me to do such things like a reinterpret_cast. But this code gives me a compilation error:

invalid cast from type ‘Value’ to type ‘void*’

So my question is then: how do I add support for such a reinterpret_cast, so that I didn't have to manually switch it to static_cast in every part of the project?


Solution

  • Unfortunately, reinterpret_cast is not designed for this. The reason this works for POD types is #3 on the cppreference website:

    A value of any integral or enumeration type can be converted to a pointer type. A pointer converted to an integer of sufficient size and back to the same pointer type is guaranteed to have its original value, otherwise the resulting pointer cannot be dereferenced safely (the round-trip conversion in the opposite direction is not guaranteed; the same pointer may have multiple integer representations) The null pointer constant NULL or integer zero is not guaranteed to yield the null pointer value of the target type; static_cast or implicit conversion should be used for this purpose.

    And, I believe §12.3.2 plays a role here:

    A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void.

    In short: User-defined conversions do not participate in resolution of reinterpret_casts.

    Possible solutions

    1. Explicitly take the address of v:

     void* p=reinterpret_cast<void*>(&v);
    

    2. Defined operator void*() as you did, you could write

    void *p = v;
    

    Note: This probably opens up a wormhole of problems due to unwanted implicit conversions

    3. Use static_cast as you said yourself.

    Note: use &v rather than defining operator void*() for the same reason as in No. 2

    4. Ideally, but probably unrealistic, fix the underlying design issue requiring casting classes to void

    No. 3 might be the least painful solution here.

    EDIT for comment

    There are two ways here:

    1. Use an overloaded address-of operator (operator&). This may not be possible depending on how Value is used.

    #include <cstdlib>
    
    struct Value
    {
        size_t value;
        void* operator &() { return reinterpret_cast<void*>(value); }
    };
    
    int main()
    {
        Value v;    
        void* p=reinterpret_cast<void*>(&v); // This is how the project uses it
    }
    

    2. Implement an operator uintptr_t. While this requires verbose double-casting, it transfers the conversion into the Value class, and uintptr_t is guaranteed to be able to hold a void*.

    #include <cstdlib>
    
    struct Value
    {
        size_t value;
        operator uintptr_t() { return static_cast<uintptr_t>(value); }
    };
    
    int main()
    {
        Value v;
        void* p=reinterpret_cast<void*>(static_cast<uintptr_t>(v)); // This is how the project uses it
        // or less verbose
        void* p2=reinterpret_cast<void*>(uintptr_t(v)); 
    
    }