Search code examples
c++static-castconst-cast

Nested static_cast and const_cast


I have a system call like the following one:

int transfer(int handle, int direction, unsigned char *data, int length);

I have written the following two functions:

int input(int handle, void* data, int length)
{
    return transfer(handle, 1, static_cast<unsigned char*>(data), length);
}

int output(int handle, const void* data, int length)
{
    return transfer(handle, 0, static_cast<unsigned char*>(const_cast<void*>(data)), length);
}

I don't like the nested const_cast inside the static_cast, is there a way to perform the conversion from const void* to unsigned char* in a single step?


Solution

  • Using a C-style cast will generate identical assembly. As seen in Compiler Explorer:

    //Source #1
    int transfer(int handle, int direction, unsigned char *data, int length);
    int input(int handle, void* data, int length)
    {
        return transfer(handle, 1, static_cast<unsigned char*>(data), length);
    }
    
    int output(int handle, const void* data, int length)
    {
        return transfer(handle, 0, static_cast<unsigned char*>(const_cast<void*>(data)), length);
    }
    
    //Source #2
    int transfer(int handle, int direction, unsigned char *data, int length);
    int input(int handle, void* data, int length)
    {
        return transfer(handle, 1, (unsigned char*)data, length);
    }
    
    int output(int handle, const void* data, int length)
    {
        return transfer(handle, 0, (unsigned char*)data, length);
    }
    
    //Assembly (both)
    input(int, void*, int):
            mov     ecx, edx
            mov     rdx, rsi
            mov     esi, 1
            jmp     transfer(int, int, unsigned char*, int)
    output(int, void const*, int):
            mov     ecx, edx
            mov     rdx, rsi
            xor     esi, esi
            jmp     transfer(int, int, unsigned char*, int)
    

    So it's clear that simply using a C-style cast will solve your problem.

    However, you shouldn't use a C-style cast

    The reason for the verboseness of the C++ casts is to ensure that you aren't making mistakes. When a maintainer sees your code, it's important that they see the const_cast and the static_cast, because writing the code this way informs the reader that casting away the const-ness of the pointer is intentional and desired behavior. A code maintainer should see these casts, and presume that there was intent behind the code, instead of having to guess whether you knew that casting directly from const void* to unsigned char* would involve risking Undefined Behavior. Your example might not contain UB (since you specified that the contract of transfer is to treat data as read-only when direction is 0), but it's important that anyone else who needs to make changes to your code understands the deliberateness of your coding practices.