45

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

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • 12
    If the type has an empty set of values, what can you do with the variable? – Barmar Sep 15 '14 at 17:11
  • 3
    They key is "The void type is an incomplete type **that cannot be completed**", that's what makes it different than any other incomplete type. – K-ballo Sep 15 '14 at 17:33
  • 1
    @K-ballo: `struct Foo` is an incomplete *object type*. `void` is not an *object type*. That makes it different in a very fundamental way. – Ben Voigt Sep 15 '14 at 17:40
  • @BenVoigt: Well, why do you think `void` isn't an object type? – K-ballo Sep 15 '14 at 17:41
  • 5
    @K-ballo: Because the [definition of *object type*](http://stackoverflow.com/a/25853661/103167) says it isn't. – Ben Voigt Sep 15 '14 at 17:45
  • @BenVoigt: I guess I could have been clear... Why do you think the definition of object type says it isn't? It's because an object is a piece of storage, which is defined by a complete type. Since `void` can never be completed, there would be no layout associated to it, so there can never be storage for it, hence it's not an object. – K-ballo Sep 15 '14 at 17:47
  • The void type (if it were a type) would have one value. If you had a variable of type void, it would always hold exactly the same value and thus contain no information. Languages such as Haskell and Scala have void types (called () in Haskell and Unit in Scala) that have one value. Occasionally this is quite useful. – Theodore Norvell Sep 15 '14 at 17:49
  • @TheodoreNorvell: it *is* a type by C++'s definition of "type", though. Using any other meaning of the word "type" in the context of C++ (for example the meanings used by Haskell or Scala) is bound to be confusing even if it leads to your conclusion that under those circumstances void would have a value :-) – Steve Jessop Sep 15 '14 at 20:39
  • First and foremost, you can't declare a variable of type `void` because the spec pretty clearly says you can't (and what the spec says, whether sensical or not, is the law). Secondly, if you did manage to declare a variable of type `void` it would occupy zero bits, have no address, and be pretty much invisible. Even if you did manage to declare it there would be no way to prove it. – Hot Licks Sep 15 '14 at 20:48
  • I suspect there is some confusion because you *can* have variables of type `void*`. But `void` and `void*` are not related -- it's simply a "conservation" of reserved name space that the same letters are used for both. Kind of like using identical cast syntax for both scalars and pointers, even though the two concepts are only distantly related to each other. – Hot Licks Sep 15 '14 at 20:51
  • @HotLicks: [They are related, `void*` really is the combination of `void` and `*`], though I don't care to defend that decision. Personally, I think it would be better not to have `void*` and enable implicit conversion from any pointer type to `uintptr_t` instead (the reverse obviously is not implicit). But I suppose that would break the distinction between null pointer literals and pointers represented as the address `0`. – Ben Voigt Sep 15 '14 at 21:02
  • 1
    @BenVoigt: implicit conversion to `uintptr_t` would enable some weird expressions though, such as `ptr + 1.0`. Makes sense to me to have a type for "address of something unspecified", especially in C. But if you're going to make that an integer type I think you should require a cast in both directions. A pointer type that isn't spelled `void*` might satisfy all quibbles. – Steve Jessop Sep 15 '14 at 21:36
  • @Steve: I'm tired of having to convert `void*` to `char*` to do pointer arithmetic, so apart from the use of floating-point, I'd consider that an advantage. And one could easily add some `operator+(T*, double) = delete` overloads in 13.6. As well as `operator+(T*, U*) = delete`. Although I don't see why it would necessarily decay in those sorts of expressions. – Ben Voigt Sep 15 '14 at 21:40
  • 1
    @BenVoigt: if you regularly want to do pointer arithmetic on `void*` then in effect, to you pointers are numbers (that is: you don't think you should need to say the size of the thing pointed to in order to know how to increment the pointer, ergo you think of pointers as numeric addresses). That's fair enough in the relevant contexts, and is why GCC has that extension. It's just not the type-safe vision that C or especially C++ try to inspire in their followers ;-) – Steve Jessop Sep 15 '14 at 21:45
  • @Steve: Right, I wouldn't want implicit typeless pointer arithmetic to happen to typed pointers, but I should be able to do `uintptr_t p = some_typed_ptr; uintptr_t p2 = p+block_size;` where the operands already have the type erased. – Ben Voigt Sep 15 '14 at 21:47
  • 1
    Oh, FWIW the standard doesn't currently guarantee for a `const char *ptr = "hi";` that `(const char*)((uintptr_t)ptr+1) == ptr+1`. But specifying that is a very fringe concern, I can imagine an architecture that would naturally want to violate it but I don't know if any such has ever existed. Come to think of it, your proposal opens a hole in the `const` system though, since pointers to const and pointers to non-const would alike be converting to `uintptr_t`. What would be the signature of `memcpy`? – Steve Jessop Sep 15 '14 at 21:58
  • @SteveJessop: Oh yuck. Yes, you'd still need four different types for the different levels of cv-qualification. – Ben Voigt Sep 15 '14 at 22:40
  • 1
    There is a edge case (embedded firmware in my case) where you want to extract a value that the linker provides as a symbol. The value of this symbol is usually a pointer to an object, but the linker can just set a symbol to any integer (say "foo = 3"). In this case, if you want to get access to the linker symbol "foo", you need to typically go through some hoops such as `extern int foo; int myfoo = (int)&foo;`. In this case, "foo" is not really an integer, but you need some dummy object type to fool the compiler. It might be "nice" if you could do `extern void foo; int myfoo =(int)&foo;` – Mark Lakata Sep 15 '14 at 22:42
  • @SteveJessop You are right, it is a type. What I was trying to point out is that the standard is silly (or not logical) to say that it "has an empty set of values". It would be better to say that the void type is not associated with any set of values or that it is associated with a set containing one value (that you can not express or do anything with). – Theodore Norvell Sep 22 '14 at 19:30

7 Answers7

27

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    So? Where does 7.1 state that object types are necessary? – MSalters Sep 15 '14 at 18:09
  • @MSalters: 7.1 covers many different declarations. Object types are only necessary for declarations of objects. But I don't think Dmitry is claiming that `extern void f;` should declare a function or a reference. If he did claim that, it would be pretty straightforward to show that `void` is also not a function type and not a reference type. – Ben Voigt Sep 15 '14 at 18:46
  • @MSalters: Besides, I think it is section 8 that controls declarations. The first sentence "A declarator declares a single variable, function, or type, within a declaration." And from section 3: "A *variable* is introduced by the declaration of a reference other than a non-static data member or of an object" – Ben Voigt Sep 15 '14 at 18:47
  • The latter part is easiest. The fact that variables are introduced by a declaration doesn't mean that everything declared is a variable. Section 8 covers declarators, which are the "non-type parts" of a declaration. See 8/1. But the `void` here is a _type-specifier_, in particular a _simple-type-specifier_ (7.1.6.2). – MSalters Sep 15 '14 at 20:00
  • @MSalters: Right, and it could appear in (1) a typedef, (2) a function declaration, where it would serve as the return type and the declarator must contain `()`, (3) a reference declaration, where the declarator must contain `&`, (4) a variable declaration of pointer type, where the declarator contains `*`. The syntax definitely allows `void` to be a *type-specifier*. But the rules do not allow declaring a variable whose type is `void` (alone). – Ben Voigt Sep 15 '14 at 20:15
  • @MSalters: Not everything declared is a variable. Everything declared is a variable, function, or type (I already quoted section 8). OP is trying to declare a variable here. So the restrictions on variables apply. – Ben Voigt Sep 15 '14 at 20:17
  • That appears to be the missing link. I agree that all possible declarations are enumerated, so it has to be one of those listed. – MSalters Sep 15 '14 at 20:21
  • Why must an object be declared to have "object type"? – xskxzr Dec 30 '18 at 13:57
8

"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.

IronMensan
  • 6,761
  • 1
  • 26
  • 35
  • 4
    We can't _define_ an object of incomplete type, but can **declare** a variable of such types. http://coliru.stacked-crooked.com/a/55b2c751ba86478f –  Sep 15 '14 at 17:14
  • 3
    Note, that the post is not an answer what I'm looking for. Because I asked about declaration, not definition. Object definition of incomplete type denied by 3.9/5. –  Sep 15 '14 at 17:15
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.

MSalters
  • 173,980
  • 10
  • 155
  • 350
2

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.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Could you look at my updated answer. I clarified what exact I want to understand. –  Sep 15 '14 at 17:20
  • 2
    An `extern` declaration requires that one of the other modules being linked has a definition of the variable. But there can't be a module that defines a `void` variable, because what would it put in it? – Barmar Sep 15 '14 at 17:26
  • 2
    So my tag `struct my_tag {};` is similarly useless. Yet, I can define an object of its type, I can assign to it, it has a nonzero size etc. – dyp Sep 15 '14 at 18:09
  • What can you assign to it? – Barmar Sep 15 '14 at 18:43
  • 2
    A new "value"/object of the same type? `my_tag x; x = my_tag{};` – dyp Sep 15 '14 at 18:45
1

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.

Paul Evans
  • 27,315
  • 3
  • 37
  • 54
  • 1
    `you can _only_ declare pointers and use them in function signatures.` It is not strictly speaking right. Because I've cited an exaple which _declares a variable of incoplete type_. –  Sep 15 '14 at 17:24
  • @DmitryFucintv ok, but it *can never* be completely specified as `struct Foo;` can, so it's special that way – Paul Evans Sep 15 '14 at 17:27
  • @Yakk thanks, added something of that (hopefully) addresses that in the answer – Paul Evans Sep 15 '14 at 23:16
1

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.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 1
    I think this belongs on a discussion of the empty base class optimization; it has nothing to do with `void`. – Ben Voigt Sep 15 '14 at 22:42
  • 1
    @BenVoigt: When someone asks "why is X not allowed in language Y", the "direct" answer is almost always "because the spec for Y says so", but that's generally not very informative or interesting. I think it is more informative in many cases to examine whether X could be allowed in any useful way without causing problems; in many cases where something is forbidden, it is to to avoid at least some problems that would be caused by allowing it. – supercat Sep 15 '14 at 22:53
  • 1
    But all you prove is that `(sizeof f) >= 1`. Which can easily be satisfied by making `1 == sizeof (void)` just like is already done for `sizeof (empty_struct)`. – Ben Voigt Sep 15 '14 at 23:03
  • @BenVoigt: If the rules about object uniqueness had allowed for zero-sized types, and if the pointer-to-anything type was called something like `pointer` rather than `void*`, then a zero-size `void` could have a semantic meaning distinct from an empty structure even if the latter also had zero size. I don't see any useful semantic meaning for a `void` type that takes up space, though. – supercat Sep 15 '14 at 23:25
  • 1
    In other words, if you take all the behavior that `void` has now, and create new keywords for it, you could give `void` a brand new meaning, and change the language to make it useful. But how does this help us understand the `void` we have in the language we have? – Ben Voigt Sep 16 '14 at 01:44
  • @BenVoigt: The `void` keyword has multiple meanings. I don't really know what there is to "understand" about "the" `void`. More generally, C syntax and keywords are a thrown-together hodgepodge of fixes applied to a language that was thrown together to solve some particular programming problems. Trying to understand the `void` is like trying to understand the "unsigned", another keyword whose meaning depends upon context. – supercat Sep 16 '14 at 12:34
1

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.

AnArrayOfFunctions
  • 3,452
  • 2
  • 29
  • 66