【问题标题】:incomplete type support for list对列表的不完整类型支持
【发布时间】:2020-04-13 00:40:32
【问题描述】:

我用类似于std::list 的 API 实现了一个列表,但编译失败

struct A { my_list<A> v; };

该列表有一个基类,该基类有一个成员 base_node,该成员具有 prevnext 字段,node(派生自 base_node)拥有 T 值(即列表的模板参数)。编译错误是

error: ‘node<T>::val’ has incomplete type
     T val;
       ^~~
note: forward declaration of ‘struct A’

我查看了 GCC 代码,似乎它们拥有一个大小为 T 的字节缓冲区,所以不确定它是如何为它们工作的。 std::list 如何将A 存储在其节点中?

[更新]

struct A { };

template <typename T>
struct B : public A
{
    using B_T = B<T>;
    T t;
};

template <typename T>
class C
{
    using B_T = typename B<T>::B_T; // this fails to compile
    //using B_T = B<T>; // this compiles fine
};

struct D { C<D> d; };

【问题讨论】:

  • 您对代码的描述非常模糊不清。我无法得到my_listnodebase_node 之间的关系。尝试使用可重现的示例更新 Q。
  • 代码很长。 struct A { std::list&lt;A&gt; v; }; 取自 LLVM 测试并编译,但我不确定如何编译。我不确定他们如何将列表节点持有的类型从列表编译中分离出来
  • 无法根据您的图表和描述重现:coliru.stacked-crooked.com/a/b41e97113d13de05
  • 可能粘贴的编译器错误后面跟着一系列“需要来自...”的注释,这可能有助于找出导致问题的代码中的内容而不是我的代码。
  • @aschepler 谢谢。我能够用您的代码重现它,我将根据您的示例重构我的代码,请随时写一个答案以便我可以接受它coliru.stacked-crooked.com/a/450f05eba3710a7b

标签: c++11 stl


【解决方案1】:

在您的简化示例中

struct A { };

template <typename T>
struct B : public A
{
    using B_T = B<T>;
    T t;
};

template <typename T>
class C
{
    using B_T = typename B<T>::B_T; // this fails to compile
    //using B_T = B<T>; // this compiles fine
};

struct D { C<D> d; };

您遇到了类模板实例化的问题。

首先,请注意一个类定义本质上具有两个解析过程(不一定以这种方式实现):

  1. 首先确定基类和类成员的类型。在这个过程中,这个类被认为是不完整的,尽管之前声明的基和成员可以被定义中的后续代码使用。

  2. 在类定义中的某些不影响基类或成员类型的代码中,类被认为是完整的。这些地方包括成员函数定义体、成员函数默认参数、静态成员初始化器和非静态成员默认初始化器。

例如:

struct S {
    std::size_t n = sizeof(S);                  // OK, S is complete

    std::size_t f() const { return sizeof(S); } // OK, S is complete

    using array_type = int[sizeof(S)];          // Error, S incomplete

    void f(int (&)[sizeof(S)]);                 // Error, S incomplete
};

模板让这件事变得更棘手,因为它们更容易意外地间接使用尚未完成的类。这尤其出现在 CRTP 代码中,但此示例是另一种可能发生的简单方式。

类模板实例化的基本方式(有点简化)是:

  • 仅仅命名一个类模板特化,例如X&lt;Y&gt;,本身并不会导致类模板被实例化。
  • 以对不完整类类型有效的方式使用类模板特化不会导致模板被实例化。
  • 以任何需要类型完整的方式使用类模板特化,例如命名类的成员或使用类类型(不是指针或引用)定义变量,都会导致模板的隐式实例化。
  • 实例化类模板涉及确定基类和成员的类型,这与类定义解析的“第一遍”非常相似。所有这些基本类型和成员类型在当时都必须有效。实例化成员定义在很大程度上延迟到需要每个成员,但在此步骤中没有选择性地实例化成员类型:要么全部要么错误。
  • 当基类或成员声明涉及另一个模板特化时,该过程可以是递归的。但是在其他实例化期间,原始实例化上下文的类类型被认为是不完整的。

看这个例子,struct D 定义了一个成员 C&lt;D&gt; d;,它要求 C&lt;D&gt; 是完整的,所以我们尝试实例化特化 C&lt;D&gt;。到目前为止,D 不完整。

C&lt;D&gt; 只有一个成员,即

using B_T = typename B<D>::B_T;

由于这命名了另一个类模板特化B&lt;D&gt; 的成员,现在我们必须尝试实例化该B&lt;D&gt; 特化。到目前为止,DC&lt;D&gt; 仍然不完整。

B&lt;D&gt; 有一个基类,即A。它有两个成员:

using B_T = B<D>;
D t;

成员类型B&lt;D&gt;::B_T 很好,因为仅命名B&lt;D&gt; 不需要完整的类型。但是实例化B&lt;D&gt; 需要两个成员都具有良好的格式。类成员不能将不完整的类作为其类型,但类型 D 目前仍不完整。

正如您所注意到的,您可以通过避免将成员命名为 B&lt;T&gt;::B_T 而直接使用类型 B&lt;T&gt; 来解决此问题。或者,您可以将原始 B_T 定义移动到其他基类或特征结构,并确保其新位置是 可以 使用不完整类型作为参数进行实例化的位置。

许多模板只是假设它们的参数必须始终是完整类型。但是,如果在编写时仔细考虑代码如何使用模板参数和其他间接使用的依赖类型,它们在更多情况下会很有用,这些在实例化时可能是不完整的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-02
    • 2014-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-23
    • 1970-01-01
    • 2021-06-05
    相关资源
    最近更新 更多