void
is a bizarre wart in the C++ type system. It's an incomplete type that cannot be completed, and it has all sort of magic rules about the restricted ways it can be employed:
A type cv
void
is an incomplete type that cannot be completed; such a type has an empty set of values. It is used as the return type for functions that do not return a value. Any expression can be explicitly converted to type cvvoid
([expr.cast]). An expression of type cvvoid
shall be used only as an expression statement, as an operand of a comma expression, as a second or third operand of?:
([expr.cond]), as the operand oftypeid
,noexcept
, ordecltype
, as the expression in areturn
statement for a function with the return type cvvoid
, or as the operand of an explicit conversion to type cvvoid
.
(N4778, [basic.fundamental] ¶9)
Besides the itchy feeling about all those strange rules, due to the limited ways it can be used it often comes up as a painful special case when writing templates; most often it feels like we would like it to behave more like std::monostate
.
Let's imagine for a moment that instead of the quotation above, the standard said about void
something like
It's a type with definition equivalent to:
struct void { void()=default; template<typename T> explicit void(T &&) {}; // to allow cast to void };
while keeping the void *
magic - can alias any object, data pointers must survive the roundtrip through void *
.
This:
- should cover the existing use cases of the
void
type "proper"; - could probably allow the removal of a decent amount of junk about it spread through the standard - e.g. [expr.cond] ¶2 would probably be unneeded, and [stmt.return] would be greatly simplified (while still keeping the "exception" that
return
with no expression is allowed forvoid
and that "flowing off" of avoid
function is equivalent toreturn;
); - should still be just as efficient - empty class optimization is nowadays supported everywhere;
- be intrinsically compatible on modern ABIs, and could be still special-cased by the compiler on older ones.
Besides being compatible, this would provide:
- construction, copy and move of those empty objects, eliminating the special cases generally needed in templates;
- bonus pointer arithmetic on
void *
, operating as forchar *
, which is a common extension, quite useful when manipulating binary buffers.
Now, besides the possibly altered return values of <type_traits>
stuff, what could this possibly break in code that is well-formed according to current (C++17) rules?