Search code examples
c++sdkabinullptr

Returning an empty string literal VS. returning a nullptr - Are they the same?


I recently came across the following function in the public SDK of an application that I work on:

virtual char* ExtentName() {return "";}

When I compile the application using Visual Studio with the /permissive- flag, the function above causes the following compilation error:

error C2440: 'return': cannot convert from 'const char [1]' to 'char *'
note: Conversion from string literal loses const qualifier (see /Zc:strictStrings)

I'm really surprised this code compiles under any circumstances, because it's converting a string literal (in this case a null-terminator) into a char*. How is that possible?

Additionally, I would like to fix this problem without causing an SDK break. This is what I consider the best solution:

virtual char* ExtentName() {return nullptr;}

The change above doesn't break the ABI, but I'm worried it could break our users' code, although I'm not sure how. Is that a possibility? Thank you for any information!


Solution

  • Casting a string literal to char* without const qualification implicitly has been disallowed since C++11.

    MSVC allows it anyway for backwards-compatibility because earlier versions of C++ allowed it. The /permissive- flag makes it behave standard-conform.

    The first function does not return a null pointer value, it returns a valid pointer to a string literal which will be an array of char of length 1 containing only the null-terminator. Therefore the two functions are not at all equal.

    The second function returns a null pointer value, which in contrast to the first function's return value, can e.g. not be indirected through.

    You can technically keep the function signatures by doing an explicit const_cast:

    virtual char* ExtentName() { return const_cast<char*>(""); }
    

    That in itself has well-defined behavior, but any attempt to write to the array that the pointer returned from this function points to will cause undefined behavior without warning from the compiler.

    Therefore this shouldn't be done. However, if you already compiled the original function against a previous C++ standard or permissive compiler, then the function already did exactly this and it would not introduce any new risks of UB. Writing through the returned pointer would have been UB without warning in the original code as well.

    The correct thing to do is making the return value const char*, because the user is clearly not allowed to modify the pointed-to values returned by this function. It was probably a mistake not to do so from the beginning.