4

So this static_cast code is totally legal:

int n = 13;
void* pn = static_cast<void*>(&n);
void** ppn = &pn;

Yet this has to be made into a reinterpret_cast to compile:

int n = 13;
int* foo = &n;
void** bar = static_cast<void**>(&foo);

If I don't change it I get the error:

error C2440: static_cast: cannot convert from int ** to void ** note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

So I take it the issue is "the types are unrelated". Yet I still don't understand, if it's OK when going from an int* to void* how can they be unrelated as a int** and a void**?

Jonathan Mee
  • 35,107
  • 16
  • 95
  • 241
  • 3
    why is your second snippet striked through? – 463035818_is_not_a_number Sep 21 '18 at 18:12
  • 3
    You could see `&foo` as a *pointer to `int*`*, and `bar` is a *pointer to `void*`*. Those two types are very different. It's the extra level of indirection which makes the whole difference. – Some programmer dude Sep 21 '18 at 18:15
  • 1
    Possible duplicate of [Why can't static\_cast a double void pointer?](https://stackoverflow.com/questions/26277391/why-cant-static-cast-a-double-void-pointer) – J. Calleja Sep 21 '18 at 18:19
  • 4
    Note that it is not even guaranteed that `sizeof (int*)` and `sizeof (void*)` are the same – Ben Voigt Sep 21 '18 at 18:22
  • @user463035818 It gives the error in the question. Just didn't want to give the appearance that it was functional code. – Jonathan Mee Sep 21 '18 at 18:26
  • 1
    @BenVoigt Yikes... what? I thought pointers were the same size? – Jonathan Mee Sep 21 '18 at 18:28
  • its very hard to read. Questions typically have erroneous code, its hard to miss when the next line says "...I get the error". I never copy code from questions but rather from answers.... – 463035818_is_not_a_number Sep 21 '18 at 18:28
  • @user463035818 \*shrug\* I'll remove it if you think so. I just wanted it to be clear. – Jonathan Mee Sep 21 '18 at 18:31
  • 1
    @JonathanMee: Your particular platform may guarantee that all object pointers are the same size and representation, but that's a non-portable assumption. (The guarantee is in fact very popular, since it's part of the POSIX standard... but older architectures and bare metal systems do otherwise and still are C++ compliant) – Ben Voigt Sep 21 '18 at 18:31
  • 1
    @JonathanMee Another approach that is easier to read is to add a comment on the line which raises the diagnostic. – eerorika Sep 21 '18 at 18:31
  • @BenVoigt So... how would this even work? I thought there was a requirement that anything could be cast to and from a `void*`? If a `void*` were of a different size wouldn't that nullify this requirement? – Jonathan Mee Sep 21 '18 at 18:34
  • @JonathanMee no. As long as `void*` is big enough to uniquely represent any pointer value, it can work. There is no reason why a `T*` couldn't be smaller than `void*`. – eerorika Sep 21 '18 at 18:35
  • 2
    @JonathanMee: The round-trip requirement can be satisfied if pointers without alignment requirements (`void*` and `char*`) are bigger than pointers with alignment requirements (including `int*`). – Ben Voigt Sep 21 '18 at 18:36
  • 1
    Meanwhile other popular platforms take advantage of the fact that aligned pointers don't store any address information in the lowest bits by reusing those bits for other purposes, for example function pointers on ARM use one bit to indicate the target function uses the Thumb instruction set. – Ben Voigt Sep 21 '18 at 18:38
  • @BenVoigt OK, I'm totally off topic here. Would you prefer to answer this in a new question? I can open one if you prefer. But how would I even use something like [`fwrite`](https://en.cppreference.com/w/cpp/io/c/fwrite) in such a system; say I was trying to stream out an array, how could I even know that `void*` had the same stride? – Jonathan Mee Sep 21 '18 at 19:00
  • 2
    `static_cast` isn't even an explicit conversion, it's an implicit conversion done explicitly – curiousguy Sep 21 '18 at 19:39
  • 1
    @JonathanMee: Why would `fwrite` care about the stride of `void*`? (Like 303's answer says, there actually isn't one, pointer math with `void*` is illegal) Are you trying to write an array of void pointers `void* a[]`? You can do that, but the resulting file won't be portable. Pointer values only have meaning in the same address space, even reading the file on the exact same system after restarting will be useless. – Ben Voigt Sep 21 '18 at 20:06

2 Answers2

8

int is in no way related to void. The same goes for int** and void** and so they cannot be converted using static_cast.

void* however, is special. Any data pointer type (including int*) can be static_cast into void* and back, despite no type being related to void (even further, the conversion to void* doesn't need a cast, as it is implicit). int* doesn't have this property, nor does void** and nor does any other pointer besides void*.


The additional freedoms that have been granted to void* come with additional restrictions. void* cannot be indirected, nor can it be used with pointer arithmetic. These restrictions are only possible because there can never be an object of type void. Or from opposite point of view, objects of void cannot exist because of these restrictions.

void** cannot be given those freedoms, because it cannot be given the same restrictions. It cannot be given those restrictions because void* objects do exist and they need to exist. If we couldn't indirect or iterate void**, then we couldn't use arrays of void* for example.

eerorika
  • 181,943
  • 10
  • 144
  • 256
0
void* pn = static_cast<void*>(&n);

is an implicit conversion; you can also write

void *pn = &n;

It means that pn stores a pointer to some object type; the programmer is responsible for knowing what that object type is. To cast back you need a static_cast:

int *pi = static_cast<int*>(pn);

Note that using static_cast to cast to any type significantly different than the original (float is significantly different, const int isn't) is a way of doing a reinterpretation. You should spell that reinterpret_cast not implicit conversion (or static_cast) followed by static_cast.

This is the whole purpose of void*, an concept tracing back to C, where casts are spelled with C-style casts obviously (not static_cast...) but have otherwise identical meanings.

Going back to the syntax of declarations in C and C++:

The declaration of a pointer to int is int (*pi); (parentheses are useless but help illustrate the point), you can read it like: I declare that expression (*pi) has type int. You can read a function declaration that way: in int f(int i); I declare that if i has type int then f(i) has type int.

The declaration void (*pi); looks like a pointer to a void but there is no such thing as an object of type void, the expression *pi isn't even well formed, it doesn't make sense. It's a special case in the type system: the syntax says "pointer to void", semantic says "pointer to something".

In C and C++, a pointer object is a first class object and you can take its address and have a pointer to a pointer, etc. (Contrast with Java where references like other fundamental types aren't class objects.)

So you can have a int**, int***... pointers to (pointers ... to int); you can have for the same reason void**: declared like a pointer to (pointer to void), semantically a pointer to (pointer to something).

Just like a pointer to int can't be assigned to a pointer to float without a cast:

float *f = &i; // ill-formed

because of the type mismatch, a type different from void** can't be assigned to a void**: the result of dereferencing a void** must be a void* object.

curiousguy
  • 7,344
  • 2
  • 37
  • 52
  • 1
    Interestingly, `reinterpret_cast` between pointer types is actually defined as having the same result as a conversion to `void*` then `static_cast`. – Ben Voigt Sep 21 '18 at 22:03