• Advertisement
Sign in to follow this  

depandancy and forward declarations

This topic is 3210 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have got the follwoing to work;
//A.h

#pragma once

#include "B.h"
class B;

class A
{
public:
	A();
	~A();

	B* bPointer;

	int getID();
	void func();
};

//B.h

#pragma once

#include "A.h"
class A;

class B
{
public:
	B();
	~B();
	
	A* aPointer;

	int getID();
	void func();
};

#include "A.h"

A::A(void)
{
}

A::~A(void)
{
}


int A::getID(){
	return 20;
}

void A::func(){
	bPointer->getID();
}
#include "B.h"

B::B(void)
{
}

B::~B(void)
{
}

int B::getID(){
	return 20;
}

void B::func(){
	aPointer->getID();
}
But what if i wanted to use the A and B variables in the above classes by value? such as;
//A.h

#pragma once

#include "B.h"
class B;

class A
{
public:
	A();
	~A();

	B b;

	int getID();
	void func();
};

and
//B.h

#pragma once

#include "A.h"
class A;

class B
{
public:
	B();
	~B();
	
	A a;

	int getID();
	void func();
};

How would that work? even when included the headers, i still get the error that the values cant be defined.

Share this post


Link to post
Share on other sites
Advertisement
That can't work. If an A contains a B and a B contains an A, then an A would have to contain an A by value, which means that A would need to be infinitely large.

Share this post


Link to post
Share on other sites
The maximum you can get is to make them references, e.g.

class A;
class B {
A &a;
};


It is interesting that the unknown size of class A (and vice versa) does lead to more implications, like you cannot declare an array of class A as long as A is not defined, or use the sizeof operator. See this. Basically, you can't do anything that requires the compiler to know the exact size of the underlying type (whereas it allways knows the size of a pointer/reference).

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
That can't work. If an A contains a B and a B contains an A, then an A would have to contain an A by value, which means that A would need to be infinitely large.

