Click to See Complete Forum and Search --> : Generic programming


ltcmelo
July 26th, 2006, 07:32 AM
Hi.
I have a code that doesn't compile. And I know the reason it doesn't. I'd like to ask if anyone has a workaround or some special trick to make it work.


template <class T>
class A
{
typedef typename T::MyInt MyInt;
};

template <class T>
class B : public A<B<T> >
{
public:
typedef int MyInt;
};

template <class T>
class C
{
public:
typedef int MyInt;
};

int main(int argc, char* argv[])
{
// This compiles fine, which is natural.
C<A<int> > c;

//This does NOT compile, since by the time A is defined, B has not
//defined the tipe MyInt yet, which is also easy to see. Because B inherits
//from A.
B<int> b;
}


If anyone has an idea that would make this 'inheritance' code compile, please let me know. I know there are other options to structure the templates. However, I really need that classes A and B are defined as described above.


Thanks.

marten_range
July 26th, 2006, 08:07 AM
However, I really need that classes A and B are defined as described above

Why?

If you need the classes to be exactly like defined in your post that makes it difficult to change anything in order to make it compile right?

If you could fill in the details in what you try to accomplish perhaps the someone here could offer a reasonable alternate path.

TomWidmer
July 26th, 2006, 08:07 AM
You ask how to fix it, but then say you can't change A or B. Without changing A or B, it is obviously impossible to fix your problem.

Imagining for a moment that you can modify A or B, you can:
1. Pass the type as an additional template parameter to the A template (e.g. class B: public A<B<T>, int>).
2. (best solution) Modify the A template do that it doesn't need T to be complete in its definition, but only in the definition its member functions. (e.g. template <class T>
class A
{
void f(){
typedef typename T::MyInt MyInt;
//use MyInt
}
};
3. Add a level of indirection via a traits class or similar:template <class T, class Traits>
class A
{
typedef typename Traits::MyInt MyInt;
};

class BTraits
{
public:
typedef int MyInt;
};

template <class T>
class B : public A<B<T>, BTraits>
{
public:
typedef BTraits::MyInt MyInt;
};

NMTop40
July 26th, 2006, 08:24 AM
What you are doing makes no sense to me:

// This compiles fine, which is natural.
C<A<int> > c;

It would look like this one might not compile, as A<int> is invalid. (You cannot then define A::MyInt as int::MyInt does not exist).

The compiler may have ignored that because it isn't used anywhere, in fact T is not used anywhere in C so it may have stripped it out completely.


//This does NOT compile, since by the time A is defined, B has not
//defined the tipe MyInt yet, which is also easy to see. Because B inherits
//from A.
B<int> b;

I am more surprised that this one does not compile as B<int>::MyInt is defined, and although A is constructed first, B is well defined. What confuses me is why B is inheriting from A when all that A has is a private typedef.

It looks like you are looking for a traits class. TomWidmer above has provided a possible solution though it's not clear if it's what you are looking for.

traits classes (and overloaded functions) provide a sort of compile-time polymorphism for templates where you want to do the same things but with different types you do them a different way. Sometimes there is a default behaviour and you have to specialise for the specific types that behave differently. Sometimes there is no default behaviour (in which case if you use a type that does not have a defined overload you will get a compile-time error).

traits classes and overloaded functions are extensible, i.e. when you write your own class you can then define the traits for your class.

ltcmelo
July 26th, 2006, 08:35 AM
You ask how to fix it, but then say you can't change A or B. Ok, sorry for that. Its because this is some already existent code that I can't change (at least right now). I'd like to make some small refactors on it. That's why I said A and B must remain the same. But the truth is that if changes (in A or B) are minor, I might be able to do it.


Without changing A or B, it is obviously impossible to fix your problem.So, are you 100% sure. I've seen some very interesting 'hacks' with c++ templates...

Anyway, your sugestions are valid, but not possible in my case. Basically, because in the real situation class B has some other template parameters (user defined data). This makes things even more complicated because some of the types are 'dependent' of others and so on... The second solution is actually the best and would be very ok, however I really need that types for member variables in that class.

What I was looking for that is some kind of 'forward type declarations'. I know such thing doesn't exist, but that's why I mentioned that some very weird hacks might exist for that. But I agree with you... without changing A and B this seems to be imossible (at least till now).

Thanks.

ltcmelo
July 26th, 2006, 08:47 AM
It would look like this one might not compile, as A<int> is invalid. (You cannot then define A::MyInt as int::MyInt does not exist). Yes you're right. I apologize for that. I must say that the code I posted is not real. I just tried to simulate a situation I have in the real application I'm working. So, classes A and B are a lot bigger and more complete than that. When I made this 'translation' for the post here, I left some details out (and forgot of others).

