Why is it impossible to have a reference-to-void?
Asked Answered
H

10

55

Why is it impossible to have a reference to void? The only thing I found in the C++ Standard is this line, at 8.3.2.1

A declarator that specifies the type "reference to cv void" is ill-formed.

Why is it that way? Why can't I write a "generic" function that accept a void&?

Just to be clear, I have no useful application in mind where using a reference-to-void could be better than using templates, but I'm just curious about the rationale for forbidding this construct.


To clarify a little, I understand that using a reference-to-void "as is" would be as meaningless as dereferencing a pointer-to-void. However, I could cast it to a reference-to-sometype in order to use it, couldn't I? In fact, I don't see why the following snippet can work...

void foo(void *data)
{
    int *i = reinterpret_cast<int*>(data);
    // do something with i
}

...while this one cannot:

void foo(void &data)
{
    int &i = reinterpret_cast<int&>(data);
    // do something with i
}
Heall answered 19/1, 2009 at 15:9 Comment(0)
B
34

If you did have a reference to void, what would you do with it? It wouldn't be a number, or a character, or a pointer, or anything like that. Your hypothetical generic function couldn't perform any operation on it, except taking its address (and not its size).

"void" has two uses: to disclaim any knowledge of type (as in void *), and to specify nothing as opposed to something (void function return). In neither case is it possible to say anything about a void something except that it may have an address.

If you can't think of a way something can be useful, and I can't, that is at least evidence that something is useless, and that may well be at least part of the rationale here.

