79608657

Date: 2025-05-06 12:03:34
Score: 2
Natty:
Report link

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.


What is reference?

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.


How it can be done?

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:

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

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

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


Extra credits.

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.

Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Me too answer (2.5): having similar issue
  • Contains question mark (0.5):
  • Self-answer (0.5):
Posted by: Danilo