Suppose I want to create an unmodifiable linked-list (i.e. it can only be traversed, no nodes can be added or removed once it was initially created). This could be easily implemented by:
struct ListNode
{
int value;
ListNode* nextNode;
}
My question is .... Would it be possible to use references instead of pointers?
struct ListNodeWithRefs
{
int value;
ListNodeWithRefs &nextNode;
}
I am not sure it would provide any performance gain at all but ... this question popped up while coding and my answer so far is no but I could be missing something.
In principle, nothing prevents you from using references, and constructing list elments like this:
ListNodeWithRefs::ListNodeWithRefs(ListNodeWithRefs &next):
nextNode(next)
{}
But there is a chicken and egg problem because next
also enforces its next
element to exist at its creation and so on ...
Note: I think my question can also be applied to defining the list as:
struct ListNodeConst
{
int value;
const ListNode* nextNode;
}
No. Reasons:
That was quite tricky but this worked :
Take a look at this example by sbi, it seems to work: https://stackoverflow.com/a/3003607/1758762
This is typical of a cons-list in functional languages:
The trick is though,
List a
is a full type and can refer either toEmpty
OR another node (which is why it can terminate).In order to achieve this in C++, you could take advantage of either a union (but it's not that well supported) or of polymorphism.
And then:
And now, you don't have a chicken & egg problem any longer; just start from an instantiation of
EmptyList<T>
.Note: the presence of
_kind
in the base class is not that OO, but it makes things closer to the functional example by tagging which alternative is used.How does the list end?
You will need at least two types: the end and not. You also need lifetime management. And either runtime or static knowledge of which type.
A completely static implementation could be done, where each node is its own type that knows how far it is to the end.
Or you could just have an unintialized buffer, and create elements off it in reverse order.
A circle is also possible. Make the first reference refer to the last element you construct.
As @Vlad said, the problem with references is that you will need a final object. The good news is that, in principle, you can still have a cyclic list, if you have a use for it. This is a fundamental thing, if the "next" element is a non-nullable reference means that there is always a next element, that is, the list is either infinite or, more realistically, it closes on itself or into another list.
Taken the exercise further is quite interesting and strange. Basically the only thing that seems to be possible is to defined the equivalent of the a node (which also represents the list).
The elements have to live in the stack and the list is static (well, references are not rebindable anyway) and the links have to be
const
references! Eventually thevalue
can be made thenmutable
apparently (probably safe?). (At this point one wonders how is this different from a stack array references by a modulo indices.)There is only one way to construct the
node
/list object, that is to close it with itself (or with other preexising node). So the resulting list are either circular or "rho" shape.I wasn't able to make a node that is not trivially constructible (aggregate?). (Adding any sort of basic constructor produced segmentation faults for a reason I can't understand, both in
gcc
andclang
). I wasn't able to encapsulate the node in a "container" object for the same strange reason. So making an object that could be constructed like this was impossible to me:Finally, since a proper container cannot be constructed it is not clear what is the semantics of this object, for example when two "lists" are equal? what doesn't mean to assign? or assign between list of different sizes?
It is a quite paradoxical object, with no general value or reference semantics apparently.
Any comments or improvements are welcomed!