0

I want to provide an subscript operator structure for the struct I am writing. I plan to do this with two structs foo and bar. Code is as follows:

#include <iostream>
struct foo;

struct bar{
    uint32_t *mem;
    uint32_t *opcode;

    bar():mem(nullptr),opcode(nullptr){}
    bar( foo *f, int index ){
        this->mem = f->memory + (index%16);
        this->opcode = f->instructions +(index%16);
    }
    operator bool(){ return (this->mem != nullptr) & (this->opcode != nullptr); }
};
std::ostream& operator<<( std::ostream& os, bar &t ){
    if( t ){
        return os << "bar:" << (*t.mem) << "\t-\t" << (*t.opcode);
    }else{
        return os << "bar: NOT INITIALIZED";
    }
}

struct foo{

    uint32_t *memory = new uint32_t[16]();
    uint32_t *instructions = new uint32_t[16]();

    foo(){}

    ~foo(){
        delete[] this->memory;
        delete[] this->instructions;
    }

   bar &operator[]( int index){
      return bar( *this, index%16 );
   } 

};
std::ostream& operator<<( std::ostream& os, foo &f ){
    for( int i =0 ; i < 16; i++ ){
        os << f.memory[i] << "\t" << f.instructions[i] << "\n";
    }
    return os;
}

I am using CygWin[x86_64] and Notepad++ as main compiler and editor on Windows 7 64 bit.

I have tried a lot of permutations on my own to fix this problem, but I would like to show the following:

bar( foo *f, int index ){
    this->mem = f->memory + (index%16);
    this->opcode = f->instructions +(index%16);
}

and 

bar( foo *f, int index ){
    this->mem = f->memory[index%16];
    this->opcode = f->memory[index%16];
}

'f' is incomplete type error, with note that I have used forward declaration.

bar( foo *f, int index ){
    this->mem = f->memory[index%16];
    this->opcode = f->memory[index%16];
}

two forward declarations notes, and two invalid use of incomplete type struct foo on this->mem = f->memory[index%16] and this->opcode = f->memory[index%16];


I have tried a bunch of other stuff but it seems I have mostly an issue with incomplete type. I have searched SO for answers, and one did explain what is incomplete type, other issue was about recursive definition and this one doesn't define how to make an incomplete type complete.

I am hung on this for past several days, going trough iterations for simple operator overloading. Maybe I am phrasing it wrong in questions, or searching for wrong answers.

But can someone point out my mistakes and/or write how to overload array subscript operator with code and not just body less functions?

4
  • 2
    I made an answer but deleted it because I noticed there are a lot of other things that will break so to get on the right track: Skip the manual memory allocation. uint32_t *memory = new uint32_t[16]; should be uint32_t memory[16]; etc. Commented Nov 6, 2019 at 19:45
  • Where'd you put your "terminal" at in the above code. Can you post a more complete sample? Commented Nov 6, 2019 at 19:46
  • @TJBandrowsky Yeah terminal was remnant of minimal executable example. When I realised that I typed foo / bar in question I just renamed it. Commented Nov 6, 2019 at 19:50
  • 1
    @Danilo Already edited that for you. Commented Nov 6, 2019 at 19:51

3 Answers 3

4

Basic design problems (which don't relate to your question) aside, there are two three things that keep this from compiling:

  1. The body of the constructor bar::bar(foo*,int) is defined inline, and uses members from foo. Since foo isn't defined yet (it's an incomplete type), the compiler chokes because it doesn't know about the members of foo yet.

  2. When you call the constructor, you pass in a foo &, not a foo *.

  3. foo::operator[] returns a non-const reference to a temporary, which some compilers might accept, but is just plain wrong. (spotted by Ted Lyngmo)

The following code compiles (https://godbolt.org/z/_F_ZpJ):

#include <iostream>
struct foo;

struct bar
{
    uint32_t *mem;
    uint32_t *opcode;

    bar():mem(nullptr),opcode(nullptr){}
    bar( foo *f, int index );
    bar (bar const &) = default;  // add default copy constructor
    operator bool(){ return (this->mem != nullptr) & (this->opcode != nullptr); }
};


struct foo{

    uint32_t *memory = new uint32_t[16]();
    uint32_t *instructions = new uint32_t[16]();

    foo(){}

    ~foo(){
        delete[] this->memory;
        delete[] this->instructions;
    }

   bar operator[]( int index){
      return bar( this, index%16 ); // this, not *this
   } 

};


bar::bar( foo *f, int index )   // moved the definition down here
{
     this->mem = f->memory + (index%16);
     this->opcode = f->instructions +(index%16);
}

std::ostream& operator<<( std::ostream& os, bar &t )
{
    if( t ){
        return os << "bar:" << (*t.mem) << "\t-\t" << (*t.opcode);
    }else{
        return os << "bar: NOT INITIALIZED";
    }
}


std::ostream& operator<<( std::ostream& os, foo &f )
{
    for( int i =0 ; i < 16; i++ ){
        os << f.memory[i] << "\t" << f.instructions[i] << "\n";
    }
    return os;
}

As I already said, there are some basic design problems and you should think about redesigning this code. Something like the code at this link is better. Better yet, ditch foo altogether use the refactoring suggested in TJ Bandrowsky's answer.

Sign up to request clarification or add additional context in comments.

11 Comments

Would you mind answering why did you choose to put constructor bellow the foo struct?
bar &operator[]( int index) was one of the things I commented in my now deleted answer. It compiles and will probably end in disaster. Edit: Actually, it doesn't compile: godbolt.org/z/oIJ5U2
@Danilo As I said in the post, foo is an incomplete type, You can't refer to members of foo until after the struct is defined.
@TedLyngmo Agreed.
@Danilo If you are going to continue down this rabbit hole, at least read What is The Rule of Three? and The rule of three/five/zero.
|
0

I'd consider refactoring the thing to something like:

bar {
uint opcode, mem;
}

then make your machine model a std:vector and it would be a lot simpler.

8 Comments

What is machine model? I have tried using std::vector ... I had bad time referencing it since (and I've read this on SO somewhere) it can not be changed.
Well, it looks like you are trying to write something that emulates assembler, so a list of your bars would be like a program. Therefor std::vector<bar> instructions. Then you just loop through instructions ...the tell tale sign is that you are using way too many pointers for modern C++. Instead of trying to roll your own collections, use the ones they have.
@Danilo What do you mean by "it cannot be changed"? You can insert, delete, and update individual values of a vector....
@Spencer. I tried to implement subscirpt operator with 1D std::vector instead of 2 arrays but, when I searched for the issue I was having (on some questions several days ago) it said that reference to item in vector cant change the value of vectors item.
@TJBandrowsky yeah, assembler is close to what I want to do, but every memory is tied to instruction, no looping over programs - just bouncing out variables between memories and their instructions. That is why I need to hold in bar opcode and memory, since I can then use operators >>, << and = to set,get memory and assign instruction.
|
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.