CRTP 和不完整类型
CRTP and Incomplete Types
我想简要说明一下完整类型与 CRTP 的关系。我认为这个 有点相关。但是,我这里的问题与 CRTP 有关,其中派生 class 成员函数显式调用基 class 成员函数,后者又调用派生函数。这似乎不同于在主例程中对派生类型调用一次基 class 函数。
我也读过这个,其中解释了使用派生class的基class的static constexpr
成员直到派生的 class 被编译器看到并且是完整的。但是在那种情况下,派生的 class 也被模板化了。
这是我的问题。考虑
template<typename D> struct B{
void foo() const { static_cast<const D*>(this)->baz(); }
};
struct D : B<D> {
void bar() const { foo(); }
void baz() const {}
};
据我了解,在 class 的右大括号之前,每个 D
都是不完整的类型。我也明白成员函数模板在使用之前不会被实例化。因此,如果我们暂时忽略 D::bar
,则以下内容在 main 中有效是有道理的:
D d; d.foo();
我需要进一步说明的是什么是使用?例如,在定义 D::bar
时,有一个对 B::foo
的调用(因此该函数可能在那里被实例化),这将要求 D
成为一个完整的类型。但是在定义 D::bar
的地方,D
还没有完成。或者是?
我认为可能发生的情况是,在定义 D::bar
并调用 B::foo
的地方,它会强制编译器为 B::foo
进行声明(不需要定义) .并且也许在 D::bar
在其他某个点实际调用之前,定义不会发生。但是我 运行 在 C++ Insights 上做这个并且变得更加困惑,因为 B
到 B<D>
的显式特化甚至发生在 D
声明之前。澄清将不胜感激!
使用 B<D>
作为 D
的基础 class 要求 B<D>
是一个完整的 class。因此它将导致 B<D>
.
的隐式实例化
class 特化的实例化点紧接在需要它的命名空间范围声明之前,即在 D
的定义之前。 (来自 [temp.point]/4)
B<D>
的隐式实例化不会导致 foo
的成员函数定义的隐式实例化。因此 D
不需要在这里完成。
定义
void bar() const { foo(); }
的 ODR-use 为 foo
。因此它会导致 B<D>::foo
.
的隐式实例化
成员函数特化的实例化点紧接在命名空间范围声明之后和翻译单元的末尾。 (来自 [temp.point]/1 and [temp.point]/7.1)
这两个都在 D
的定义之后,因此 D
将在这些点完成。因此 static_cast<const D*>(this)->baz();
不是问题。
请注意 D
不是模板。模板实例化规则不适用于它。使用或引用 bar
并不重要。使用 D d; d.foo();
或任何遵循 D
.
定义的东西都没有关系
我想简要说明一下完整类型与 CRTP 的关系。我认为这个
我也读过这个static constexpr
成员直到派生的 class 被编译器看到并且是完整的。但是在那种情况下,派生的 class 也被模板化了。
这是我的问题。考虑
template<typename D> struct B{
void foo() const { static_cast<const D*>(this)->baz(); }
};
struct D : B<D> {
void bar() const { foo(); }
void baz() const {}
};
据我了解,在 class 的右大括号之前,每个 D
都是不完整的类型。我也明白成员函数模板在使用之前不会被实例化。因此,如果我们暂时忽略 D::bar
,则以下内容在 main 中有效是有道理的:
D d; d.foo();
我需要进一步说明的是什么是使用?例如,在定义 D::bar
时,有一个对 B::foo
的调用(因此该函数可能在那里被实例化),这将要求 D
成为一个完整的类型。但是在定义 D::bar
的地方,D
还没有完成。或者是?
我认为可能发生的情况是,在定义 D::bar
并调用 B::foo
的地方,它会强制编译器为 B::foo
进行声明(不需要定义) .并且也许在 D::bar
在其他某个点实际调用之前,定义不会发生。但是我 运行 在 C++ Insights 上做这个并且变得更加困惑,因为 B
到 B<D>
的显式特化甚至发生在 D
声明之前。澄清将不胜感激!
使用 B<D>
作为 D
的基础 class 要求 B<D>
是一个完整的 class。因此它将导致 B<D>
.
class 特化的实例化点紧接在需要它的命名空间范围声明之前,即在 D
的定义之前。 (来自 [temp.point]/4)
B<D>
的隐式实例化不会导致 foo
的成员函数定义的隐式实例化。因此 D
不需要在这里完成。
定义
void bar() const { foo(); }
的 ODR-use 为 foo
。因此它会导致 B<D>::foo
.
成员函数特化的实例化点紧接在命名空间范围声明之后和翻译单元的末尾。 (来自 [temp.point]/1 and [temp.point]/7.1)
这两个都在 D
的定义之后,因此 D
将在这些点完成。因此 static_cast<const D*>(this)->baz();
不是问题。
请注意 D
不是模板。模板实例化规则不适用于它。使用或引用 bar
并不重要。使用 D d; d.foo();
或任何遵循 D
.