tags:

views:

482

answers:

7

Can anyone explain why this code gives the error:

error C2039: 'RT' : is not a member of 'ConcreteTable'

(at least when compiled with VS2008 SP1)

class  Record
{
};

template <class T>
class Table
{
public:
    typedef typename T::RT Zot; // << error occurs here
};

class ConcreteTable : public Table<ConcreteTable>
{
public:
    typedef Record RT;
};

What can be done to fix it up. Thanks!

Update: Thanks pointing out the issue, and for all the suggestions. This snippet was based on code that was providing an extensibility point within an existing code base, and the primary design goal was reducing the amount of work (typing) required to add new extensions using this mechanism.

A separate 'type traits' style class actually fits into the solution best. Especially as I could even wrap it in a C style macro if the style police aren't looking!

+1  A: 

you are trying to use class CponcreateTable as a template paramter before the class is fully defined.

The following equivalent code would work just fine:

class  Record
{
};


template <class T>  Table
{
public:
    typedef typename T::RT Zot; // << error occurs here
};


class ConcreteTableParent
{
public:
    typedef Record RT;
};


 class ConcreteTable: public Table<ConcreteTableParent>
{
public:
...
};
Raz
+1  A: 

Why not just do something like this?

class  Record
{
};

template <class T>
class Table
{
public:
    typedef typename T Zot;
};

class ConcreteTable : public Table<Record>
{
public:
    typedef Record RT;  //You may not even need this line any more
};
Pesto
Great minds think alike -- you posted essentially the same answer while I was typing up mine.
Tony
That's completely what I would do, absolutely.
Ray Hidayat
+1  A: 

The problem is that ConcreteTable is defined in terms of Table, but you can't define Table without a definition of ConcreteTable, so you've created a circular definition.

It also looks like there may be an underlying problem in the way you are designing your class hierarchy. I am guessing what you are trying to do is provide ways of manipulating a generic record type in your definition of Table, but leaving it up to ConcreteTable to define what the record type is. A better solution would be to make the record type a parameter of the Table template, and ConcreteTable a direct subclass:

class Record {};

template <class T>
class Table {
public:
    typedef T RT;
};

class ConcreteTable : public Table<Record> {
};

Now you eliminate the circular dependency, and Table is still abstracted based on the type of record.

Tony
+4  A: 

That's because the class ConcreteTable is not yet instantiated when instantiating Table, so the compiler doesn't see T::RT yet. I'm not really sure how exactly C++ standard handles this kind of recursion (I suspect it's undefined), but it doesn't work how you'd expect (and this is probably good, otherwise things would be much more complicated - you could express a logical paradox with it - like a const bool which is false iff it is true).

Fixing

With typedefs, I think you cannot hope for more than passing RT as additional template parameter, like this

template <class T, class RT>
class Table
{
public:
    typedef typename RT Zot;
};

class ConcreteTable : public Table<ConcreteTable, Record>
{
public:
    typedef Record RT;
};

If you don't insist on RT being available as Table<>::Zot, you can put it inside a nested struct

template <class T>
class Table
{
public:
  struct S {
    typedef typename RT Zot;
  };
};

class ConcreteTable : public Table<ConcreteTable>
{
public:
    typedef Record RT;
};

Or even external traits struct

template <class T>
struct TableTraits<T>;

template <class T>
struct TableTraits<Table<T> > {
  typedef typename T::RT Zot;
};

If you only want the type be argument/return type of a method, you can do it by templatizing this method, eg.

void f(typename T::RT*); // this won't work

template <class U>
void f(U*); // this will

The point of all these manipulations is to postpone the need for T::RT as late as possible, particularly till after ConcreteTable is a complete class.

jpalecek
+1  A: 

Notice that what you want to do can't be allowed by the compiler. If it was possible, you would be able to do this:

template <class T>
class Table
{
public:
    typedef typename T::RT Zot; 
};

class ConcreteTable : public Table<ConcreteTable>
{
public:
    typedef Zot RT;
};

Which would be a kind of type-definition-infinite-loop.

The compiler blocks this possibility by requiring a class to be fully defined when one of its members needs to be used; in this case, the point of template instatiation for Table (the ancestors list in ConcreteTable) is before the definition of RT, so RT can't be used inside Table.

The workaround requires having an intermediate class to avoid the mutual dependence, as others already stated.

Fabio Ceconello
+1  A: 

When Table<ConcreteTable> is instantiated, ConcreteTable is still an incomplete type. Assuming you want to stick with CRTP you could just pass Record as an additional template parameter like:

class  Record
{
};

template <class T, class U>
struct Table
{
    typedef U RT;
};

struct ConcreteTable : Table<ConcreteTable, Record>
{
};

Also note that you can access ConcreteTable as a complete type in any member functions in Table because they are instantiated only later when used. So this would be ok:

struct  Record
{
};

template <class T>
struct Table
{
    void foo()
    {
      typedef typename T::RT Zot;
      Zot a; // ...
    }
};

struct ConcreteTable : Table<ConcreteTable>
{
    typedef Record RT;
};

int main()
{
    ConcreteTable tab;
    tab.foo();
}
Chestal
+1  A: 

I think everyone else has covered it pretty well, I just wanted to add that I think it's bad practice to inherit from a template of self and then try to patch things round to make it work. I would take a different approach and have the record type (RT) as the parameters instead of ConcreteTable itself. If you've ever looked at the std::iterator class, it uses this exact approach:

template <class Category, class T, class Distance = ptrdiff_t,
      class Pointer = T*, class Reference = T&>
  struct iterator {
    typedef T         value_type;
    typedef Distance  difference_type;
    typedef Pointer   pointer;
    typedef Reference reference;
    typedef Category  iterator_category;
  };

When a subclass inherits from iterator, it does this:

struct ExampleIterator : std::iterator<std::forward_iterator_tag, Example>

Which is exactly what you want to do. Notice that the 'RecordType' fields are actually in the superclass, and passed in through template parameters. This is the best way to do it, it's in the standard library because of it.

If you want to do more specialisation of the ConcreteTable subclass, you can always override methods from Table, as well as using the template parameters.

Ray Hidayat