Ошибка определения типа шаблона - PullRequest
3 голосов
/ 31 января 2009

Может кто-нибудь объяснить, почему этот код выдает ошибку:

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

(по крайней мере, при компиляции с 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;
};

Что можно сделать, чтобы исправить это. Спасибо!

Обновление: Спасибо за указание на проблему и за все предложения. Этот фрагмент кода был основан на коде, который предоставлял точку расширяемости в существующей кодовой базе, и основной целью разработки было сокращение объема работы (набора текста), необходимого для добавления новых расширений с использованием этого механизма.

Отдельный класс стиля 'type traits' в действительности лучше всего подходит для решения. Тем более, что я мог бы даже обернуть его в макрос стиля С, если полиция стиля не смотрит!

Ответы [ 7 ]

4 голосов
/ 31 января 2009

Это потому, что класс ConcreteTable еще не создан при создании экземпляра Table, поэтому компилятор еще не видит T :: RT. Я не совсем уверен, как именно стандарт C ++ обрабатывает такую ​​рекурсию (я подозреваю, что она не определена), но она не работает так, как вы ожидаете (и это, вероятно, хорошо, иначе все было бы намного сложнее - вы могли выразить с ним логический парадокс - как const bool, который является ложным, если это правда).

Крепежный

С typedefs, я думаю, вы не можете надеяться на большее, чем просто передать RT в качестве дополнительного параметра шаблона, например,

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

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

Если вы не настаиваете на доступности RT как Table<>::Zot, вы можете поместить его во вложенную структуру

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

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

Или даже структура внешних черт

template <class T>
struct TableTraits<T>;

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

Если вы хотите, чтобы тип был только аргумент / возвращаемый тип метода, вы можете сделать это с помощью шаблона этого метода, например.

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

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

Смысл всех этих манипуляций состоит в том, чтобы отложить необходимость в T :: RT как можно позже, особенно до тех пор, пока ConcreteTable не станет полным классом.

1 голос
/ 31 января 2009

Я думаю, что все остальные достаточно хорошо это рассмотрели, я просто хотел добавить, что я считаю, что это плохая практика - наследовать от шаблона себя, а затем пытаться исправлять ошибки, чтобы заставить его работать. Я бы выбрал другой подход и в качестве параметров использовал бы тип записи (RT) вместо самой ConcreteTable. Если вы когда-либо смотрели на класс std :: iterator, он использует именно такой подход:

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;
  };

Когда подкласс наследует от итератора, он делает это:

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

Это именно то, что вы хотите сделать. Обратите внимание, что поля 'RecordType' на самом деле находятся в суперклассе и передаются через параметры шаблона. Это лучший способ сделать это, потому что он есть в стандартной библиотеке.

Если вы хотите повысить специализацию подкласса ConcreteTable, вы всегда можете переопределить методы из таблицы, а также использовать параметры шаблона.

1 голос
/ 31 января 2009

Когда создается экземпляр Table<ConcreteTable>, ConcreteTable все еще является неполным типом. Предполагая, что вы хотите придерживаться CRTP , вы можете просто передать Record в качестве дополнительного параметра шаблона, например:

class  Record
{
};

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

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

Также обратите внимание, что вы можете получить доступ к ConcreteTable как полному типу в любых функциях-членах в Table, потому что они создаются только позже при использовании. Так что это будет хорошо:

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();
}
1 голос
/ 31 января 2009

Обратите внимание, что то, что вы хотите сделать, не может быть разрешено компилятором. Если бы это было возможно, вы могли бы сделать это:

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

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

Что было бы своего рода бесконечным циклом определения типа.

Компилятор блокирует эту возможность, требуя полного определения класса, когда необходимо использовать один из его членов; в этом случае точка создания шаблона для Table (список предков в ConcreteTable) находится перед определением RT, поэтому RT нельзя использовать внутри Table.

Обходной путь требует наличия промежуточного класса, чтобы избежать взаимной зависимости, как уже говорилось другими.

1 голос
/ 31 января 2009

Проблема в том, что ConcreteTable определен в терминах Table, но вы не можете определить Table без определения ConcreteTable, поэтому вы создали циклическое определение.

Похоже, что при проектировании иерархии классов может возникнуть основная проблема. Я предполагаю, что вы пытаетесь обеспечить способы манипулирования общим типом записи в вашем определении Table, но оставляете за собой конкретную таблицу, чтобы определить, какой тип записи. Лучшим решением было бы сделать тип записи параметром шаблона Table, а ConcreteTable - прямым подклассом:

class Record {};

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

class ConcreteTable : public Table<Record> {
};

Теперь вы устраняете циклическую зависимость, и таблица по-прежнему абстрагируется в зависимости от типа записи.

1 голос
/ 31 января 2009

Почему бы просто не сделать что-то подобное?

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
};
1 голос
/ 31 января 2009

вы пытаетесь использовать класс CponcreateTable в качестве параметра шаблона до того, как класс будет полностью определен.

Следующий эквивалентный код будет работать нормально:

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:
...
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...