Search code examples
c++type-conversionstrict-aliasing

reinterpret cast of a class template instantiation to another


I implemented a container template class owning a storage of type std::unique_ptr with customizable deleter, as follows:

template <class D>
struct Container
{
    Container(const char* str)
        : data(typename D:: template make(str))
    { }
    std::unique_ptr<const char[], D>  data;
};

Here's what the deleter (container template parameter) might look like:

struct DefaultDelete : std::default_delete<const char[]>
{
    static const char* make(const char* str)
    {
        auto p = new char[strlen(str) + 1];
        memcpy(p, str, strlen(str) + 1);
        return p;
    }
};

struct CustomDelete
{
    static const char* make(const char* str)
    {
        // perform custom allocation & construction...
        auto p = str; // simplified example
        return p;
    }
    void operator()(const char* p) const noexcept
    {
        // perform custom deletion...
    }
};

Now, I want that an object of type Container<CustomDelete> can be implicitly casted as const Container<DefaultDelete>&. In order to do so, I implemented the following type-cast operator:

template <class D>
struct Container
{
    ... (same as above)
    template <class E>
    operator const Container<E>& ()     // implicit type-cast
    {
        return reinterpret_cast<const Container<E>&>(*this);
    }
};

Tested on Linux/gcc and Windows/msvc, this works as expected:

void print(const Container<DefaultDelete>& c)
{
    std::cout << c.data.get() << "\n";
}

int main()
{
    const char* const source = "hello world";
    Container<DefaultDelete> cd(source);
    print(cd);
    Container<CustomDelete> cc(source);
    print(cc);
    return 0;
}

results in:

hello word
hello word

However, as far as I understand, the above implementation of the type-cast operator violates strict aliasing rules and, although test worked as expected, it leads to undefined behavior.

So, my questions are:

Can the type-cast operator be implemented in such a way as not to violate strict aliasing rules ? And what is such implementation ?

What I want to achieve is to be able to pass a Container<CustomDelete> object to any function that needs a const Container<DefaultDelete>& (just like the print() function above) without the need to convert/create a new Container<DefaultDelete> object, because heap allocation is not allowed in the context where I have to call the function.

I noticed that when the CustomDelete class has a different size from DefaultDelete size, then reference returned by type-cast operator is broken. To compensate for this, I added a static assertion in operator implementation to check that both types have the same size, ie:

static_assert(sizeof(Container<D>) == sizeof(Container<E>), "size mismatch");

Assuming any positive answer to my 1st question exists, what other tests should be performed to make sure the type-cast works properly ?


Solution

  • Can the type-cast operator be implemented in such a way as not to violate strict aliasing rules ?

    No. It's not the cast itself that's the problem. It's that accessing the result of the cast, no matter how it was done, violates strict aliasing.