Forward declarations aren't allowed any more?

A

Adam Nielsen

Hi all,

I've recently upgraded my compiler and some of my code no longer works.
I put it through the Comeau online compiler and it came up with the
same errors, so I'm guessing my code is wrong.

I always thought that if you had a recursive object (where two objects
refer to each other) all you had to do was put in a forward declaration
so the compiler knew what was going on. Now it seems this no longer
works. Could anyone please enlighten me as to the correct way I should
be doing this?

The code below demonstrates the problem - it won't compile.

Many thanks,
Adam.

---

struct B;

struct A
{
B getB(void)
{
return B();
}
};

struct B
{
A getA(void)
{
return A();
}
};

int main(void)
{
A a;
B b;

A ba = b.getA();
B ab = a.getB();

return 0;
}
 
A

Adam Nielsen

Move the definition of A::getB to somewhere after struct B.
I bet that's what your compiler is telling you also.

It was, but I'm still not clear how to do that. My example was perhaps
a bit simplistic (I see what you mean with my example code) but my real
code uses templates, so it's not quite as easy to move the definitions
around (see below.)

I'm a bit reluctant to start throwing "extern" and "export" keywords
around, as it seems that compiler support may not be that great yet
(http://www.parashift.com/c++-faq-lite/templates.html#faq-35.12)

The code below gives the same error, and if I move the definitions out
into their own .cpp files as is I get linker errors.

Is there a standard (and well supported) way to avoid the linker errors?

Thanks again,
Adam.

--- test.cpp ---

#include "a.hpp"
#include "b.hpp"

int main(void)
{
A a;
B b;

A ba = b.getA<A>();
B ab = a.getB<B>();

return 0;
}

--- a.hpp ---

#ifndef __A_HPP__
#define __A_HPP__

struct A;

#include "b.hpp"

struct A
{
template <class T>
B getB(void)
{
return B();
}
};

#endif

--- b.hpp ---

#ifndef __B_HPP__
#define __B_HPP__

struct B;

#include "a.hpp"

struct B
{
template <class T>
A getA(void)
{
return A();
}
};

#endif

---
 
A

Adam Nielsen

Is this now your "real" code, or is your "real" code something third again?
I don't think anyone in this group is real happy chasing phantoms when
trying to help someone.

If you want help then post real code, not made-up examples that you
think resemble real code.

I'm sorry about that, I do realise how annoying it is to offer solutions
and then discover the wrong question was asked, but it was an attempt to
offer clear, easy to read code to make it quicker for everyone to
comprehend - I'm sure everyone here is very busy, so I try to save them
time by making my example code concise.

In this case I accidentally over-simplified, so I apologise for that. I
am not an expert, after all.

This is of course not my "real" code, as the particular code in question
is over 600 lines and won't compile unless there are another dozen or so
files of similar length - I'm sure nobody here has the desire to read
through that!

The latest example code I posted is, to the best of my knowledge, an
accurate representation of the problem. A function template in two
non-template classes access each other's class. The only difference in
my "real" code is that the reference is in the form of a function call
on a parameter, rather than a return value, however as far as I'm aware
that doesn't make a difference in this particular case.
This is extremely bad. "b.hpp" should be self-contained. It should not
require client code to make any forward declarations before the #include.

"b.hpp" *is* self contained. You will notice that it pulls in a.hpp (to
declare struct A) if required, double-underscore macros aside. Client
code can include just one of either a.hpp or b.hpp (or both) and it will
work as expected. These two files are part of the same "library" if you
will, so I believe their interdependence is acceptable, especially
considering the client code does not need to treat them any differently.

If you have any suggestions for a better way of implementing this while
keeping the declaration of A and B separate, I'd be very keen to know!

Cheers,
Adam.
 
F

Federico Zenith

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Adam said:
I always thought that if you had a recursive object (where two objects
refer to each other) all you had to do was put in a forward declaration
so the compiler knew what was going on. Now it seems this no longer
works. Could anyone please enlighten me as to the correct way I should
be doing this?

The code below demonstrates the problem - it won't compile.

Many thanks,
Adam.

---

struct B;

struct A
{
B getB(void)
{
return B();
^^^
There's your problem: you are telling the compiler to build a B without
having told it how to do it yet.

Try this (compiles with g++ 4.1.2):

- ---

struct B;

struct A
{
B getB();
};

struct B
{
A getA(void)
{
// no problem, the compiler already knows how to build an A
return A();
}
};

// Ok, now the compiler knows how to build a B
B A::getB()
{
return B();
}

int main(void)
{
A a;
B b;

A ba = b.getA();
B ab = a.getB();

return 0;
}
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.5 (GNU/Linux)
Comment: Using GnuPG with SUSE - http://enigmail.mozdev.org

iD8DBQFIjsfPBIpu+y7DlLcRAtDTAJ4tcGoNY1/v6202A9ebkZRta65XygCffoO8
hLXrtSwrdhAg4KSV9pitk8Y=
=XlYN
-----END PGP SIGNATURE-----
 
A

Adam Nielsen

Hi Federico,
^^^
There's your problem: you are telling the compiler to build a B without
having told it how to do it yet.

Yes, you're right - thanks for your reply. I'm used to dealing with
pointers (trying to switch to references) so I had forgotten about that.
Try this (compiles with g++ 4.1.2):

Yes, that works for me too. Unfortunately I neglected to mention that
the functions are actually template functions, so although your solution
still works with template functions, I'm unsure how to take it a step
further and separate the implementation of the template functions off
into a separate file - I keep getting linker errors.

I'm aware of a few potential workarounds, but they seem to be
compiler-specific (or not implemented.)

Cheers,
Adam.
 
B

Bo Persson

Adam said:
Hi Federico,


Yes, you're right - thanks for your reply. I'm used to dealing with
pointers (trying to switch to references) so I had forgotten about
that.

Yes, that works for me too. Unfortunately I neglected to mention
that the functions are actually template functions, so although
your solution still works with template functions, I'm unsure how
to take it a step further and separate the implementation of the
template functions off into a separate file - I keep getting linker
errors.

You can still use Federico's solution for templates, but you might
have to keep it all in the same file.

The basic idea is to have the "return B()" in a place where the
compiler actually knows what a B is (not just that it is some class).


Bo Persson
 
J

James Kanze

You can still use Federico's solution for templates, but you might
have to keep it all in the same file.
The basic idea is to have the "return B()" in a place where
the compiler actually knows what a B is (not just that it is
some class).

There is one trick that might be usable. (Note that I've never
actually tried this, so buyer beware.) Make the actual function
a template, e.g.:

template< typename T >
class A
{
public:
template< typename U >
U get() { return U() ; }
} ;

Of course, the client code will then have to write a.get<B>(),
rather than a.getB(). (And of course, B will have to be fully
defined before the client code calls A::get<B>().)
 
R

Richard Herring

Alf P. said:
* Adam Nielsen:
[..]
}
--- a.hpp ---
#ifndef __A_HPP__

Symbols with double underscore are reserved for the implementation.
I wouldn't be surprised if that symbol was automatically generated _by_
the implementation, or at least the associated IDE. In which case...
You have UB.

.... only when you try to compile under a _different_ implementation.
 
J

James Kanze

* Richard Herring:
Alf P. said:
* Adam Nielsen:
[..]
}
--- a.hpp ---
#ifndef __A_HPP__
Symbols with double underscore are reserved for the
implementation.
I wouldn't be surprised if that symbol was automatically
generated _by_ the implementation, or at least the
associated IDE. In which case...
You have UB.
... only when you try to compile under a _different_ implementation.
He he, that's a very good argument: always hold the door open
for the possibility that some non-portable code is generated
by the C++ implementation.

Which raises the question: what constitues the "C++
implementation"? I'd tend to exclude code generating Wizards
and such, but who knows. If the Wizard is designed to generate
OWL or OLE or whatever, you can't really require that what it
generates be "portable C++".
Funny I didn't think of that.
I think, though, it must qualify as a red herring. <g>

Now, now. No McCarthyism, please:). I'm sure you don't really
think that Richard is a communist.
 

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

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top