Or zero in size! (yes yes, that is a purely theoretical snark that doesn't apply in this situation)

Share this post


Link to post
Share on other sites
Quote:
Original post by NotAYakk
Quote:
Original post by SiCrane
That can't work. If an A contains a B and a B contains an A, then an A would have to contain an A by value, which means that A would need to be infinitely large.

Or zero in size! (yes yes, that is a purely theoretical snark that doesn't apply in this situation)


Not blessed by standards, I think.

Share this post


Link to post
Share on other sites
Quote:
Original post by maya18222
#include "B.h"
class B;

Why do you include B.h, and then forward declare B? That's kind of redundant. You should include B.h in A.cpp instead, and A.h in B.cpp. That way you decrease the number of includes in your header files, which is a good thing.

Share this post


Link to post
Share on other sites
(Note: this is a ridiculously theoretical discussion that might be complete clap-trap. Not responsible for any brain damage resulting from reading it.)

Quote:
Original post by phresnel
Quote:
Original post by NotAYakk
Quote:
Original post by SiCrane
That can't work. If an A contains a B and a B contains an A, then an A would have to contain an A by value, which means that A would need to be infinitely large.

Or zero in size! (yes yes, that is a purely theoretical snark that doesn't apply in this situation)
Not blessed by standards, I think.

So the idea would be something like this:

struct A {
B b;
};
struct B {
A a;
};


Both A and B would have size 1. (void*)&(b.a) == (void*)B(b) == (void*)&(a.b) == (void*)&(a.b.a.b.a.b.a.b.a.b).

So, assuming the rule that you need a complete class to instantiate an instance of a class was eliminated... would the above be legal?

sizeof(A) == sizeof(B) == 1.

sizeof(A) == sum(sizeof(contents of A)).

It passes the various POD tests (memory layout is indeed consistent). In fact, the POD test demands that &(a.b.a) == &(a) (or, more specifically:
A a;
B* b = reinterpret_cast<B*>(&a);
A* a2 = reinterpret_cast<A*>(b);
then:
a2 == &a
is true.

I'm not sure how this would actually be useful (if it was allowed). You could use it as a form of infinite type loop? Where each recursive function's call's choice is moderated by the type of a passed in substructure.

It is definitely pathological.

Share this post


Link to post
Share on other sites
Quote:
Original post by NotAYakk
(Note: this is a ridiculously theoretical discussion that might be complete clap-trap. Not responsible for any brain damage resulting from reading it.)

(Okay.)

Quote:

Quote:
Original post by phresnel
Quote:
Original post by NotAYakk
Quote:
Original post by SiCrane
That can't work. If an A contains a B and a B contains an A, then an A would have to contain an A by value, which means that A would need to be infinitely large.

Or zero in size! (yes yes, that is a purely theoretical snark that doesn't apply in this situation)
Not blessed by standards, I think.

So the idea would be something like this:
*** Source Snippet Removed ***
Both A and B would have size 1. (void*)&(b.a) == (void*)B(b) == (void*)&(a.b) == (void*)&(a.b.a.b.a.b.a.b.a.b).

Upon which you couldn't depend. As far as I am informed, different instances require different addresses when asked by operator&(T).

Or do you want to tell us that a member of a class' member should be that [outer] class? In that case, you should use a reference-type, not a value-type.

Quote:

It passes the various POD tests (memory layout is indeed consistent). In fact, the POD test demands that &(a.b.a) == &(a)

I don't understand what your whole post has to do with my answer ("size zero not required by standards") onto your statement.

Quote:

I'm not sure how this would actually be useful (if it was allowed). You could use it as a form of infinite type loop? Where each recursive function's call's choice is moderated by the type of a passed in substructure.

Some languages support lazy-evaluation, which leads to lists that have infinite size, but which are only unfolded to the element that is demanded. This allows for some elegant algorithms. Look at Haskell. Such algorithms could then for example be implemented in C++ using our hypothetical device.

Quote:
It is definitely pathological.

As often as you say "pathological", as often I fail to understand what you want to say with it. It does not seem to be internet slang either. Is that some kind of local slang where you live?

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnel
Quote:
Original post by NotAYakk
It is definitely pathological.

As often as you say "pathological", as often I fail to understand what you want to say with it. It does not seem to be internet slang either. Is that some kind of local slang where you live?


Try looking here. It's definitely not a local phenomenon.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Quote:
Original post by phresnel
Quote:
Original post by NotAYakk
It is definitely pathological.

As often as you say "pathological", as often I fail to understand what you want to say with it. It does not seem to be internet slang either. Is that some kind of local slang where you live?


Try looking here. It's definitely not a local phenomenon.


I see now that I was blind.... Added to standard set of dictionaries, thanks.

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnel
Quote:
Original post by NotAYakk
(Note: this is a ridiculously theoretical discussion that might be complete clap-trap. Not responsible for any brain damage resulting from reading it.)

(Okay.)

Upon which you couldn't depend. As far as I am informed, different instances require different addresses when asked by operator&(T).

Or do you want to tell us that a member of a class' member should be that [outer] class? In that case, you should use a reference-type, not a value-type.[/quote]
References are aliases. That isn't the same thing at all.

Suppose you have:

struct B {};
struct A {
B b;
};
A a;
assert( (void*)&a == (void*)&(a.b) );
Note that two instances of two different classes have different addresses. This is pretty much _required_ by the standard.

I'm just saying that we could have a pretty consistent model of C++ with the above happening, where two instances of the same type share the same address in limited cases: namely:

struct A;
struct A { A a; }

And I'm aware that the above isn't well formed C++ currently.

It probably isn't worth the effort to allow it either.

Quote:
Quote:
It passes the various POD tests (memory layout is indeed consistent). In fact, the POD test demands that &(a.b.a) == &(a)

I don't understand what your whole post has to do with my answer ("size zero not required by standards") onto your statement.

I am speculating on what happens if you are allowed to embed a structure A into a structure A.

Given certain constraints, the idea isn't meaningless.
Quote:
Quote:
I'm not sure how this would actually be useful (if it was allowed). You could use it as a form of infinite type loop? Where each recursive function's call's choice is moderated by the type of a passed in substructure.

Some languages support lazy-evaluation, which leads to lists that have infinite size, but which are only unfolded to the element that is demanded. This allows for some elegant algorithms. Look at Haskell. Such algorithms could then for example be implemented in C++ using our hypothetical device.

Note that I'm not storing actual data in this recursive structure, as it stands.

But you could, for example, create a repeating 3 phase recursive call like:

struct A;
struct B;
struct C;
struct A { B elem; }
struct B { C elem; }
struct C { A elem; }
template<typename T, typename func>
void three_step( const T& t, func f ) {
if(func(t)) return;
three_step( t.elem, f );
};


where the layout of the 'empty' struct A and it's contents determines what type is passed to the template parameter f in what order.

A more complex layout 'empty' struct, containing possibly recursive contents, could be used to create an even more arbitrary sequence of instructions together with a helper function.

...

To extend C++ to contain these idioms, you would have to do something like:
empty struct A;
to say that A is a struct that contains no data (all data members of A are also empty, but it can contain non-virtual methods and the like).

Then allow empty structs to be placed in C++ classes and structs by reference, and they don't take up any memory space. Arrays of such structs should also be empty.

Then:

empty struct TreeLayout;
empty struct RightChild { TreeLayout contents; }
empty struct LeftChild { TreeLayout contents; }
empty struct TreeLayout {
RightChild left;
LeftChild right;
};

becomes legal.

The technique might become useful by allowing you to define operations and algorithms on structured data using an empty struct. Then again, it probably wouldn't be useful.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement