当模板 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));
}