3

While arguing with someone over the suggestion he made in the comment thread of this answer, I came across some code that gcc4.8 and VS2013 refuse to compile but clang happily accepts it and displays the correct result.

#include <iostream>

int main()
{
    int i{ 5 };
    void* v = &i;
    std::cout << reinterpret_cast<int&>(*v) << std::endl;
}

Live demo. Both GCC and VC fail with the error I was expecting, complaining that the code attempts to dereference a void* within the reinterpret_cast. So I decided to look this up in the standard. From N3797, §5.2.10/11 [expr.reinterpret.cast]

A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). —end note ] No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called.

In this case T1 is void and T2 is int, and a void* can be converted to int* using reinterpret_cast. So all requirements are met.

According to the note, reinterpret_cast<int&>(*v) has the same effect as *reinterpret_cast<int*>(&(*v)), which, by my reckoning, is the same as *reinterpret_cast<int*>(v).

So is this a GCC and VC bug, or are clang and I misinterpreting this somehow?

Community
  • 1
  • 1
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 1
    What if you do `void* v = &i; *v;`? clang still doesn't complain. [Should I use static_cast or reinterpret_cast when casting a void* to whatever](https://stackoverflow.com/questions/310451/should-i-use-static-cast-or-reinterpret-cast-when-casting-a-void-to-whatever?rq=1) looks interesting. (Warning: old, info may be outdated.) –  Jan 17 '14 at 06:07
  • 1
    According to [this](http://stackoverflow.com/questions/7346634/dereferencing-an-invalid-pointer-then-taking-the-address-of-the-result), it looks as if `&*ptr` is legal, but not equivalent to `ptr` (which I'm guessing is what you meant in the last `reinterpret_cast`). Thus, the pointer would still have to be dereferenced. – chris Jan 17 '14 at 06:16
  • @chris In the comments from the question you linked to, Oli Charlesworth [says](http://stackoverflow.com/questions/7346634/dereferencing-an-invalid-pointer-then-taking-the-address-of-the-result#comment8862523_7346634) that C99 explicitly states `&*E` is the same as `E`. So that part does pan out. I guess the question that remains is what if `E` is `void`, an incomplete type. – Praetorian Jan 17 '14 at 06:33
  • @Praetorian, Yeah, and that note is only in C, not C++. The whole topic is messy, but even `void *p; &*p;` should work fine in C if it's a true noop. – chris Jan 17 '14 at 06:55

3 Answers3

3

Thing is, the expression inside the cast is invalid. According to §5.3.1 Unary Operators:

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points. If the type of the expression is “pointer to T,” the type of the result is “T.” [ Note: a pointer to an incomplete type (other than cv void) can be dereferenced. The lvalue thus obtained can be used in limited ways (to initialize a reference, for example); this lvalue must not be converted to a prvalue, see 4.1. — end note ]

void isn't an object type. So things stop there, the whole expression is invalid. clang appears to get this wrong here.

What's more, the void type can only be used in a specific set of circumstances as per §3.9.1 Fundamental types:

An expression of type void shall be used only as an expression statement (6.2), as an operand of a comma expression (5.18), as a second or third operand of ?: (5.16), as the operand of typeid or decltype, as the expression in a return statement (6.6.3) for a function with the return type void, or as the operand of an explicit conversion to type cv void.

So even if the derefence was legal, you can't use a void "object" as the source in a cast (except a cast to void).

reinterpret_cast<int&>(*v) might indeed have the same effect as *reinterpret_cast<int*>(&(*v)) when both these expressions are valid (and no fancy operator overloading ruins the day). Just because an expression could be re-written in a valid form doesn't imply that it is itself valid.

Mat
  • 202,337
  • 40
  • 393
  • 406
  • I agree that if you look at `operator*` in isolation the expression doesn't make sense. My conjecture was that `&(*p)` is equivalent to `p`, which does not seem to be the case from the question @chris linked to in the comments. – Praetorian Jan 17 '14 at 06:24
  • I take that back, according to [this](http://stackoverflow.com/questions/7346634/dereferencing-an-invalid-pointer-then-taking-the-address-of-the-result#comment8862523_7346634) comment, C99 does state `&*p` is the same as `p`. – Praetorian Jan 17 '14 at 06:35
  • Clang 16 has changed behaviour here to match gcc/VC. See https://releases.llvm.org/16.0.0/tools/clang/docs/ReleaseNotes.html#c-c-language-potentially-breaking-changes – TBBle May 19 '23 at 01:57
3

An expression of type void is allowed as a mostly just syntactical device in a return statement, and also you can cast an expression to void, but that's all: there are no glvalues of type void, an expression of type void does not refer to memory. Thus the quoted passage from the standard, starting with a glvalue, does not apply. Thus, clang is wrong.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
1

If v is of type void *, then *v just makes no sense. The problem is not the cast, the problem is that it is illegal to dereference a pointer to void.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278