Cyclic includes / forward definitions always solvable?

J

John Doe

Hi all,
Regarding those cyclic dependencies of classes (like cases in which
class A refers to class B and class B to class A): can they ALL be
resolved by forward defining classes, and by implementing the actual
functions outside the class definition, OR there are STILL cases which
cannot be solved in this way and could require rewriting your code /
rewriting your class hierarchy?
 
L

Leor Zolman

Hi all,
Regarding those cyclic dependencies of classes (like cases in which
class A refers to class B and class B to class A): can they ALL be
resolved by forward defining classes, and by implementing the actual
functions outside the class definition, OR there are STILL cases which
cannot be solved in this way and could require rewriting your code /
rewriting your class hierarchy?

Resolving cyclic dependencies has nothing to do with where you put the
actual function implementations (including in the case of inline functions,
since they can be defined "ex-situ"); it does have to do with whether or
not the compiler has seen a "complete definition" of the class at the point
where that information is required. Typically, the information of interest
is the storage requirement for instances of that class.

Note that a class definition would be considered complete even if it
includes /pointers to incomplete types/.

As far as I know, it all boils down to this: you cannot write any
declaration or statement that requires the compiler to know how big a class
is before you've provided a complete definition of that class.
-leor

Leor Zolman
BD Software
(e-mail address removed)
www.bdsoft.com -- On-Site Training in C/C++, Java, Perl & Unix
C++ users: Download BD Software's free STL Error Message
Decryptor at www.bdsoft.com/tools/stlfilt.html
 
I

Ivan Vecerina

John Doe said:
Regarding those cyclic dependencies of classes (like cases in which
class A refers to class B and class B to class A): can they ALL be
resolved by forward defining classes, and by implementing the actual
functions outside the class definition, OR there are STILL cases which
cannot be solved in this way and could require rewriting your code /
rewriting your class hierarchy?

A workaround is always possible, but there are still some
tough cases that require an alteration of the design.


For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};

For the next C++ standard, I believe that it is being
considered to explicitly allow the the instantiation
of some standard containers (e.g. std::vector)
with incomplete types.
But there is no such guarantee currently.



Another classic problem is: how do you define a function f(void)
that returns a pointer to an f(void) ?

A workaround is to use an intermediate structure:
struct FP {
FP (*fp)(void);
};

FP f(void)
{
FP ans = {f};
return ans;
}

This idiom is actually useful in some implementations
of a state machine.



Regards,
Ivan
 
J

Jorge Rivera

Ivan said:
A workaround is always possible, but there are still some
tough cases that require an alteration of the design.


For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};

I don't particularly understand the usefulness of this example.

A is a strcuture that contains a list of B's, which in turns contains a
list of A's, which contains a list of B's, which conains a list of
A's....., you get the picture.

I think it IS a good thing that compilers don't allow such horrible
things...
 
A

Andrey Tarasevich

Jorge said:
I don't particularly understand the usefulness of this example.

A is a strcuture that contains a list of B's, which in turns contains a
list of A's, which contains a list of B's, which conains a list of
A's....., you get the picture.

I don't. In this case the list can be empty, which means that this
recursion is not necessarily infinite.

What you are saying is essentially the same as saying that functional
recursion is impossible because it never ends.
I think it IS a good thing that compilers don't allow such horrible
things...

I don't see anything horrible with it. It think you are mistaking this
situation with other situation, which employs direct aggregation

struct A { B b; };
struct B { A a; };
// Impossible and makes no sense

This is not the same as the previous example with 'std::list's, since
'std::list's implement indirect aggregation.
 
J

John Doe

A workaround is always possible, but there are still some
tough cases that require an alteration of the design.


For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};

This compiles just fine in VC++7.1 ...

For the next C++ standard, I believe that it is being
considered to explicitly allow the the instantiation
of some standard containers (e.g. std::vector)
with incomplete types.
But there is no such guarantee currently.

You mean that they are doing particular cases in the compiler like Java
is doing for Strings? That would be really bad. I hate that: a language
should be just a language and should treat all data types equally.

But anyway I don't think it's the reason for which it compiles. I tried
with my own template instead of std::string and it compiled on the same,
because the std::string basically just uses pointers to the classes, so
it just requires a forward definition of such classes in order to
compile correctly, right?



Another classic problem is: how do you define a function f(void)
that returns a pointer to an f(void) ?

