RFC: Can we stop the extension to allow dereferencing void* in C++?

This came up here: [clang] void is apparently referenceable · Issue #56014 · llvm/llvm-project · GitHub

The reproducer (Compiler Explorer) is basically:

void foo(void *v) {
    *v;
}

No other compilers in C++ mode support this, so I’d be just as happy to have us make this an error to be more consistent with the other implementations.

Does anyone have a good reason why we should keep this extension? It seems thoroughly unmotivated.

The last time it was discussed: 20073 – Should not be allowed to dereference a void *

Not much discussion happened here when @zygoloid suggested switching it to a ExtWarn (instead of just an error), but the fallout seems minor.

So, what do we think? Anyone ahve a good reason not to just make this a hard-error?

2 Likes

The really fun part is that clang rejects arithmetic on a void*, but gcc accepts it as an extentsion and MSVC rejects both: Compiler Explorer. I also don’t really see how either of them are a useful extension. It just makes it more likely to introduce bugs.

It was suggested on the defect report that perhaps we could do Warn-As-Error, and release-notes document that it will be going away in a future release.

Candidate patch up for review here; ⚙ D135287 Disallow dereferencing of void* in C++.

Set as a SFINAEError/Warning-as-default-error for now, we can make it a hard error in a few versions if this ends up not being controversial.

Somehow I put this in ‘runtimes’ instead of ‘clang’, so moved! Please comment if you have concerns!

I’m in favor of this change in theory, but have two practical situations I’m worried about. Dereferencing void * very rarely makes sense, but consider:

template <typename Ty>
std::remove_pointer_t<Ty> get_it(Ty *Ptr) {
  return *Ptr;
}

When you call this templated function with a void *, I believe it’s harmless to dereference the result to return it because the return type of the function is void: Compiler Explorer. AIUI, this situation comes up in generic programming from time to time.

The other situation is in unevaluated contexts. I don’t see the harm in allowing something like:

template <typename Ty>
auto func(Ty *Ptr) -> decltype(*Ptr) {
  ...
}

I don’t know how common this code is, but I have a hazy recollection of it being related to why we accept this, similar to how C++ accepts returning a void expression: [stmt.return]

For your first exmaple, Both GCC and MSVC reject that for the same reason: Compiler Explorer

Note that the remove_pointer_t part is fine, but the dereference in the return is important there. NOTE of course that the dereferenceable concept that motivated this patch is actually commonly used for exactly this situation!

For the second example, the same: GCC and MSVC already reject it: Compiler Explorer

While I also don’t see the harm of it, I don’t see value in allowing it if no one else does.

Not everyone worries about cross-compiler compatibility; if Clang users are relying on the behavior in reasonable circumstances, we should be supporting them.

That said, I still think it’s reasonable to see how well this behavior change goes over with our users. This search does not suggest to me that there’s considerable usage in the wild. It might be hard for us to test the patch ourselves because I’m not aware of any large Clang-only corpus of code that we can test against, but that would raise our confidence if you know of any.

So my feeling is (having seen that search result), let’s move forward with it and identify it as a potentially breaking change, and see how it goes.

I can get that… I’m less motivated to support those as non-standard.

Anyway, I’ll wait until at Monday AM to see if there are any other comments before doing anything.

I’m pretty sure this is always always a bug.
Ie, i don’t think in generic context we can establish a meaningful relation between void* and an object of type void like we do for any other T (both because “object of type void” is currently nonsensical and because a void* is not really a pointer to void, even in an unevaluated context, and we are misled by the spellings void and void*

I agree that void can be painful to deal with but I don’t think clang should try to solve that problem that way, it’s not portable and error prone.

I’d move ahead and reconsider if we find issues in real world scenario.

Thanks all! Patch submitted as 6685e56ceddf7c88eb3c39e838a8164941aade05

This seems very undesirable. It’s one thing to allow it in non-SFINAE contexts, where you just turn ill-formed code into well-formed, but this change the meaning of valid code in observable ways. The function above should result in a substitution error for func<void>.

GCC’s arithmetic on void* extension (and related sizeof void extension) is disabled in SFINAE and constraint checks, so the answer to “can you do arithmetic on void*?” is always no (even though actually you can do it).

1 Like

The point of GCC’s arithmetic on void extension is so you can just write ptr + n instead of (void*)((char*)ptr + n) i.e. use a void* as an arbitrary pointer to untyped memory, which is common in systems programming (c.f. caddr_t in old UNIXes). What is the result of adding 4 to the address 0x1234? Well it’s 0x1238 of course. That’s what you get when using char* or unsigned char* or byte*, but unlike those pointers, with void* you can’t dereference it (except with clang :wink:) and there’s no mistaking the result for a string of char or other character data. Arguably, using char* or unsigned char* as the general purpose “pointer to arbitrary memory” type makes less sense than using void*, but you’re forced to do that for any arithmetic, at least before we got std::byte.

I find it much harder to justify GCC allowing arithmetic on function pointers though!

2 Likes

@jwakely : We opted to NOT carve out the SFINAE exception there, so that IS now a substitution failure, where it wasn’t before: Compiler Explorer

This patch landed yesterday morning.

3 Likes