I'm looking for a formal explanation of that fact in the Standard. I've found what 3.9.1/9 says and trying to give an explanation used that section.
Section 3.9.1/9, N3797:
The void type has an empty set of values. The void type is an incomplete type that cannot be completed. 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 (5.4). 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, noexcept, 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.
I don't understand how it implies from the fact that the void type has an empty set of values?
Suppose that type T has an empty set of values. Why does compiler throw an error when it come across the following line:
extern T v;
We can decalre a variable of incomplete type in the following way:
#include <iostream>
#include <cstring>
using namespace std;
struct Foo;
extern Foo f; //OK!
int main()
{
}
and it works fine
It cannot be done on a void type
#include <iostream>
#include <cstring>
using namespace std;
extern void f; //compile-time error
int main()
{
}
You cannot declare a variable of type
void
because variables must have object type or be references,extern void f;
doesn't declare a reference, andvoid
is not an object type:Section 3
[basic]
says thatSection 3.9
[basic.types]
says thatWell - I really don't see the rationale behind this. It's going to be great if this way we can declare a variable with unknown type. Something like 'void *' and arrays of unknown size. Imagine code like this:
You can now actually do something similar with arrays:
Life example.
[edit] The answer below makes valid observations, but they're contradicting. As these might be valuable, I'll not delete them, but see Ben Voight's answer and the comments there for a more straightforward approach.
Your observations about
extern
declarations are specifically allowed by 7.1.1/8:void
is not a "declared but undefined class", and there's no other exception in 7.1.1 which applies.Additionally, 3.9/5 is fairly explicit that it is in fact allowed:
Emphasis mine. This part of the standard is quite specific about the differences between definitions and declarations, so by omission it specifies that declarations are allowed.
Because C and C++ assume that any objects may be compared for identity by comparing their addresses, they must ensure that all objects have fixed non-zero size. Were it not for that requirement, there are in fact many cases where it would be somewhat useful to declare zero-sized objects [e.g. in code which uses templates which contain fields that will sometimes be useful and sometimes not, or as a means of forcing a structure to be padded to a certain alignment requiring that it contain an element requiring such alignment]. As it is, however, zero-size types would be inconsistent with fact that the rule specifying that every object has a unique address includes no exception which would allow for the existence of zero-sized objects that could share an address.
Even if zero-size objects were permissible, however, a "pointer to unknown object" should not be the same as a "pointer to a zero-size object". Given that the type
void*
is used for the former, that would imply that something else should be used for the latter, which would in turn imply that something other thanvoid
should be the type of thing to which a zero-sized object points.void
is an incomplete type - you can only declare pointers to them and use them in function signatures. Obviously,extern Foo f;
is permitted becausestruct Foo
can be defined in another compilation unit (and if it's not the error will be detected by the linker), butvoid
can't ever be "defined" (and the compiler knows this, of course) sovoid
's quite special in this case.If the variable has an empty set of values, it can't be used for anything.
You can't assign to it, because there are no possible values to assign.
You can't access it, because you never assigned to it, so it has an indeterminate value.
Since there are no possible values, there's no size of the variable.
void
is just used as a placeholder in variable places. It's used as a return type to indicate that the function doesn't return a value. It's used inC
in the argument list to indicate that the function takes no arguments (to resolve an ambiguity from the pre-prototype version of the language). And it's used with pointer declarations to create generic pointers that can be translated to any other pointer type. There's no such analogous use for it in variable declarations.