CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 10 of 10
  1. #1
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    how to forward declare a class in a class ?

    If you have a class having a pointer to a not-yet defined class, you can forward declare that class. But how do you do this if the class is a class defined inside another class.

    Below is a sample of the problem, this is what I'd like to get compiling but now it doesn't. the

    Code:
    // Helper class.  Basically a wrapped typed pointer.
    template <typename Ty>
    class ConstPtr
    {
    public:
    	const  Ty* p;
    	const Ty& operator()() { return *p; }
    };
    
    class A
    {
    public:
    	class Item
    	{
    	public:
    		const char*			szName;
    		ConstPtr<B::Item>	b;	// <-- Problem
    	};
    
    	typedef std::tr1::array<Item,3> Array;
    	static const Array All;
    };
    
    class B
    {
    public:
    	class Item
    	{
    	public:
    		const char*			szName;
    		ConstPtr<A::Item>	a;
    	};
    
    	typedef std::tr1::array<Item,2> Array;
    	static const Array All;
    };
    
    const A::Array A::All = 
    {
    	{
    		{	"one",		&B::All._Elems[0], },
    		{	"two",		&B::All._Elems[1], },
    		{	"three",	&B::All._Elems[1], },
    	}
    };
    const B::Array B::All = 
    {
    	{
    		{	"red",		&A::All._Elems[0], },
    		{	"blue",		&A::All._Elems[1], },
    	}
    };
    Now the above is a simplified section of the problem, in reality I have several dozen of classes similar to A and B, with varying members in the Item classes.

    I can't seem to figure out how to tell the compiler that at the definition of the A class that B::Item is a valid class and taking a pointer of it is ok.
    if I change the ConstPtr<B::Item> to const void* the code compiles and "works" but I don't have a pointer of the right type in that case.

  2. #2
    Join Date
    Oct 2008
    Posts
    1,456

    Re: how to forward declare a class in a class ?

    well, you cannot access a member type of an incomplete type. That said, you can write

    Code:
    template <typename Ty>
    class ConstPtr
    {
    public:
    	const  Ty* p;
    	const Ty& operator()() { return *p; }
    };
    
    class A
    {
    public:
    	class Item;
    
    	typedef std::tr1::array<Item,3> Array;
    	static const Array All;
    };
    
    class B
    {
    public:
    	class Item;
    
    	typedef std::tr1::array<Item,2> Array;
    	static const Array All;
    };
    
    class A::Item
    {
    public:
    	const char*			szName;
    	ConstPtr<B::Item>	b;
    };
    
    class B::Item
    {
    public:
    	const char*			szName;
    	ConstPtr<A::Item>	a;
    };
    now, I'm 99.9% sure that std::tr1::array supports incomplete types ( this is not guaranteed for containers, in general; although it may work in vc anyway ... I mean, the static declaration cannot be a problem, but the typedef could ... ) but I've neither the compiler nor the standard at my disposal at the moment ...

  3. #3
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    Re: how to forward declare a class in a class ?

    And the 0.1 percentile wins.

    It doesn't work because countrary to other STL classes, array isn't a regular dynamic container, it's a wrapper around a regular C-style array. and as such, to define the static member, it needs to know the actual size of the Item subclass.

    All I need is a way to forward declare a typename in a typename and make the compiler assume that by the time it's needed for implementation things will have been properly declared I just need it for define a pointer, it doesn't even have to know when defining the A::Item what specific type B::Item really is. That's what forward declares are for afterall. :s

  4. #4
    Join Date
    Oct 2008
    Posts
    1,456

    Re: how to forward declare a class in a class ?

    Quote Originally Posted by OReubens View Post
    And the 0.1 percentile wins.

    It doesn't work because countrary to other STL classes, array isn't a regular dynamic container, it's a wrapper around a regular C-style array. and as such, to define the static member, it needs to know the actual size of the Item subclass
    no, sorry, maybe I didn't expressed myself well ... the point is that, yes, clearly you need a complete type to define a variable of type array<T,N> but, loosely speaking, a static variable is not defined at its point of declaration at class scope. The issue I was alluding to has nothing to do with that static variable.

    consider the following

    Code:
    struct A;
    struct B{ static A x; };
    struct A{};
    A B::x; // (1)
    this does compile because A must be a complete type only at (1).

    then, consider this

    Code:
    template < class T >
    struct X
    {
        T t;
    };
    
    struct A;
    struct B{ static X<A> x; };
    struct A{};
    X<A> B::x;
    that is similar to your use case and again compiles fine.

    The potential issue I was alluding to has to do with the possibility of the class template to require "indirectly" its parameter to be complete at the point of declaration of the variable.

    For example,

    Code:
    template < class T, class V = typename T::I >
    struct X
    {
        T t;
    };
    
    struct A;
    struct B{ static X<A> x; };
    struct A{ typedef int I; };
    X<A> B::x;
    this gives an error instead ( on the Comeau compiler ). Unfortunately, such bogus implementations cannot be excluded from the standard for STL containers, but I'm 99.9&#37; sure that an std::array<> ( being designed to behave as "true" array ) should not allow such pathological cases; this is what I meant by "array supports incomplete types" in my last post.
    Last edited by superbonzo; June 14th, 2012 at 05:05 AM. Reason: typos

  5. #5
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    Re: how to forward declare a class in a class ?

    I tried it... it doesn't work... because even though I declare the array as static, what's inside the std::array isn't...

    It's the same reason why I added the subclass in the first place. because...
    Code:
    class A
    {
    public:
       int x;
    
       static A a[3];  // This compiles and does basically the same as:
       static std::tr1::array<A,3> a_array;  // This doesn't compile
    };
    it doesn't know "yet" what the size of A is when you declare the static array. You keep getting an error about it:

    error C2148: total size of array must not exceed 0x7fffffff bytes
    see reference to class template instantiation 'std::tr1::array<_Ty,_Size>' being compiled
    .....
    error C2079: 'std::tr1::array<_Ty,_Size>::_Elems' uses undefined class 'A'

    The Item-subclass was intended to solve that and it does. but then I'm getting into a mess with the forward references of the subclasses.

    Using the array (or whatever) is the basic point of this project, because we want to to get the benefits of STL classes and as such the static/readonly data needs to behave as container classes.
    Last edited by OReubens; June 14th, 2012 at 11:43 AM.

  6. #6
    Join Date
    Oct 2008
    Posts
    1,456

    Re: how to forward declare a class in a class ?

    I see, although I'm not sure it's a legal compiler behavior ...

    note that the Comeau compiler does not report an error in this equivalent code:

    Code:
    template<class T,int N>
    struct X
    {
       T t[N];
    };
    
    class A
    {
    public:
       static X<A,3> a_array;
    };
    and the fact that VC reports a size > 0x7fffffff bytes may suggest a compiler bug ( how can it infer a size from that declaration ? at least the error message surely is a compiler bug ) or is it one of those border line cases where the compiler is free to raise an error or not ? Of course, the VC implementors could always argue that there's no standard guarantee for that to work in any case; nonetheless, IMHO it would be still a compiler bug because we should be allowed to at least implement our own version of std::array with such a guarantee using a code like the above.

    anyway, a possible solution may consist in exploiting the fact that the compiler is required to not implicitly instantiate static members of implicitly instantiated templates that are not required to exist in the context they are declared into ( yes I now, this standardese is quite incomprehensible ):

    Code:
    template<class Tag,class T,int N>
    struct X
    {
       static T t[N];
    };
    
    struct A
    {
       struct Tag{};
       X<Tag,A,3> a_array;
    };
    
    A X<A::Tag,A,3>::t[3];
    this compiles in VC too; then, you could (hopefully) write something similar to implement an std::array-like container that works with my code in post #2 ...

  7. #7
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    Re: how to forward declare a class in a class ?

    I have tried something like defining my own array class using a static member instead as in your 2Nd code sample.
    but that doesn't work either. I'm not so much needing an array of size X having specific types in it...


    THe key area is that the array must be fully formed/defined at compile/link time and must be present in a readonly section (preferably a namable one) in the resulting executable image.

    THe above while it works. doesn't allow you to also "fill in" the array at compile time. It only creates the array, you have to write code to stuff somethign in it.


    The whole plan here was to take a set of regular "oldschool" C structure arrays filled as const aggregates. and wrap it in a class that would allow the structures to be used like STL containers and working with the stuff in algorithms and boost and such.
    But it is quite critical everything remains set in stone at compile/link time.

    the first few attempts at this resulted in arrays being filled at runtime, which caused a HUGE slowdown at startup and made the resulting image way too big.

    It's an oddball type thing, but there's about 700Mb (yes) of data being linked into the executable image. It currently takes the compiler over an hour to chew through the "database.cpp" source.

    All the "old" code used DWORD ID's which were looked up with linear seeks when needed... My "genious idea" was to replace all those lookups with pointers to the referenced data making things a lot faster.
    Unfortunately the compiler has been fighting against me pretty much every step of the way on that process.

  8. #8
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    Re: how to forward declare a class in a class ?

    The "tag" had me puzzled for a while there as it didn't seem to do anything... It just occurred to me you're using this to make sure you can have different instances of the static data for same combinations of the actual datastructure (A) and size.

    The inability to have multiple instances of the static data for the type+size combo is one of the reasons why we ended up aborting the idea of rolling our own array class. (there's more of them)...

    Interesting way to work around that, it wouldn't have occurred to me to do it that way. Definately something to remember for something I might need it for in the future.

    And figuring that out... it also "solved" the puzzle for me how to enter the data as an aggregate in that case.
    It is terribly confusing syntax though
    Code:
    struct B; // Forward
    
    struct A
    {
    	const char* sz;
    	B* b;
    	int x;
    
       struct Tag{};
    };
    
    struct B
    {
    	const char* sz;
    	A* a;
    	int y;
    
       struct Tag{};
    };
    
    A X<A::Tag,A,3>::t[3] =
    {
    	{ "one", &X<B::Tag,B,3>::t[0], 1, },
    	{ "two", &X<B::Tag,B,3>::t[1], 1, },
    	{ "three", &X<B::Tag,B,3>::t[1], 1, },
    };
    
    B X<B::Tag,B,2>::t[2] =
    {
    	{ "red", &X<A::Tag,A,3>::t[0], 1, },
    	{ "blue", &X<A::Tag,A,3>::t[0], 1, },
    };
    While it solves the original "how to wrap static data in an array class" (with terrible syntax), it doesn't address the class in class dilemma that's still there.

    While an inability to solve that issue when we started this. Adding the "Item" subclass solved that as well, and as we went along it allowed us to do some pretty nifty template magic making using the data a lot easier without some of the messy syntax.

    The class in class is pretty much a given now. Just the forward references are still bothering us, we can work around it, but it's an obvious blemish on an otherwise very clean and comprehensible design
    Last edited by OReubens; June 15th, 2012 at 01:56 PM.

  9. #9
    Join Date
    Apr 2000
    Location
    Belgium (Europe)
    Posts
    4,626

    Re: how to forward declare a class in a class ?

    BTW...
    the a_array member in class A isn't needed.
    THe data is a static member of the X template. The a_array ends up just being an empty/dummy object which you could use an an alias.

    It would never be needed because once you have an instance of A (or pointer/treference thereof), you no longer care about the array of the whole

    Code:
    // get first element of the A array (talk about messy syntax :p)
    A& a = X<A::Tag,A,3>::t[0];
    // you could now use this instance to access another instance
    A& a2 = a.a_array.t[1];
    you always need that first step though, the 2nd makes little sense (at least in our case)
    Last edited by OReubens; June 15th, 2012 at 01:51 PM.

  10. #10
    Join Date
    Oct 2008
    Posts
    1,456

    Re: how to forward declare a class in a class ?

    >> It just occurred to me you're using this to make sure you can have different instances of the static data for same combinations of the actual datastructure (A) and size.

    right

    >> It is terribly confusing syntax though

    actually, it's even worse as I missed a "template<>" in my code in post #4 ( it turns out that VC allows its omission, by it's a non standard beahvior ), so it shoud be: "template<> A X<A::Tag,A,3>::t[3] = ...".

    moreover, there's a typo in your code snippet ( X<B::Tag,B,3> in place of X<B::Tag,B,2> ); ironically, correcting it makes it illegal ( an explicit specialization cannot precede its first use ) although it compiles fine in VC ...

    >> it doesn't address the class in class dilemma that's still there.

    well, I admit I'm a bit lost ... why can't you combine the solution in post#4 with the one in post #2 ? something like ( with no tag, assuming each A,B,... defines its own unique item ):

    Code:
    template <typename Ty>
    class ConstPtr
    {
    public:
    	const  Ty* p;
    	const Ty& operator()() { return *p; }
    };
    
    template <class Ty>
    struct StaticConstArray
    {
       typedef const typename Ty::Item Type[ Ty::Size ];
    
       static Type All;
    };
    
    struct A
    {
    	struct Item;
    	static const int Size = 3;
    };
    
    struct B
    {
    	struct Item;
    	static const int Size = 2;
    };
    
    struct A::Item
    {
    	const char*		szName;
    	ConstPtr<B::Item>	b;
    };
    
    struct B::Item
    {
    	const char*		szName;
    	ConstPtr<A::Item>	a;
    };
    
    StaticConstArray<A>::Type StaticConstArray<A>::All =
    {
    	{ "one", &StaticConstArray<B>::All[0] },
    	{ "two", &StaticConstArray<B>::All[1] },
    	{ "three", &StaticConstArray<B>::All[1] },
    };
    
    StaticConstArray<B>::Type StaticConstArray<B>::All =
    {
    	{ "red", &StaticConstArray<A>::All[0] },
    	{ "blue", &StaticConstArray<A>::All[1] },
    };
    the syntax is not that bad, IMHO. This compiles in VC ( thanks to the trick in post#4 that forbids the compiler to check the size of the static array as in post#2 ) but, strictly speaking, it's not legal, hence not portable ( it misses the "template<>" bits in front of the static arrays initializations and it uses an explicit specializations before being specialized ... ).

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured