Title
Order dependencies in template instantiation
Status
drafting
Section
13.8.4.1 [temp.point]
Submitter
Martin Sebor

Created on 2001-05-17.00:00:00 last changed 3 months ago

Messages

Date: 2002-10-15.00:00:00

Notes from the October 2002 meeting:

This needs work. Moved back to drafting status.

See also issues 595 and 1330.

Date: 2022-02-18.07:47:23

Proposed resolution:

In 13.8.4.1 [temp.point] paragraph 3 change:

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.

To:

the point of instantiation is the same as the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the nearest enclosing declaration. [Note: The point of instantiation is still at namespace scope but any declarations preceding the point of instantiation, even if not at namespace scope, are considered to have been seen.]

Add following paragraph 3:

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.

and the following example

  template <class T> struct A {
          typename T::X x;
  };

  struct B {
          typedef int X;
          A<B> a;
  };

  template <class T> struct C {
          typedef T* X;
          A<C> a;
  };

  int main ()
  {
          C<int> c;
  }
Date: 2001-10-15.00:00:00

Notes from 10/01 meeting:

This was discussed at length. The consensus was that the template case should be treated the same as the non-template class case it terms of the order in which members get declared/defined and classes get completed.

Date: 2017-02-06.00:00:00

Implementations differ in their treatment of the following code:

    template <class T>
    struct A {
	typename T::X x;
    };

    template <class T>
    struct B {
	typedef T* X;
	A<B> a;
    };

    int main ()
    {
	B<int> b;
    }

Some implementations accept it. At least one rejects it because the instantiation of A<B<int> > requires that B<int> be complete, and it is not at the point at which A<B<int> > is being instantiated.

Erwin Unruh:

In my view the programm is ill-formed. My reasoning:

  • you need a complete type B<int> because you declare a variable in main
  • B<int> contains a member of type A<B<int> >, so you need that complete.
  • A<B<int> > tries to access B<int>::X, which in turn needs B<int> being complete.

So each class needs the other to be complete.

The problem can be seen much easier if you replace the typedef with

    typedef T (*X) [sizeof(B::a)];

Now you have a true recursion. The compiler cannot easily distinguish between a true recursion and a potential recursion.

John Spicer:

Using a class to form a qualified name does not require the class to be complete, it only requires that the named member already have been declared. In other words, this kind of usage is permitted:

    class A {
        typedef int B;
        A::B ab;
    };

In the same way, once B has been declared in A, it is also visible to any template that uses A through a template parameter.

The standard could be more clear in this regard, but there are two notes that make this point. Both 6.5.5.2 [class.qual] and _N4567_.5.1.1 [expr.prim.general] paragraph 7 contain a note that says "a class member can be referred to using a qualified-id at any point in its potential scope (6.4.6 [basic.scope.class])." A member's potential scope begins at its point of declaration.

In other words, a class has three states: incomplete, being completed, and complete. The standard permits a qualified name to be used once a name has been declared. The quotation of the notes about the potential scope was intended to support that.

So, in the original example, class A does not require the type of T to be complete, only that it have already declared a member X.

Bill Gibbons:

The template and non-template cases are different. In the non-template case the order in which the members become declared is clear. In the template case the members of the instantiation are conceptually all created at the same time. The standard does not say anything about trying to mimic the non-template case during the instantiation of a class template.

Mike Miller:

I think the relevant specification is 13.8.4.1 [temp.point] paragraph 3, dealing with the point of instantiation:

For a class template specialization... 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.

That means that the point of instantiation of A<B<int> > is before that of B<int>, not in the middle of B<int> after the declaration of B::X, and consequently a reference to B<int>::X from A<B<int> > is ill-formed.

To put it another way, I believe John's approach requires that there be an instantiation stack, with the results of partially-instantiated templates on the stack being available to instantiations above them. I don't think the Standard mandates that approach; as far as I can see, simply determining the implicit instantiations that need to be done, rewriting the definitions at their respective points of instantiation with parameters substituted (with appropriate "forward declarations" to allow for non-instantiating references), and compiling the result normally should be an acceptable implementation technique as well. That is, the implicit instantiation of the example (using, e.g., B_int to represent the generated name of the B<int> specialization) could be something like

        struct B_int;

        struct A_B_int {
            B_int::X x;    // error, incomplete type
        };

        struct B_int {
            typedef int* X;
            A_B_int a;
        };
History
Date User Action Args
2022-02-18 07:47:23adminsetmessages: + msg6665
2002-11-08 00:00:00adminsetmessages: + msg752
2002-11-08 00:00:00adminsetstatus: review -> drafting
2001-11-09 00:00:00adminsetmessages: + msg564
2001-11-09 00:00:00adminsetstatus: open -> review
2001-05-17 00:00:00admincreate