当模板 class 成员使用 class 本身作为模板参数时出现未定义 class 错误
Undefined class error when template class member uses class itself as template parameter
下面的代码摘录是我难以理解的涉及 C++ 模板的问题的最小工作示例。事实上,代码编译得很好。如果 main
函数之外被注释掉的行被重新注释,我会收到以下与第 73 行相关的编译错误,即 C<A> c(A<C<A>>(3.0));
:
testing.cpp(61): error C2079: 'C<A>::b_' uses undefined class 'A<C<A>>'
testing.cpp(10): note: see reference to class template instantiation 'C<A>' being compiled
testing.cpp(73): note: see reference to class template instantiation 'A<C<A>>' being compiled
第 69 行,即 C<A> c(A<C<A>::this_class>(3.0));
在所有情况下编译。
#include <iostream>
#include <vector>
using namespace std;
template<class Z>
class A {
public:
// typedef typename Z::traits_type Traits;
A(double data = 2.0) : data_(data) {
cout << "Constructing A with data: " << data_ << endl;
}
double data() const {
return data_;
}
void setup(Z* z) {
z_ = z;
}
void doStuff() const {
// cout << "A's foo: " << Traits::foo() << endl;
cout << "Instance of Z's data_: " << z_->data_ << endl;
}
private:
double data_;
Z* z_;
};
//struct CTraits {
// static int foo() {
// return 1;
// }
//};
template<template <class> class B = A>
class C {
public:
typedef C<B> this_class;
// typedef CTraits traits_type;
C(const B<this_class>& b = B<this_class>()) : data_(4.0), b_(b) {
cout << "Constructing C using B with data: " << b_.data() << endl;
b_.setup(this);
}
void doStuff() const {
b_.doStuff();
}
private:
double data_;
friend class B<this_class>;
B<this_class> b_;
};
int main(int argc, char* argv[]) {
// The following line compiles regardless of whether all lines above that
// are commented are commented in or out.
// C<A> c(A<C<A>::this_class>(3.0));
// This will not compile if the lines above, outside of main, that are commented
// out are commented back in.
C<A> c(A<C<A>>(3.0));
return 0;
}
我的问题是,为什么 C<A> c(A<C<A>>(3.0));
行上方 main
之外的注释掉的行被重新注释掉时会导致编译错误?换句话说,什么变化导致编译失败?
此外,为什么 C<A> c(A<C<A>::this_class>(3.0));
行编译而 C<A> c(A<C<A>>(3.0));
行不编译?换句话说,使用 C<A>::this_class
作为 A
的模板参数比仅使用 C<A>
有什么特别之处?
我使用 clang++
重现了您的问题。
让我们从一个更简单的例子开始。保留您拥有的 class 定义,而是使用以下 main
:
int main(int argc, char* argv[]) {
A<C<A>> x(3.0);
}
编译失败——让我们找出原因(非正式地)。
编译器必须实例化 class A<C<A>>
。为此,它将 C<A>
作为模板参数 Z
插入到 A
中。然后它看到行typedef typename Z::traits_type Traits
,要求它实例化Z
,即C<A>
。在实例化 C<A>
时,它会将模板参数 B
设置为 A
,因此当它看到 B<this_class> b_
时,它必须实例化 A<C<A>>
。但这与我们一开始尝试实例化的 class 是一样的!这就是编译器给出错误的原因——它有一个 "incomplete type",因为编译器意识到它已经开始实例化该类型,但尚未完成。
这与您在定义时出错的原因相同:
class D {
D x;
};
现在回答问题的第二部分:为什么使用 this_class
可以解决问题?
要理解这一点,请考虑这个更简单的示例(再次使用与之前相同的 class 定义):
int main(int argc, char* argv[]) {
typedef C<A>::traits_type y;
A<C<A>> x(3.0);
}
这里发生的事情是 typedef
语句要求编译器提前实例化 C<A>
。在这样做的过程中,它像以前一样遇到 b_
变量并尝试实例化 A<C<A>>
,这再次将 C<A>
插入 A
作为模板参数 Z
。然后它看到行 typedef typename Z::traits_type Traits
,但此时它已经为 C<A>
计算了 typedef CTraits traits_type
,因此继续而不尝试再次实例化 C<A>
。
原因
总结以上讨论,您所看到的行为有两个原因。首先,即使您只需要 C<A>::traits_type
,编译器也会尝试实例化整个 C<A>
。其次,编译器在访问 C<A>::traits_type
(Z<A>::traits_type
) 时可以接受不完整的类型,但在 A<C<A>> b_
(B<this_type> b_
).[=49= 中实例化类型本身时则不行]
C<A> c(A<C<A>::this_class>(3.0));
起作用的原因是 C<A>::this_class
强制编译器提前实例化 C<A>
。
解决方法
一个可能的解决方法是提前显式实例化您需要的模板,以防止循环:
template class C<A>;
int main(int argc, char* argv[]) {
C<A> c(A<C<A>>(3.0));
}
下面的代码摘录是我难以理解的涉及 C++ 模板的问题的最小工作示例。事实上,代码编译得很好。如果 main
函数之外被注释掉的行被重新注释,我会收到以下与第 73 行相关的编译错误,即 C<A> c(A<C<A>>(3.0));
:
testing.cpp(61): error C2079: 'C<A>::b_' uses undefined class 'A<C<A>>'
testing.cpp(10): note: see reference to class template instantiation 'C<A>' being compiled
testing.cpp(73): note: see reference to class template instantiation 'A<C<A>>' being compiled
第 69 行,即 C<A> c(A<C<A>::this_class>(3.0));
在所有情况下编译。
#include <iostream>
#include <vector>
using namespace std;
template<class Z>
class A {
public:
// typedef typename Z::traits_type Traits;
A(double data = 2.0) : data_(data) {
cout << "Constructing A with data: " << data_ << endl;
}
double data() const {
return data_;
}
void setup(Z* z) {
z_ = z;
}
void doStuff() const {
// cout << "A's foo: " << Traits::foo() << endl;
cout << "Instance of Z's data_: " << z_->data_ << endl;
}
private:
double data_;
Z* z_;
};
//struct CTraits {
// static int foo() {
// return 1;
// }
//};
template<template <class> class B = A>
class C {
public:
typedef C<B> this_class;
// typedef CTraits traits_type;
C(const B<this_class>& b = B<this_class>()) : data_(4.0), b_(b) {
cout << "Constructing C using B with data: " << b_.data() << endl;
b_.setup(this);
}
void doStuff() const {
b_.doStuff();
}
private:
double data_;
friend class B<this_class>;
B<this_class> b_;
};
int main(int argc, char* argv[]) {
// The following line compiles regardless of whether all lines above that
// are commented are commented in or out.
// C<A> c(A<C<A>::this_class>(3.0));
// This will not compile if the lines above, outside of main, that are commented
// out are commented back in.
C<A> c(A<C<A>>(3.0));
return 0;
}
我的问题是,为什么 C<A> c(A<C<A>>(3.0));
行上方 main
之外的注释掉的行被重新注释掉时会导致编译错误?换句话说,什么变化导致编译失败?
此外,为什么 C<A> c(A<C<A>::this_class>(3.0));
行编译而 C<A> c(A<C<A>>(3.0));
行不编译?换句话说,使用 C<A>::this_class
作为 A
的模板参数比仅使用 C<A>
有什么特别之处?
我使用 clang++
重现了您的问题。
让我们从一个更简单的例子开始。保留您拥有的 class 定义,而是使用以下 main
:
int main(int argc, char* argv[]) {
A<C<A>> x(3.0);
}
编译失败——让我们找出原因(非正式地)。
编译器必须实例化 class A<C<A>>
。为此,它将 C<A>
作为模板参数 Z
插入到 A
中。然后它看到行typedef typename Z::traits_type Traits
,要求它实例化Z
,即C<A>
。在实例化 C<A>
时,它会将模板参数 B
设置为 A
,因此当它看到 B<this_class> b_
时,它必须实例化 A<C<A>>
。但这与我们一开始尝试实例化的 class 是一样的!这就是编译器给出错误的原因——它有一个 "incomplete type",因为编译器意识到它已经开始实例化该类型,但尚未完成。
这与您在定义时出错的原因相同:
class D {
D x;
};
现在回答问题的第二部分:为什么使用 this_class
可以解决问题?
要理解这一点,请考虑这个更简单的示例(再次使用与之前相同的 class 定义):
int main(int argc, char* argv[]) {
typedef C<A>::traits_type y;
A<C<A>> x(3.0);
}
这里发生的事情是 typedef
语句要求编译器提前实例化 C<A>
。在这样做的过程中,它像以前一样遇到 b_
变量并尝试实例化 A<C<A>>
,这再次将 C<A>
插入 A
作为模板参数 Z
。然后它看到行 typedef typename Z::traits_type Traits
,但此时它已经为 C<A>
计算了 typedef CTraits traits_type
,因此继续而不尝试再次实例化 C<A>
。
原因
总结以上讨论,您所看到的行为有两个原因。首先,即使您只需要 C<A>::traits_type
,编译器也会尝试实例化整个 C<A>
。其次,编译器在访问 C<A>::traits_type
(Z<A>::traits_type
) 时可以接受不完整的类型,但在 A<C<A>> b_
(B<this_type> b_
).[=49= 中实例化类型本身时则不行]
C<A> c(A<C<A>::this_class>(3.0));
起作用的原因是 C<A>::this_class
强制编译器提前实例化 C<A>
。
解决方法
一个可能的解决方法是提前显式实例化您需要的模板,以防止循环:
template class C<A>;
int main(int argc, char* argv[]) {
C<A> c(A<C<A>>(3.0));
}