可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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
DEMO
It cannot be done on a void type
#include <iostream>
#include <cstring>
using namespace std;
extern void f; //compile-time error
int main()
{
}
DEMO
回答1:
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, and void
is not an object type:
Section 3 [basic]
says that
A variable is introduced by the declaration of a reference other than a non-static data member or of an object.
Section 3.9 [basic.types]
says that
An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not a void
type.
回答2:
"void type is an incomplete type"
You can't create variables of any incomplete type
"...that cannot be completed"
While your example of extern
incomplete struct can be completed at some later point, the compiler knows that any declaration of type void
can never be completed.
回答3:
[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:
The name of a declared but undefined class can be used in an extern declaration. Such a declaration can only be used in ways that do not require a complete class type.
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:
A class that has been declared but not defined, an enumeration type in certain contexts (7.2), or an array of unknown size or of incomplete element type, is an incompletely-defined object type. [45] Incompletely defined object types and the void types are incomplete types (3.9.1). Objects shall not be defined to have an incomplete type.
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.
回答4:
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 in C
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.
回答5:
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 than void
should be the type of thing to which a zero-sized object points.
回答6:
void
is an incomplete type - you can only declare pointers to them and use them in function signatures. Obviously, extern Foo f;
is permitted because struct Foo
can be defined in another compilation unit (and if it's not the error will be detected by the linker), but void
can't ever be "defined" (and the compiler knows this, of course) so void
's quite special in this case.
回答7:
Well - 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:
#include <iostream>
#include <cstring>
using namespace std;
extern void f;
int main()
{
cout << (int &)f << endl; //cout 'f' as it was integer
}
struct {
int a;
double b;
} f{};
You can now actually do something similar with arrays:
#include <iostream>
#include <cstring>
using namespace std;
struct Foo;
extern int arr[];
int main()
{
cout << arr[2] << endl;
}
int arr[4]{};
Life example.