12

我试图了解一个简单的 CRTP 模式是否符合标准。

下面的代码按预期编译和工作(在 clang 上)。

但是我对相关标准章节/段落的理解是虚函数CRTP<Derived, Base>::DoSomething()的实例化点应该在代码的(B)点,这里没有Derived的完整声明可用的。因此,内部 typedef Type 也不应该可用。

谁能指出验证此代码的相关标准章节?

换句话说,在这种情况下,虚函数被实例化为 ATFER 点 C?非常感谢您提供任何见解。

弗朗切斯科

//-------------------------
// START CODE

#include <iostream>

struct Type1 {};
struct Type2 {};

struct Base
{
  virtual ~Base() {}
  virtual void DoSomething() = 0;
};

template< typename T, typename U >
struct CRTP : U
{
  virtual void DoSomething() { DoSomething( typename T::Type() ); }

 void DoSomething( Type1 ) { std::cout << "1\n"; }
 void DoSomething( Type2 ) { std::cout << "2\n"; }
};

// (A) point of inst. of CRTP< Derived, Base > ( 14.7.1.4 ) ??
// (B) point of inst. of CRTP< Derived, Base >::DoSomething() (14.6.4.1.4 ) ??

struct Derived : CRTP< Derived, Base >
{
  typedef Type2 Type;
};

// (C)

int main()
{
  Base *  ptr = new Derived;
  ptr->DoSomething();
  delete ptr;
}

// END CODE
//-------------------------

相关(?)标准段落:

14.6.4.1 4 如果一个虚函数被隐式实例化,它的实例化点紧跟其封闭类模板特化的实例化点。

14.7.1 4 如果在需要完全定义的对象类型的上下文中使用类类型,或者如果类类型的完整性可能影响程序的语义,则隐式实例化类模板特化。

14.7.1 9 实现不应隐式实例化不需要实例化的类模板的函数模板、成员模板、非虚拟成员函数、成员类或静态数据成员。如果虚拟成员函数不会被实例化,则未指定实现是否隐式实例化类模板的虚拟成员函数。

4

2 回答 2

5

这似乎是编译器将实例化延迟CRTP<Derived, Base>::DoSomething()到翻译单元结束的结果,这是允许的(参见CWG 问题 993)。

CRTP<Derived, Base>绝对在Derived(§14.6.4.1 [temp.point]/p4, 所有引号都指向 N3936) 的定义之前实例化:

对于类模板特化、类成员模板特化或类模板的类成员的特化,如果特化是隐式实例化的,因为它是从另一个模板特化中引用的,如果引用特化的上下文依赖于在模板参数上,并且如果特化没有在封闭模板的实例化之前被实例化,则实例化点就在封闭模板的实例化点之前。否则,这种特化的实例化点紧接在引用特化的命名空间范围声明或定义之前。

是否CRTP<Derived, Base>::DoSomething()需要实例化取决于在需要成员定义存在的上下文中引用的短语的含义(§14.7.1 [temp.inst]/p2)。所有非纯虚函数都是 odr 使用的(§3.2 [basic.def.odr]/p2),并且“每个程序都应包含该程序中 odr 使用的每个非内联函数或变量的一个定义” (§3.2 [basic.def.odr]/p4);这是否算作“在需要成员定义存在的上下文中引用”尚不清楚。

(即使它不需要被实例化,但是编译器仍然可以根据 §14.7.1 [temp.inst]/p11 自由地实例化它——“未指定实现是否隐式实例化如果虚拟成员函数不会被实例化,则类模板。”。)

如果CRTP<Derived, Base>::DoSomething()确实实例化了,则第 14.6.4.1 节 [temp.point]/p5 和 p8 (强调我的)涵盖了这种情况:

5 如果一个虚函数被隐式实例化,它的实例化点紧跟其封闭类模板特化的实例化点。

8函数模板、成员函数模板或类模板的成员函数或静态数据成员的化可以在翻译单元内具有多个实例化点,并且除了上述实例化点之外,对于任何这种在翻译单元内有一个实例化点的特化,翻译单元的末端也被认为是一个实例化点。类模板的特化在翻译单元内最多有一个实例化点。任何模板的特化都可能在多个翻译单元中具有实例化点。如果根据一个定义规则(3.2),两个不同的实例化点赋予模板特化不同的含义,则程序是非良构的,不需要诊断。

也就是说,它有两个实例化点,CRTP< Derived, Base >一个在实例化点之后,一个在翻译单元的末尾。在这种情况下,在实例化的两个点上查找名称typename T::Type会产生不同的结果,因此程序格式错误,不需要诊断。

于 2014-09-09T04:13:39.417 回答
0

的使用new Derived导致Derived类被实例化。

更正:Derived本身不是模板,因此需要立即使用其结构布局和包含的成员声明。这导致CRTP<Derived,Base>在 Derived 定义之后立即实例化。等我有空的时候再去查一下正式的标准;但重点仍然是 CRTP 的实例化只计算结构和可用成员,而不是成员函数的主体;并且它知道 Derived 类的结构和成员。

成员函数在使用之前不会被实例化(这里是构造函数),并且此时它已经拥有类本身。要查找的另一件事是 Derived 的构造函数(因为它不是模板)是在类之后立即生成的,还是仅在需要时生成。如果是前者,则可以通过使 Derived 成为带有虚拟参数的模板来使其变得懒惰。但这并不影响这个特定的问题:无论是紧随其后Derived还是紧随其后main,函数实例化仍然不是在解析 的声明之前Derived

这导致CRTP<Derived,Base>被实例化。但在这两种情况下,只需要类结构,而不是任何成员的实际代码。擦除所有内联函数体,您会发现此时没有问题。

现在使用 Derived 默认构造函数,因此Derived::Derived()被隐式实例化。实例化点紧跟在 的定义之后main

在实例化Derived::Derived()时,则需要CRTP<Derived,Base>::CRTP(). 它与需要它的模板实例化在同一点实例化。该构造函数需要所有虚函数,因此DoSomething()再次实例化,与启动它的实例化相同。您可以看到,就所有成员(而不是函数体)的所有声明而言,在完全呈现的 Derived 类的完整定义已知之后,这一切都发生了。

这是缺少的见解:类定义不包括成员函数定义,即使它们是在类定义的词法封闭区域内给出的。请记住定义声明之间的区别,分别用于类和函数。

于 2014-09-08T19:58:28.813 回答