I am more surprised that this one does not compile as B<int>::MyInt is defined, and although A is constructed first, B is well defined. What confuses me is why B is inheriting from A when all that A has is a private typedef. Yes, at first I also thought that this would also compile for the same reason. But I tried in a couple of compilers (including comeau online) and it didn't work. I think is because although B is well defined, it's not yet defined by the time A is constructed/defined.
About, the class A having only a typedef, as I said this is just for the posting. In the real situation, A is a complete class.


It looks like you are looking for a traits class. TomWidmer above has provided a possible solution though it's not clear if it's what you are looking for. Once more, you're right. All of the types are 'traits types'. Actually all the reall classes A and B are based on traits, tags definitions, compile time assertions and a whole other bunch of generic programming techniques. But again, I only posted what I thought to be necessary for solving the problem (in other words, if I have that structure problem solved, I can 'translate' it to my application easily).

Thanks.

TomWidmer
July 26th, 2006, 09:55 AM
Ok, sorry for that. Its because this is some already existent code that I can't change (at least right now). I'd like to make some small refactors on it. That's why I said A and B must remain the same. But the truth is that if changes (in A or B) are minor, I might be able to do it.This doesn't really make sense. B doesn't compile, so how can it be part of existent code? Presumably you've made a change to B that has caused this problem in the first place, so what was the purpose of this change, and what was B like before this? We might have an alternative solution.

Graham
July 26th, 2006, 09:55 AM
Your A/B hierarchy is using the "Curiously Recurring Template" pattern. The name of this pattern should give a clue as to why it can't work - recursion. Since B derives from A, you could get into the situation where A is trying to access something that it defines (via B). Result: recursive definition.

For example, let's modify your original slightly:

template <class T>
class A
{
public:
typedef typename T::MyInt MyInt;
};

template <class T>
class B : public A<B<T> >
{
public:
};

In this case, A's definition of MyInt comes from B's definition, as you intended, but B's definition is inherited from A, which gets it from B<T>, which inherits it from A......

Traits classes. More people should be aware of them.

ltcmelo
July 26th, 2006, 09:59 AM
B doesn't compile, so how can it be part of existent code? No, you're not correct on that comment. B is not the problem. What is the problem is the typedef definition in A, which is exactly I'm trying to add in the already existent code. Right now, the real A class does NOT have that type definitions.

TomWidmer
July 26th, 2006, 10:18 AM
Ok, I think I get you now - you're trying to add some member variables to A (via the typedef) that are of types defined in B. And you're at liberty to change A as much as you like as long as it doesn't break B? Is that a reasonably synopsis?

Well, you can't hold the members directly in A, but you can use something like void*, boost::any, or a similar concoction, where the type of the member variable declared in A is independent of T, but resolved to the correct type when it is used in member functions of A.

ltcmelo
July 26th, 2006, 10:49 AM
Ok, I think I get you now - you're trying to add some member variables to A (via the typedef) that are of types defined in B. And you're at liberty to change A as much as you like as long as it doesn't break B? Is that a reasonably synopsis?

Yes, pretty much this... actually, I can make minor changes to B, as long it doesn't break the whole thing.

ltcmelo
July 26th, 2006, 10:57 AM
For example, let's modify your original slightly:

template <class T>
class A
{
public:
typedef typename T::MyInt MyInt;
};

template <class T>
class B : public A<B<T> >
{
public:
};

In this case, A's definition of MyInt comes from B's definition, as you intended, but B's definition is inherited from A, which gets it from B<T>, which inherits it from A...

Graham, sorry but I think I didn't get how your sugestion would work (basically, because I don't get how MyInt will be instantiate in A). Haven't you forgotten of anything? Can you post an example of use?

Graham
July 26th, 2006, 02:20 PM
Graham, sorry but I think I didn't get how your sugestion would work (basically, because I don't get how MyInt will be instantiate in A). Haven't you forgotten of anything? Can you post an example of use?
It won't work, that's the point. All I did was to change the access control on the typedef in A and remove the one from B. This is functionally equivalent to what you originally posted, except that the typedef the A is trying to access actually comes from A itself (via B), rather than being independently defined by B. The point being that using something from B could have come from A itself, and so we get the recursion that makes the general case of what you're trying to do problematic.

It's like mutally-composing class pairs: you have to break the recursion by introducing at least one pointer. In this case, you break the recursion by introducing a traits class.

ltcmelo
July 26th, 2006, 03:37 PM
Well, thanks everyone. I'll consider your comments.