【发布时间】:2015-09-28 18:17:23
【问题描述】:
以下代码编译并给出预期结果(GCC 和 clang):
template <typename T> struct Derived;
struct Base
{
template <typename T>
void foo(T * const t)
{
dynamic_cast<Derived<T> * const>(this)->bar(t);
}
};
template <typename T>
struct Derived : Base
{
void bar(T const *) const { }
};
代码将调用Base 中的foo 调度到Derived 中的bar。
作为参考,以下代码无法编译:
struct Derived2;
struct Base2
{
template <typename T>
void foo(T * const t)
{
dynamic_cast<Derived2 * const>(this)->bar(t);
}
};
struct Derived2 : Base2
{
template <typename T>
void bar(T const *) const { }
};
GCC 提供以下诊断:
main.cpp: In member function 'void Base2::foo(T*)':
main.cpp:126:45: error: invalid use of incomplete type 'struct Derived2'
dynamic_cast<Derived2 * const>(this)->bar(t);
^
main.cpp:119:8: note: forward declaration of 'struct Derived2'
struct Derived2;
^
C++14 标准在“单一定义规则”部分中声明如下:
5 翻译单元中只需要一个类的定义 如果以需要类类型的方式使用该类 完全的。
[ 示例:以下完整的翻译单元是 格式良好,即使它从未定义 X:
结构 X; // 将 X 声明为结构类型
结构 X* x1; // 在指针结构中使用 X
X* x2; // 在指针结构中使用 X
——结束示例]
[注:规则为 声明和表达式描述了在哪些上下文中完成类 类型是必需的。如果满足以下条件,则类类型 T 必须是完整的: (5.1) — 定义类型 T 的对象 (3.1),或
(5.2) — 声明类型 T 的非静态类数据成员 (9.2),或
(5.3) — T 在新表达式 (5.3.4) 或
中用作对象类型或数组元素类型 (5.4) — 左值到右值的转换应用于引用类型 T (4.1) 或
的对象的左值 (5.5) — 将表达式(隐式或显式)转换为 T 类型(第 4、5.2.3、5.2.7、5.2.9、5.4)或
(5.6) — 一个不是空指针常量且类型不是 cv void* 的表达式,使用标准转换(第 4 条)、dynamic_cast (5.2.7 ) 或 static_cast (5.2.9),或 ...
这似乎表明第一个例子是不合法的。这是构造错误的吗?如果是这样,为什么我没有收到错误消息?
【问题讨论】:
-
对于从属名称,完整性在实例化点进行测量。
-
要使 dynamic_cast 正常工作,您需要多态性,即至少有一个虚函数。然后,由于您甚至不检查结果,因此更喜欢使用引用形式,其中 C++ 在失败时抛出异常。
-
这听起来像是two-phase lookup的经典例子。
-
做你想做的事的更惯用的方法是 CRTP 。 en.m.wikipedia.org/wiki/Curiously_recurring_template_pattern
-
@MarkLakata,我熟悉 CRTP。在这种情况下我不能使用它,因为基类实际上是一个访问者基类,它的类型需要被前向引用。
标签: c++ templates forward-declaration incomplete-type