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 cv void
([expr.cast]).
An expression of type cv void
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 of typeid
, noexcept
, or decltype
, as the expression in a return
statement for a function with the return type cv void
, or as the operand of an explicit conversion to type cv void
.
(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 for void
and that "flowing off" of a void
function is equivalent to return;
);
- 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 for char *
, 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?
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…