The implicit instantiation for a member class template when the specialization is referenced in the enclosing class template

我只是一个虾纸丫 提交于 2020-12-29 07:44:47

问题


#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?


回答1:


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. :)




回答2:


Well I did run CppInsights on your first example: https://cppinsights.io/lnk?code=dGVtcGxhdGU8dHlwZW5hbWUgVD4Kc3RydWN0IEF7CiAgICB0ZW1wbGF0ZTx0eXBlbmFtZSBVPgogICAgc3RydWN0IEJ7fTsKICAgIEI8VD4gYjsKfTsKCmludCBtYWluKCkgewogICAgQTxpbnQ+IGE7Cn0KCgoK&insightsOptions=cpp2a&std=cpp2a&rev=1.0



来源:https://stackoverflow.com/questions/65101334/the-implicit-instantiation-for-a-member-class-template-when-the-specialization-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!