Answering my own question after a bit of clarity for anyone stumbling onto this issue.
Subscript operator definition
The main thing is how operator[] implemented by default. Subscript operators have few versions:
// subscript operators
return_type& parent_struct::operator[](std::size_t idx);
const return_type& parent_struct::operator[](std::size_t idx) const;
Where major part to notice is the little '&' (ampersand) at return type (return_type
) which means that it is returned as reference which in this case can be constant or not.
So if we consider some variable (lets call it int myvar
) it has few ways it can be referenced:
int myvar = 3; // holds value of 3, at some stack given address
int *pointer_to_myvar = &myvar; // holds address of myvar, at some stack given pointer
int &ref_to_myvar = myvar; // is reference to existing myvar
int copy_myvar=myvar; // is copy of myvar
And if we change myvar
to 5, both myvar
and ref_to_myvar
will change values, but pointer_to_myvar
and copy_myvar
will be the same.
In case of copy_myvar
we have simply made a new variable and copied the value, once we have done copying it they become independent.
In case of pointer_to_myvar
it doesn't hold any value, but address of myvar
so, if myvar
changes, so will value stored at that address but address will be the same.
In case of ref_to_myvar
it is like having alias to existing variable (myvar
) so if anything changes to either address or value of myvar
it will change in reference as well.
So this is the case with subscript operators, and what they return. They return reference
to existing member (in this case instructions and memory) however said member can by anything. But the main issue here is that it must exist (by type) at least somewhere in the code before being referenced by operator.
When designing a class or struct we handle these "references" by different constructors and operators (which I haven't done in original question) to handle these types of handling. In this case foo
and bar
have no way to knowing what each other is because computer doesn't really care. Each type (even struct or class) is bunch of bytes of memory and we tell it how it will read it via struct declaration and definition. So simply because we might understand whats done, it doesn't mean computer does.
So for member must exist and we can do it in few ways:
Having global variable we will change every time we need to reference any member ( in this case struct bar{//code here}; bar ref;
and assign within subscript operator before returning reference to it. Issue with this approach is that we can't have multiple references to multiple parts of foo
, benefit is that in some cases (as in question) we don't need to in order to implement specific instruction onto specific memory address.
Having container struct or class (in this case foo
) be made of bar
objects so we can simply return specific bar
object that already exists in foo
. Issues with this approach is that we have to make sure we understand lifetime (or scope) of the object : or when is constructor and when is deconstructor called. Benefits is that we can manipulate different members of foo
and have no worries about will it mess something up - answer of TJ Bandrowsky
Having few local variable or instance of bar
within foo
that we will change when using subscript operator and keeping track of fixed references like ( in struct foo
we can have members of bar first
, bar second
...) so we can keep track of fixed amount of references if we need to. Issue and benefit here are same, that is limit to objects we can reference before accidentally overwriting some reference. For some niche cases it is a benefit for others its a fault.
In original Question I have made an reference by member (memory and instruction) but original struct bar
couldn't be referenced. So the main issue wasn't in implementation but in &
(ampersand) and what it meant. Struct bar
held correct memory of member of foo
and was in itself a reference, but it wasn't possible to reference it later. (The whole "we might know, but computer doesn't"). Based on Question doing approach 2 would be more suitable.
With all that being said, C++ doesn't limit us to just one way of doing things and we have complete freedom to do anything we wish. We can return pointer from subscript operator, we can return new instance of reference (as I tried in Question) and honestly possibilities are endless. To further more bring this to the end, we can take a look at answer from Spencer.
bar operator[]( int index){
return bar( this, index%16 ); // this, not *this
}
Where he simply removed reference ('&') from operator and returned new instance that in itself was a reference. Just because cpp reference suggest we should use reference in subscript operator it doesn't mean we have to. In this case this
is an pointer of the address to foo
instance, and by changing operator return type and using pointer (as used in bar
constructor) we can compile our code and it will work with added headache (issue to original design) of having to find a way to safely use bar
even if members of foo
change due to scope of the object.
For anyone having similar issues, Id suggest figuring out how to safely do rule of 5 before trying to use any fancy extra credit. Understanding the scope and life style of object is crucial for any kind returns or output parameters.