任何人都可以解释为什么这段代码会出错:
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'样式类实际上最适合解决方案。特别是如果风格警察不看的话,我甚至可以用C风格的宏包装它!
答案 0 :(得分:4)
这是因为在实例化Table时尚未实例化ConcreteTable类,因此编译器还没有看到T :: RT。我不确定C ++标准究竟是如何处理这种递归的(我怀疑它是未定义的),但是它没有达到预期的效果(这可能是好的,否则事情就会复杂得多 - 你可以用它表达一个逻辑悖论 - 就像一个const bool,如果它是真的那就是假的。)
使用typedef,我认为你不能指望将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 :(得分:1)
在完全定义类之前,您尝试使用类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:
...
};
答案 2 :(得分:1)
为什么不做这样的事情?
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
};
答案 3 :(得分:1)
问题在于ConcreteTable是根据Table定义的,但如果没有ConcreteTable的定义,则无法定义Table,因此您创建了一个循环定义。
在您设计类层次结构的方式中,似乎也存在潜在的问题。我猜你要做的是提供在Table的定义中操作通用记录类型的方法,但是将它留给ConcreteTable来定义记录类型是什么。更好的解决方案是使记录类型成为Table模板的参数,而ConcreteTable是直接子类:
class Record {};
template <class T>
class Table {
public:
typedef T RT;
};
class ConcreteTable : public Table<Record> {
};
现在你消除了循环依赖,并且仍然根据记录类型抽象表。
答案 4 :(得分:1)
请注意,编译器不允许您想要执行的操作。如果可能的话,你可以这样做:
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中使用。
解决方法需要有一个中间类来避免相互依赖,正如其他人已经说过的那样。
答案 5 :(得分:1)
实例化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();
}
答案 6 :(得分:1)
我认为其他人都已经很好地介绍了它,我只是想补充一点,我认为从自我模板继承然后尝试修补它以使其工作是不好的做法。我会采用不同的方法并将记录类型(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子类进行更多特化,可以始终覆盖Table中的方法,以及使用模板参数。