const correctness and parameters to functions with structs containing void*

11,122

Solution 1

The error you are observing has absolutely nothing to do with const qualifier used in function declaration, or with any const qualifiers you explicitly used in your code.

The problem is the same as in the following minimal example

void *p = "Hello";

which suffers from the same error.

In C++ language the type of string literal is const char [N]. By the rules of const correctness it is convertible to const void *, but it is not convertible to void *. That's all.

A more formally correct error message would be "Cannot convert from const char [22] to void *", or "Cannot convert from const char * to void *", but apparently the inner workings of the compiler perform the conversion to const void * first (under the hood) and then stumble on conversion to void *, which is why the error message is worded that way.

Note that const correctness rules of C++ language used to include an exception that allowed one to implicitly convert string literals to char * pointers. This is why

char *p = "Hello";

compiles with a mere warning, even though it violates the rules of const correctness just like the previous example. That exception applied only to conversions to char * type, not to void * type, which is why the previous example produces an error. This special conversion has been deprecated in C++03 and removed from the language in C++11. This is why the compiler issues a warning about it. (If you switch your compiler to C++11 mode it will become an error.)

Solution 2

First of all, your test class and function just make things unnecessarily complex. In particular, the error has nothing to do with the const in Test * const pStruct, because this only means that pStruct must not be made to point to anything else. After all, in your own words:

the const modifier on the function definition makes it so that the pointer is constant but what it points to is not

Here is a simplified piece of code to reproduce the problem:

int main() {
    void *ptr = "This is a test string"; // error
}

As for your question,

What I don't understand, given my understanding of const correctness, is why is the compiler emitting this error, "invalid conversion from ‘const void*’ to ‘void*’" at all? Since the const modifier on the function definition makes it so that the pointer is constant but what it points to is not, why should this be a problem?

Because a string literal is a char const[], "decaying" to a char const *, and the conversion to a non-constant pointer would lose the const qualifier.

This does not work for the same reason the following won't:

int main() {
    int const *i; // what's pointed to by i shall never be modified
    void *ptr = i; // ptr shall modify what's pointed to by i? error!
}

Or more precisely,

int main() {
    int const i[22] = {}; // i shall never be modified
    void *ptr = i; // ptr shall modify i? error!
}

If this was not an error, then you could use ptr to implicitly bypass the const qualifier of i. C++ simply does not allow this.

Finally, let's look at this piece of code:

pStruct->pSomething = (void*)"This is a test string"; // no error now

Again, this can be simplified and reproduced with int rather than char so as not to obfuscate the real issue:

int main() {
    int const i[22] = {}; // i shall never be modified
    void *ptr = (void*)i; // ptr shall modify i? ok, I'll just shut up
}

You should not use C-style casts in C++. Use one of static_cast, reinterpret_cast, dynamic_cast and const_cast to make it clear which kind of conversion should be enforced.

In this case, you'd have seen the trouble it takes to "shut up" the compiler:

int main() {
    int const i[22] = {};
    void *ptr = const_cast<void*>(reinterpret_cast<void const *>(i));
}

And, of course, even though this may compile without a warning, the behaviour of the program is undefined because you must not use const_cast to cast away the constness of an object originally initialised as constant.


Edit: I forgot about the whole char * C compatibility business. But this is covered in the other answers already and, to my best understanding, does not render anything in my answer incorrect.

Solution 3

First of all, using C style casts will break const correctness. That is the only reason that your cast "works". So don't do it. Use reinterpret_cast, which (should, I didn't test it) get you a similar error to the one you are seeing.

So "This is a test string" is a const char*. If you reference it as a void*, something could modify the contents of void* later. If you make it so you can mess with the contents of that, you're no longer const correct.

And you COULD if there was no error, shown below.

int main() {
    Test t;
    TestFunc(&t);
    reinterpret_cast<char*>(t.pSomething)[0]='?';
    return 0;
}
Share:
11,122
Andrew Falanga
Author by

Andrew Falanga

My first programming job was as a Test Engineer. That was the title and it was, essentially, a software engineer who's job was to write tests. This was in the Boise based Enterprise LaserJet R&amp;D Lab. The languages used were TCL, KSH, BASH, C, C++ and, in the end, C#. Today I work for Micron Technology, Inc. as a Systems Software Engineer. My professional software engineering career has been focused mainly in libraries and lower level programs. I have written Linux kernel modules. I prefer to work in the Kernel before any other. User facing programs I tend to avoid.

Updated on June 05, 2022

Comments

  • Andrew Falanga
    Andrew Falanga almost 2 years

    I'm not going to say I fully understand the idea of const correctness but let's at least say that I understand it. So, when I encountered this, I was/am stumped. Can someone please explain this to me. Consider the following code:

    #include <iostream>
    
    struct Test {
        int someInt;
        void * pSomething;
    };
    
    void TestFunc(Test * const pStruct) {
        pStruct->someInt = 44;
        pStruct->pSomething = "This is a test string"; // compiler error here
    }
    
    int main() {
        Test t;
        TestFunc(&t);
        return 0;
    }
    

    At the point I've annotated with a comment I get this error from gcc (4.5.3 for cygwin):

    foo.cpp:10:24: error: invalid conversion from `const void*' to `void*'
    

    It's apparently something to do with the fact that the struct contains a void* because some experimentation revealed that changing the struct to:

    struct Test {
        int someInt;
        char * pSomething;
    };
    

    produces a warning as opposed to an error. Also, leaving the structure unchanged but modifying this line to include the following cast compiles the code without warning:

    pStruct->pSomething = (void*)"This is a test string"; // no error now
    

    What I don't understand, given my understanding of const correctness, is why is the compiler emitting this error, "invalid conversion from ‘const void*’ to ‘void*’" at all? Since the const modifier on the function definition makes it so that the pointer is constant but what it points to is not, why should this be a problem? I'm assuming that there is some sort of implicit cast happening, as originally written, since the string literal would be something like const char * that must be converted to void *. That notwithstanding, the question remains since what pStruct points to is not constant.

    For reference, I read up on const correctness here and here.

    • quantdev
      quantdev almost 10 years
      char* is special, there's an exception for it when it comes to pointer conversions
    • Neil Kirk
      Neil Kirk almost 10 years
      To elaborate on @quantdev, char * is allowed to point to a string literal for backwards compatibility with C. But this is bad style. In C++ you should consider only const char * can point to a string literal.
  • Neil Kirk
    Neil Kirk almost 10 years
    There's no std:: in front of reinterpret_cast
  • Christian Hackl
    Christian Hackl almost 10 years
    VC actually says "cannot convert 'const char [22]' to 'void *' - Conversion loses qualifiers".
  • Andrew Falanga
    Andrew Falanga almost 10 years
    I didn't intend to complicate things. Rather, I was using the "smallest amount of code that reproduces." You see, I'm bringing some C code into C++ and the function in question takes the sg_io_hdr_t struct. That struct has a void * to which the string literal is being assigned. Thank you for the detailed answer. This is quite helpful.
  • Andrew Falanga
    Andrew Falanga almost 10 years
    @ChristianHackl I think that would have been more clear. I jumped to a conclusion. I'm quite glad that I asked here.
  • Christian Hackl
    Christian Hackl almost 10 years
    @AndrewFalanga: The introducing line was not meant to criticise your question, of course. Sorry if it came across that way.