문제

I'm trying to understand if a simple CRTP pattern is valid by the standard.

The code below compiles and works as expected (on clang).

But my understanding of the relevant standard chapters/paragraphs is that the point of instantiation of the virtual function CRTP< Derived, Base >::DoSomething() should be at point (B) of the code, where the full declaration of Derived is not available. Therefore the inner typedef Type should not be available either.

Can anyone kindly point out the relevant standard chapter that validates this code?

In other words, something that says that in this case the virtual function is instantiated ATFER point C? Thanks a lot in advance for any insight.

Francesco

//-------------------------
// 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
//-------------------------

Relevant (?) standard paragraphs:

14.6.4.1 4 If a virtual function is implicitly instantiated, its point of instantiation is immediately following the point of instantiation of its enclosing class template specialization.

14.7.1 4 A class template specialization is implicitly instantiated if the class type is used in a context that requires a completely-defined object type or if the completeness of the class type might affect the semantics of the program.

14.7.1 9 An implementation shall not implicitly instantiate a function template, a member template, a non-virtual member function, a member class, or a static data member of a class template that does not require instan- tiation. It is unspecified whether or not an implementation implicitly instantiates a virtual member function of a class template if the virtual member function would not otherwise be instantiated.

도움이 되었습니까?

해결책

This appears to be a result of the compiler delaying instantiation of CRTP<Derived, Base>::DoSomething() until the end of the translation unit, as it is allowed to do (see CWG issue 993).

CRTP<Derived, Base> is definitely instantiated right before the definition of Derived (§14.6.4.1 [temp.point]/p4, all quotes are to N3936):

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.

Whether CRTP<Derived, Base>::DoSomething() is required to be instantiated at all depends on the meaning of the phrase referenced in a context that requires the member definition to exist (§14.7.1 [temp.inst]/p2). All non-pure virtual functions are odr-used (§3.2 [basic.def.odr]/p2), and "every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program" (§3.2 [basic.def.odr]/p4); whether that counts as "referenced in a context that requires the member definition to exist" is unclear.

(Even if it's not required to be instantiated, however, the compiler is still free to instantiate it per §14.7.1 [temp.inst]/p11 - "It is unspecified whether or not an implementation implicitly instantiates a virtual member function of a class template if the virtual member function would not otherwise be instantiated.".)

If CRTP<Derived, Base>::DoSomething() is indeed instantiated, then the situation is covered by §14.6.4.1 [temp.point]/p5 and p8 (emphasis mine):

5 If a virtual function is implicitly instantiated, its point of instantiation is immediately following the point of instantiation of its enclosing class template specialization.

8 A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one definition rule (3.2), the program is ill-formed, no diagnostic required.

That is, it has two points of instantiation, one right after CRTP< Derived, Base >'s point of instantiation, and one at the end of the translation unit. In this case, at the two points of instantiations name lookup for typename T::Type would produce different results, so the program is ill-formed, no diagnostic required.

다른 팁

The use of new Derived causes the Derived class to be instantiated.

Correction: Derived is not itself a template, so its structure layout and contained member declarations are needed right away. That causes CRTP<Derived,Base> to be instantiated immediately following the Derived definition. I'll have to look up the formal standard later when I have more time; but the the point is still that the instantiation of CRTP only figures out the structure and available members, not the bodies of the member functions; and it knows the structure and members of the Derived class when it does so.

The member functions are not instantiated until they are used (here, the constructor), and it already has the class itself at that time. The other thing to look up is whether the constructor of Derived, since it is not a template, is generated immediately following the class, or only if/when needed. If the former, it can be made lazy by making Derived a template with a dummy argument. But that doesn't affect this specific question: whether right after Derived or right after main, the function instantiation is still not before parsing the declaration of Derived.

That causes CRTP<Derived,Base> to be instantiated. But in both cases it is only the class structure that is needed, not the actual code for any of the members. Erase all the inline function bodies, and you'll see there is no problem at this point.

Now the Derived default constructor is used, so Derived::Derived() is implicitly instantiated. The point of instantiation is immediately following the definition of main.

In instantiating Derived::Derived(), it then needs CRTP<Derived,Base>::CRTP(). It is instantiated at the same point as the template instantiation that needed it. That constructor needs all the virtual functions, so DoSomething() is instantiated, again, at the same point as the instantiation that kicked it off. You can see that all this happens well after the complete definition of the fully rendered Derived class is known, in terms of all the declarations of all the members (not the function bodies).

That's the missing insight: the class definition does not include member function definitions, even if they are given within the lexical enclosing region of the class definition. Remember the distinction between definitions and declarations, separately for classes and functions.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top