A workaround is to use an intermediate structure:
struct FP {
FP (*fp)(void);
};

FP f(void)
{
FP ans = {f};
return ans;
}

This was GREAT code, unfortunately it does not compile.

I guess it is because in the implementation of f you are putting a
pointer to a global function, while the definition of FP requires a
__thiscall function pointer (a function member of a class, in which
"this" is passed secretely). I'm NOT sure though.

Is there any cast which can make the above code compile? I tried but I
wasn't able to do it.

Thanks
 
I

Ivan Vecerina

John Doe said:
This compiles just fine in VC++7.1 ...

But is not currently *guaranteed* to work ( => not legal C++ ).
Container implementations would be allowed, for example,
to store a root element within the class itself,
or may otherwise use the sizeof() of the provided
parameter to select a data member or specialization,
or whatever...

So the code is not (formally) portable, even though
it might work with all your compilers in practice.
You mean that they are doing particular cases in the compiler like Java
is doing for Strings? That would be really bad. I hate that: a language
should be just a language and should treat all data types equally.

I did not mean that this would only apply to std::vector.
The fact is: the code above works well with several implementations.
But the standard allows standard library implementations to break it
(e.g. it could be needed to allow some optimizations on some platforms).
So declaring the above code above legal requires a cost/benefit analysis,
and maybe it cannot apply to all containers (e.g. std::string,
which often stores a small buffer as a data member, would not work).
But anyway I don't think it's the reason for which it compiles. I tried
with my own template instead of std::string and it compiled on the same,
because the std::string basically just uses pointers to the classes, so
it just requires a forward definition of such classes in order to
compile correctly, right?
Right - in your implementation.
This was GREAT code, unfortunately it does not compile.
It compiles as is on Comeau++, which is considered by many as being
a reference in compliance (http://www.comeaucomputing.com/tryitout/).

What is the exact error message you are getting?
I guess it is because in the implementation of f you are putting a
pointer to a global function, while the definition of FP requires a
__thiscall function pointer (a function member of a class, in which
"this" is passed secretely). I'm NOT sure though.

The sample above does not involve any member functions, just
a data member that is of type pointer-to-function.
Is there any cast which can make the above code compile?
I tried but I wasn't able to do it.

Please post the exact code you tried and associated error message.
The snippet is 'kosher' as far as I can tell (although it is not
an example of clarity...).


Cheers,
Ivan
 
J

John Doe

Ivan said:
But is not currently *guaranteed* to work ( => not legal C++ ).
Container implementations would be allowed, for example,
to store a root element within the class itself,
or may otherwise use the sizeof() of the provided
parameter to select a data member or specialization,
or whatever...

??
In that case it's VERY RIGHT that it cannot compile, because it would
allocate an infinite-size structure due to recursion. Of course this is
off the topic of my initial question: I of course never intended to
compile impossible data structures.


The sample above does not involve any member functions, just
a data member that is of type pointer-to-function.

Really?
If you do

FP fp;
//....
*(fp.f)();

you think that the f function called would not get a this pointer?
I think it would!

Please post the exact code you tried and associated error message.
The snippet is 'kosher' as far as I can tell (although it is not
an example of clarity...).


Exactly your code.
The error I get is:

error C2440: 'initializing' : cannot convert from 'FP (__cdecl *)(void)'
to 'FP (__cdecl *)(void)'
Incompatible calling conventions for UDT return value

pointing at the line "FP ans = {f};"

I have seen that error before and it usually means that it cannot
convert a thiscall to a non-thiscall. And I agree with the compiler (see
above)
 
A

Andrey Tarasevich

John said:
Really?
If you do

FP fp;
//....
*(fp.f)();

This will not compile. Should be

(*fp.fp)();

or simply

fp.fp();
you think that the f function called would not get a this pointer?
I think it would!

No, it wouldn't. 'FP::fp' is declared as a pointer to ordinary
(non-member) function. It has nothing to do with any 'this'
Exactly your code.
The error I get is:

error C2440: 'initializing' : cannot convert from 'FP (__cdecl *)(void)'
to 'FP (__cdecl *)(void)'
Incompatible calling conventions for UDT return value

pointing at the line "FP ans = {f};"

Interesting. I don't get anything laike this even though I seem to be
using the same compiler as you.

Seeing that two lines of code that you posted above already contain some
errors, I wouldn't be surprised if it turned out that you tested one
piece of code and thought about another.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top