在封闭类模板中引用专业化时,成员类模板的隐式实例化 [英] The implicit instantiation for a member class template when the specialization is referenced in the enclosing class template

查看:33
本文介绍了在封闭类模板中引用专业化时,成员类模板的隐式实例化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  #include< iostream>template< typename T>结构A {template< typename U>结构B {};B Tb;//#2};//#3int main(){A< int>一种;//#1} 

考虑上述代码,使用模板ID A< int> 会导致隐式实例化专业化 A< int> .根据以下规则:

温度点#4

对于类模板专业化,类成员模板专业化或对类模板的类成员的专业化,如果该专业化是隐式实例化的,因为它是从另一个模板专业化内部引用的,则如果上下文来自引用专业化的对象取决于模板参数,并且如果没有在封闭模板的实例化之前实例化该专业化,则实例化点紧接在封闭模板的实例化点之前.否则,这种专门化的实例化点紧接在引用该专门化的名称空间范围声明或定义之前.

对于在#1 处引用的专业化,该规则的否则部分将适用于它.这意味着 A< int> 的实例化点应该在#3 处,这里没有问题.但是,针对 A< int< 的实例化将导致针对作为其成员的专业化 B< int< 的实例化.因此,该规则的 if 部分将适用于 B< int> .我很困惑的是在这里.根据相关规则, B< int> 的实例化点应紧接在封闭模板的实例化点之前,即之前的某处#3 ,我在这里听不懂.如果通过 normal class 来考虑,只有两种方法定义其类成员,一种是在类定义中定义该类成员,另一种是在类定义,然后在遵循类定义的某个点上,在类定义之外定义类成员.

使用普通的类更改示例:

  struct Normal_A_Int {struct Normal_B_Int {};Normal_B_Int b;};int main(){Normal_A_Int a;} 

这意味着,成员类 Normal_B_Int 的定义必须在封闭类的定义中,因为声明 Normal_B_Int b 是完整的类类型.

因此,如何将成员类 B< int> 的定义放在封闭类 A< int> 的定义之前的某个位置?充其量,对于 B< int> 的定义应该在 A< int> 的定义中.第一个示例如何解释POI?

解决方案

您从温度点/4 就是这种情况:

  template< typename T>结构A {template< typename U>结构B {};B Tb;};//A< int> :: B< int>的实例化点//A< int>的实例化点int main(){A< int>一种;} 

没有太多律师要做.该标准说,这些是实例化的要点.类模板不是类,直觉不一定会延续.它们既有定义又有实例化.

采用外部类定义.如果是类,则必须定义成员的类型.如果它是一个类模板,则只能声明成员的类型.您可以降低B的定义:

  template< typename T>结构A {template< typename U>结构B;B Tb;};template< typename T>template< typename U>结构A< T> :: B {}; 