Bradshaw answered 19/1, 2009 at 15:26 Comment(8)
Well there IS actually something that you can do with a void reference, it's just something about syntax by the way: you can pass the object without dereferencing it with &, it's stupid but I don't like use a lot of & around, I found more clear using refsChopping
One use I would have is an enforce_dlopen function, which is guaranteed to not return a NULL-pointer (unlike dlopen). Unfortunately I can't express that fact in the function declaration.Lacylad
I think there is a use case for void references: unlike a void pointer (which can be a nullptr), a void reference would give you a guarantee that the reference actually refers to something, even if you cannot offer a static guarantee regarding what its type is.Inion
I actually have a case I could use this ;) But I was anyway gogling for it with the expectation of it probably beeing not possible, because there is no sense in it (almost).Meemeece
reference to void has a useful property of being used as a generic reference argument in a constructor. So you can have Foo a(anyReference) and it could internally convert it to a void pointer. It in turn allows you to pass references and the constructor can take care of casting it to a pointer for you. Sure it's far from idiomatic C++, but C++ is a multiparadigm language and things deemed dangerous in one place are a driving force in another. And as noted before, taking a void reference in a constructor is safer than taking a void pointer in a constructor.Screwdriver
If void& is useless there's no reason to forbid it. The standard is opinionated and introduces an unnecessary exception.Vernacularize
I don't see how this answers the question. How does OP's hypothetical function not work? Essentially it would provide the same functionality as void* but with the added certainty that it is not nullDives
There is definitely something useful you can do with it other than taking its address: you can pass it to dynamic_cast to cast it to a more useful type: MyType& foo = dynamic_cast<MyType&>(voidRef); That's the equivalent of what you could do with a void pointer, but you know the void reference isn't null (because it's a reference) and if you know that the void reference is supposed to be of MyType, you also know the dynamic_cast will throw an exception if your attempted typecast is illegal (a dynamic_cast of a pointer would just return null which you would then have to check for).Proposal
P
7

OK, one thing is bugging me about this. The idea of a void*, as mentioned above, is that you still have a valid variable containing an address, but the type is being ignored. This seems allowable since we can still work with the address data - the type is somewhat superfluous (or less important) in this context. Dereferencing it is bad, because to try and access a member doesn't make sense e.g. p.mem. We don't know what class to refer to, and thus the memory to jump to, the vtable pointers to follow.

However, it'd then seem to make sense that p on its own would be OK since it'd only refer to the object, but none of its data. No class information is needed to do so, just the address. I understand there's absolutely no use for this, but it's important in defining when things break down. Allowing this notion, a C++ reference (constantly dereferenced but not accessing anything) e.g. void& ref = static_cast< &void >(obj) also makes sense, and thus would allow void references. I'm not saying anyone should take it up with those in charge, but from a "making sense" point of view, it'd seem correct, no?

As Luc Touraille pointed out above (at least, this is my interpretation), it could be implemented, but the issue is a semantic one. The reasonable explanation I could come to was that since an object variable is a "tag" for a sequence of memory, the type is of important semantic value. Thus, the pointer, being thought of as a variable with an address value, treats the type as somewhat superfluous - not key to defining it.

Would anyone agree with that?

Pomfret answered 20/4, 2009 at 1:8 Comment(3)
Yes, I agree with it. I think it's the most correct answer in this question. Void means "type is unknown" not "points to nothing". "Points to nothing" is null, not void. You can't create an instance of void because its size is unknown, but you can have a pointer to void because even though its type (and hence its size) is unknown, its address can still be known. Having a reference to void could still be meaningful, although certain operations like sizeof() would have to be undefined or return some default answer like "1" (since C/C++ doesn't allow objects to have zero size).Proposal
this answer is correct and validPhosphine
I would like to take a void& something and get it "dynamic cast" to something else. So, a void reference could retain the typeid. I would be like if every virtual class descended from void.Hindermost
O
3

A reference is a reference to an instance of something. An instance of something can't be of type void. Any instance of something must have a specific type (and possibly base types).

Olga answered 19/1, 2009 at 15:15 Comment(5)
Although your statements are true, they are also irrelevant to the question being asked, because the type of a reference does not need to be the same as the type of the thing it refers to. For instance, in "BaseClass& baseRef = derivedClassInstance;", a reference with one type (a base class) refers to an instance of another type (a derived class). Having void references exist would not imply that the thing they are referencing also has type 'void'. This certainly is not the case with void pointers and the things they point to.Proposal
You wrote, "a reference does not need to be the same as the type of the thing it refers to ... a reference with one type (a base class) refers to an instance of another type (a derived class)" -- but I disagree, in that an instance of the derived type "IS" also an instance of the base type. https://mcmap.net/q/339522/-what-is-the-difference-between-quot-is-a-quot-relationship-and-quot-has-a-quot-relationship-in-java-duplicateOlga
What about "long x; char& y = *(char*)&x;" then? In general, a reference simply means that the thing the reference references should be accessed "as if" it were of the reference's type. This is not a guarantee that what is referenced actually IS the given type. Just that the compiler is allowed to assume it is when dereferencing it to access its memory. This is the same as how types of pointers (versus the types of the things they point to) work when dereferencing.Proposal
Consider that other types essentially act as if they are derived types of 'void': like normal class inheritance, the derived types add additional operations to their "base type" void, and a void pointer can point to anything without a typecast, and can be downcast to a more type-specific pointer if you want to do something with the thing it points to. There is no reason references to void couldn't have been treated the same way. Obviously you can't have an instance of void, but you could have a void reference that references an instance of something else, and it could be downcast similarly.Proposal
Where do you ever even use void* except in (deprecated) C-style memory functions like malloc and free? Users -- i.e. any code which has or which receives a reference -- generally want to know what (type) something is; and being able to declare opaque (forward-defined) types, and template types, gives other options that are type-safer than void*. Also you're thinking of references as if they were a type of pointer, but I'm not sure that was their purpose.Olga
P
3

Here are my five cents.

Each C++ expression is characterized by two independent properties: A type and a value category.

Suppose the code in the question is valid

void foo(void &data)
{
   //... 
}

Now one may ask the question, what is the type and the value category of the expression data? Naturally, it should be a lvalue of void type. Now, how about (void)5? Is it a prvalue of void type? How about std::move(data)? Should prvalue of void and xvalue of void be treated in fundamentally different ways? How about forming rvalue reference to void? This list just goes on and on.

In the case of pointer to void, none of these questions arise because if p is a pointer to void, then *p is not a valid expression according to the standard, and we don't need to worry about the type and value categories of *p. In the case of void reference, the corresponding statement has to be that the expression data itself is invalid, but if the expression itself is invalid, how can it be used in any reasonable way at all?

Protanopia answered 23/5, 2023 at 12:57 Comment(0)
V
1

Technically speaking, all that is guaranteed is that a reference to an object is an alias for it. That under the hood reference argument passing is done with pointers is an implementation detail. This can be confusing because of references reusing the & operator which is also address-of, but keep in mind that the operator actually has different meanings in different contexts (in a variable or parameter declaration it denotes a reference type, otherwise it's address-of, except when it's bitwise-and). Because it's technically just an alias for an object, a reference is 'always dereferenced' as Worrier explained.

Viper answered 19/1, 2009 at 16:16 Comment(3)
This is incorrect. Although a reference is an alias, as you say, and doesn't have to be done using pointers, as you say, taking the address of the reference is defined to mean taking the address of the thing it references (because the reference is an alias, so what else could it be?) which basically means you get a pointer to the original object as if the reference alias hadn't been there. A reference is "automatically dereferenced" in many contexts, but it is not "always dereferenced" and taking its address is a context in which the reference is definitely not dereferenced at all.Proposal
@SomeGuy Not sure which part you think is incorrect. I'm referring to the syntax for declaring references using &, which overlaps with the prior established use of & for taking addresses. I'm not saying there's any ambiguity about what applying & to an already existing reference does.Viper
@JosephGavin OK, I see the point you were trying to make, and I agree that the similarity in syntax could be a cause of confusion. Still, I think that the phrase "always dereferenced" is inaccurate and is more confusing than helpful: Even referring to an lvalue by name with no aliases involved doesn't necessarily dereference it. It depends on the context. In "int foo; int* bar=&foo;" foo has not been dereferenced (its memory has not been accessed). Only its address has been taken. Adding an alias in the middle ("int foo; int& bar = foo; int* baz=&bar;") doesn't change this.Proposal
H
1

Here's a summary of the different things that have been said, and that I've thought of.

Two main reasons why reference-to-void are disallowed


1 They would have been totally useless.

Indeed, if we look back at the times of C, void pointers had two purposes:

  • Memory management (e.g. malloc)
  • Genericity (writing functions that can accept any type of arguments)

When C++ came out, templates became the best solution to implement genericity. However, custom memory management still had to be possible, and interoperability between C++ and C was a major concern, so void* was kept. An hypothetical void reference would be of no help with memory management, and genericity is already covered, so basically it would have almost no use (except for the guarantee of non-nullness described below).

2 You wouldn't be able to do anything with it

When using a void pointer, you're not allowed to dereference it; transposed to the case of references, that means you can't use the (always hypothetical) void reference. So

void *data = // something
// using *data and data-> is forbidden

void &data = // something
// using data is forbidden

However, we could think of a use case where the reference wouldn't have to be "dereferenced" (this phrase is awfully incorrect, but you get my point), but where we would only take its address . Let's assume I have the following function:

void foo(void *dataptr)
{
    assert(dataptr != NULL); // or != 0
    // do something with dataptr
}

To avoid this annoying assert, I could write the function this way:

void foo(void &dataref)
{
    void *data = &dataref;
    // do something with data
}

However, for this to work, &dataref needs to be equivalent to dataptr, which is not the case: &dataref is equivalent to &*dataptr!

Therefore, even taking the address implies a dereferencing, at least conceptually (behind the scenes, the first equivalence is probably true, but at the semantic level it is not). Consequently, there is absolutely no usage we can make of data, so void references are an aberration.

Heall answered 19/1, 2009 at 17:38 Comment(3)
"&dataref is equivalent to &*dataptr" I am not sure I get you; do you mean here that &dataref is an rvalue, unlike dataref?Cabinetwork
@curiousguy: I meant that since a reference can be seen (like Binary Worrier noted) as a constantly dereferenced pointer, taking the adress of a reference is different from having the adress in the first place: conceptually, it is more like dereferencing an adress and taking the adress of the result, which implies a dereference, which is meaningless when dealing with void pointers.Heall
This is not true: taking the address of something, including a reference, does not imply dereferencing at all as this answer claims. That's not how C++ works. A reference may be "seen as" as a constantly dereferenced pointer for conceptual purposes, but that does not mean it actually is one. If I have "int x; int& intref=x" and I then take "int* intPtr=&intref" I have not dereferenced anything. I have just taken the address of whatever lvalue the reference was referencing ("x"). Dereferencing means actually accessing the memory the reference/pointer points to ("x"), which has not happened.Proposal
G
0

You can think of a reference as a de-referenced pointer. Syntactically you treat a reference as though it is not a pointer: you do not need the * operator to dereference it, and you can use . rather than -> to access its members.

However, you cannot dereference a void pointer. As pointed out by Binary Worrier trying to do that will give you a compiler error. And if you cannot have a dereferenced void pointer, that means you cannot have a void reference.

Gipps answered 19/1, 2009 at 17:6 Comment(1)
"you cannot dereference a void pointer." So the question is really: why can't you?Cabinetwork
C
0

If they were, they would be semantically non-differentiated from pointers, and would amount to syntactic sugar. A reference says, "I refer to something that is of this type." Allowing void or null reference would weaken that difference from pointers.

Granted, it's still possible for a reference to refer to an object that doesn't exist anymore, but that is an exception.

Concentric answered 19/1, 2009 at 18:39 Comment(1)
Actually, a reference says "I refer to something which should be treated as if it were this type when accessed via this reference", which is basically how pointers also work. For instance, consider "BaseClass& baseRef = derivedClassInstance;" where the type of the reference is not the same as the type of the object it references. References don't differ from pointers in the way you describe, and really they DO amount to syntactic sugar, except for the fact that references are required to be non-null and not reassignable, both of which are important for avoiding bugs.Proposal
H
0

The following is not a defense of the notion of void references. I offer it as an anecdote from the wild. Ask yourself if it doesn't smell funny.

My company was one of the first using C++ commercially, and initially compiled using Cfront. Early developers were still learning the language, and generally using every trick in the book (operators everywhere!). Here is a trick they thought was cool:

void Foo::something(int action, ostream &os = *(ostream *)0)
{
   ostream *os_p = &os;
   if (&os == (ostream *)0) {
      os_p = &cerr;
   }
   // continue with method
}

So here you have, not a void reference, but rather a typed reference with a potentially void binding! A moment's thought should probably suggest better alternatives to this particular idiom...

Heterozygous answered 20/4, 2009 at 2:51 Comment(1)
You seem to be confusing null with void. Null (represented by 0 in this example) is a value. Void is a type. They are two different kinds of things, like '42' vs. 'int'. Regardless, the C++ standard considers null references to be undefined behavior, and a conforming compiler could do anything it likes, causing demons to fly out of your nose, or more likely, optimizing the 'if' statement in your example away completely because references aren't supposed to be null.Proposal
T
-5

void is something that, by definition, doesn't exist, so it is not logical to have it's adress.

Trimetrogon answered 19/1, 2009 at 15:16 Comment(2)
doesn't plea for the existance of void* then, does it?Mollymollycoddle
I think you are confusing the value void with the type void (as in void* ). The title of the post didn't make this distinction either.Highland

© 2022 - 2024 — McMap. All rights reserved.