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
voidis 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 cvvoidshall 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 areturnstatement 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
voidtype "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
returnwith no expression is allowed forvoidand that "flowing off" of avoidfunction 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?