您不能使用类(定义中具有不完整"成员)来执行此操作,但是可以使用类模板来完成此操作.

问题是,为什么模板 A< T> :: B< B< T> 的实例化点为何先于 A< T> 的实例化点?最终,因为该标准是这样说的.但是请考虑一下,如果之后,您将根本无法拥有内部类模板.并且,例如,如果在 A< T> 的定义内,则名称查找将发生错误,因为 A 的定义与 A< int> :: B< int> 中,> A< int< 将不可见.因此,这实际上是有道理的.

也许直觉来自于混合定义和实例化点:

那么,如何让成员类B的定义放在封闭类A的定义之前的某个位置?

不是.指示点控制名称的可见性.直到TU为止的所有名称都是可见的.(这不是 定义.)从这个角度来看,很明显地 A< int> :: B< int> 应该有一个在 A< int> 的实例化点附近的实例化(它们应该具有相同的其他名称).如果有顺序,则内部应该首先出现(以便 A< int> 可以具有 A< int> :: B< int< 成员).如果没有顺序,则必须有关于实例如何交错或交互的语言.

此规则有两个有趣的方面.

一个是模板可以专门化.因此,为了满足此要求,当编译器实例化 A< int> 时,它必须首先选择特殊化,对其进行足够的处理以知道它具有成员类模板,并且需要实例化它,然后停止所有操作以首先实例化 A< int> :: B< int> .这并不困难,但是很微妙.就像以前一样,有一个实例化堆栈.

第二个方面要有趣得多.您可能会从普通的类直觉中期望, B< T> 的定义可以使用 A< T> 中的东西(例如typedef),这在模板上下文中需要实例化实例化 A< T> :: B< B< T> 时,尚不存在的 A< T> .喜欢:

  template< typename T>结构A {使用类型= T;template< typename U>struct B {using type = typename A< U> :: type;};B Tb;}; 

可以吗?

如果 A< int> :: B< int> 的实例化点早于 A< int> 的实例化点,我们就不能真正形成> A< int> :: type .

这是 CWG287 和 P1787 .

CWG287提出实例化点相同(一个不在另一个之前).此外,它将添加:

如果隐式实例化的类模板专业化,类成员专业化或类模板的专业化引用了一个类,类模板专业化,类成员专业化或包含直接或间接导致实例化的专业化引用的类模板的专业化,则在专业化引用的上下文中应用类引用的完整性和排序要求.

在我的示例中, A< int> :: B< int> 引用 A< int> ,这直接导致通过引用 B< B<; int> ,因此类引用( typename A< int> :: type )的完整性和排序要求适用于专业化引用( B< int> b ).这样就可以了如果typedef低于 B 的定义,则仍然可以.但是如果我们将typedef移到成员 b 下方,它将格式错误!微妙的!这具有交错实例的效果.当我们看到该成员时,我们停止正在做的事情,开始实例化 A< int> :: B< int> ,但是我们可以使用所在位置的顺序和完整性要求 A< int> 的实例化.实例化点是相同的,因此我们也可以使用TU中的相同声明.

CWG287似乎旨在复制编译器已经完成的工作.但是,CWG287自2001年以来一直开放.(另请参见#include <iostream> template<typename T> struct A{ template<typename U> struct B{}; B<T> b; //#2 }; //#3 int main() { A<int> a; // #1 }

Consider the above code, the use of the template-id A<int> causes an implicit instantiation for specialization A<int>. According to the following rule:

temp.point#4

For a class template specialization, a class member template specialization, or a specialization for a class member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

For the specialization referenced at #1, the Otherwise part of that rule will apply to it. That means, the point of instantiation for A<int> should at #3, there's no issue here. However, the instantiation for A<int> will cause the instantiation for specialization B<int> which is a member thereof. Hence, the if part of that rule will apply to B<int>. What I'm confused about is here. According to the relevant rule, the point of instantiation for B<int> shall be immediately before the point of instantiation of the enclosing template, that is, somewhere before #3, I can't understand here. If, think it through the normal class, there're only two ways to define its class member, one is to define the class member in the class definition, the other is to declare the class member in the class definition and then define the class member outside the class definition at some point where follows the classes definition.

Change the example by using a normal class:

struct Normal_A_Int{
   struct Normal_B_Int{};
   Normal_B_Int b;
};
int main(){
  Normal_A_Int a;
}

That means, the definition for member class Normal_B_Int must be in the definition of the enclosing class due to the declaration Normal_B_Int b is required a complete class type.

So, how is it possible to let the definition for member class B<int> be placed at some point precede the definition for an enclosing class A<int>? At best, the definition for B<int> shall be in the definition for A<int>. How to interpret the POI for the first example?

解决方案

The quote you provided from temp.point/4 is exactly the case:

template<typename T>
struct A {
    template<typename U>
    struct B { };
    B<T> b;
};

// point-of-instantiation for A<int>::B<int>
// point-of-instantiation for A<int>
int main() {
    A<int> a;
}

There's not much lawyering to do. The standard says those are the points of instantiation. Class templates are not classes and intuition doesn't necessarily carry over. They have both a definition and instantiation.

Take the outer class definition. If it is a class, the member's type must be defined. If it is a class template, the member's type must only be declared. You can lower the definition of B:

template<typename T>
struct A {
    template<typename U> struct B;
    B<T> b;
};

template<typename T>
template<typename U>
struct A<T>::B { };

You could not do this with classes (have an "incomplete" member in the definition), but you can with class templates.

So the question is, why is the point-of-instantiation of template A<T>::B<T> before that of A<T>? Ultimately, because the standard says so. But consider that if it were after, you couldn't have an inner class template at all. And if it were, say, inside the definition of A<T>, name lookup would misbehave because the names between the definition of A and the point of instantiation of A<int> would not be visible in A<int>::B<int>. So it actually makes some sense.

Perhaps the intuition comes from conflating definition and point-of-instantiation:

So, how is it possible to let the definition for member class B be placed at some point precede the definition for an enclosing class A?

It isn't. The point-of-instantiation controls name visibility. All names up to that point in the TU are visible. (It is not the definition.) From this point of view, it is clear intuitively that A<int>::B<int> should have a point-of-instantiation near to the point-of-instantiation of A<int> (they should see the same other names). If there is an ordering, probably the inner should come first (so that A<int> can have a A<int>::B<int> member). If there is not an ordering, there has to be language about how the instantiations are interleaved or interact.

There are two interesting aspects of this rule.

One is that templates can be specialized. So, to accomplish this requirement, when the compiler goes to instantiate A<int>, it must first select the specialization, process it enough to know it has a member class template and that it needs to instantiate it, and then stop everything to go instantiate A<int>::B<int> first. That is not difficult, but it is subtle. There is an instantiation stack, as it were.

The second aspect is far more interesting. You may expect from ordinary class intuition that the definition of B<T> can use things (e.g. typedefs) from A<T> that would in a template context require an instantiation of A<T> which doesn't yet exist when A<T>::B<T> is being instantiated. Like:

template<typename T>
struct A {
    using type = T;

    template<typename U>
    struct B { using type = typename A<U>::type; };

    B<T> b;
};

Is that OK?

If the point-of-instantiation of A<int>::B<int> is before that of A<int>, we cannot really form A<int>::type.

This is the realm of CWG287 and P1787.

CWG287 proposed that the point-of-instantiations be the same (one is not before the other). Further, it would add:

If an implicitly instantiated class template specialization, class member specialization, or specialization of a class template references a class, class template specialization, class member specialization, or specialization of a class template containing a specialization reference that directly or indirectly caused the instantiation, the requirements of completeness and ordering of the class reference are applied in the context of the specialization reference.

In my example, A<int>::B<int> references A<int> which directly caused its instantiation by way of reference to B<int>, so the requirements of completeness and ordering of the class reference (typename A<int>::type) are applied in the context of the specialization reference (B<int> b). So it is OK. It is still OK if the typedef is below the definition of B. But if we move the typedef below the member b, it will be ill-formed! Subtle! This has the effect of interleaving the instantiations. When we see the member, we stop what we're doing, go off to instantiate A<int>::B<int>, but we can use the ordering and completeness requirements from where we were in the instantiation of A<int>. The point-of-instantiation is the same, so we can also use the same declarations from the TU.

CWG287 seems to aim at replicating what compilers do already. However, CWG287 has been open since 2001. (See also this and this.)

P1787 which seems to be targeted at C++23, aims to rewrite a lot of subtle language. I think aims to have similar effect as CWG287. But to do so, they had to so thoroughly re-define name lookup that it is very difficult to me to know that. :)

这篇关于在封闭类模板中引用专业化时,成员类模板的隐式实例化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