简体   繁体   中英

const correctness and parameters to functions with structs containing void*

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 .

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.)

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;
}

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.

"blah" is an array of 5 char const . In C++11 it converts implicitly to char const* . In C++03 and earlier the literal also converted implicitly to char* , for C compatibility, but that conversion was deprecated, and it was removed in C++11.

Setting

void* p = "blah";    // !Fails.

you get the conversion sequence char const[5]char const*void* , where the last one is invalid and yields an error.

Setting

char* p = "blah";    // Compiles with C++03 and earlier.

with C++11 you get the same conversion as for void* , and an error, but with C++03 and earlier, since the source is a literal string you get char const[5]char* .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM