12

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.

Francesco
  • 215
  • 1
  • 7
  • You missed the word `specialization` at the end of 14.6.4.1.4. I believe that puts you at `(C)`, wouldn't it? This makes sense: You can't properly instantiate `virtual void DoSomething()` until you know what `T` is. – Joe Z Aug 11 '13 at 17:00
  • 1
    One of the biggest benefits of the CRTP idiom is it can be used to implement static-polymorphism, and avoid the runtime overhead of virtual functions. Why you are trying to mix CRTP and virtual functions? – Manu343726 Aug 11 '13 at 17:06
  • You might want to add [temp.point]/3 to the list of relevant paragraphs from the Standard: "Otherwise, the point of instantiation for such a [class template] specialization immediately precedes the namespace scope declaration or definition that refers to the specialization." `DoSomething` is not instantiated until `ptr->DoSomething();`, see [temp.inst]/1. – dyp Aug 11 '13 at 17:29
  • My understanding is that the class template specialization CRTP< Derived, Base > should be implicitly instantiated right before the definition of Derived. So I interpret "immediately following" as between the specialization of CRTP and the definition of Derived. – Francesco Aug 11 '13 at 17:33
  • (If you want to answer a comment, please add a @ `username` to notify the user, it's optional if it's a comment for a question/answer from `username`.) "between the specialization of CRTP and the definition of Derived" My interpretation is that the point of instantiation of the *class template specialization* `CRTP< Derived, Base >` is before `struct Derived /*...*/`, see [this failing example](http://coliru.stacked-crooked.com/view?id=8e8377367b8711834fedfeccd5ba3a05-7885f3d27d18134d8479d2ab5250c852). – dyp Aug 11 '13 at 17:41
  • @DyP thankd for the insight, I keep reading [temp.inst]/1 but I don't think that virtual functions are "implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist" (ref 14.7.1.1, is that what you meant?). Take my example and comment out ptr->DoSomething() in main(). I believe that that virtual function is instantiated even if not explicitly called, if I comment out the implementation of DoSomething in derived, the linker fails... – Francesco Aug 11 '13 at 17:48
  • 1
    Read again [temp.inst]/1: "The implicit instantiation of a class template specialization causes the implicit instantiation of the **declarations**, but not of the **definitions** or default arguments, of the class member functions, [...]" i.e. when you derived `struct Derived : CRTP< Derived, Base >`, a complete object type is required as per [class.derived]/2, and the *class template* `CRTP` is implicitly instantiated. This leads to the instantiation of the *declarations* of the member functions. – dyp Aug 11 '13 at 18:02
  • I misread your code earlier: "DoSomething is not instantiated until ptr->DoSomething();" is wrong, nvm that (was confused by the overloads). It should have been *`DoSomething(Type1)` and `DoSomething(Type2)` are not instantiated until something like `myDerived.DoSomething(Type1());` and `myDerived.DoSomething(Type2());`* – dyp Aug 11 '13 at 18:03
  • @DyP thanks. I'm ok with what [temp.inst]/1 says with respect with non virtual functions (instantiation of definitions of memb functions right before first use). My understanding is that virtual functions follow a different route, and I thought that 14.6.4.1.4 stated that. My only problem is the part that says: "immediately following...". That, in my view, is not compatible with the fact the above code actually works. Anyway, your view is that virtual functions definitions are also instantiated right before first use? If so what is 14.6.4.1.4 about? – Francesco Aug 11 '13 at 19:08
  • [temp.inst]/1 Is about *all* member function *declarations*. It doesn't talk about *definitions*. The /2 paragraph addresses that, as well as /9 (/10 in the draft n3485 that I'm using). I don't know which contexts require the definition of a virtual function to exist. As your program is working, the virtual function must have been instantiated, so /9 applies. *Why* the name `typename T::Type` is found is not clear to me, therefore I'm commenting, not answering. – dyp Aug 11 '13 at 19:36

2 Answers2

5

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Yipes, they've really worked over and complicated the specification since I first studied it long long ago. I think the intent is to make more self-referencing cases like this work properly with a well specified standard meaning. So if CRTP is instantiated when it knows nothing other than Derived is a class name, then "instantiated" means less than it used to. Until it's later ODR-referenced, it's still kind of in limbo and doesn't know about dependent symbols other than as placeholders. I see that relies on how return types and arguments "reference" their types. – JDługosz Sep 09 '14 at 14:20
0

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.

Community
  • 1
  • 1
JDługosz
  • 5,592
  • 3
  • 24
  • 45
  • *"That constructor needs all the virtual functions, so [the definition of] `DoSomething()` is instantiated"* This sounds reasonable. Do you have any references to the Standard where this is specified? – dyp Sep 08 '14 at 20:09
  • OTOH, does 14.6.4.1 not also cover the instantiation of the definition of virtual functions? – dyp Sep 08 '14 at 20:10
  • 1
    *"The use of `new Derived` causes the `Derived` class to be instantiated."* `Derived` is not a template. *"Erase all the inline function bodies, and you'll see there is no problem at this point."* The One Definition Rule states that *"A virtual member function is odr-used if it is not pure."*. I think you produce UB if you remove the definition of the virtual function, even if you don't call the constructor. – dyp Sep 08 '14 at 20:14
  • @dyp: I think se (yes, I just made that pronoun up) means that you would leave the the declaration of the intent to implement the virtual member function, but remove the implementation body. Then, pretend the implementation body followed the full definition of `Derived`. – jxh Sep 08 '14 at 20:31
  • @jxh Ah, yes, I think that would work. -- off topic: (assuming se = (s)he) ["they" is sometimes used as a gender-neutral pronoun](https://en.wikipedia.org/wiki/Gender-specific_and_gender-neutral_pronouns), you could also relatively quickly find out the answerer's surname. – dyp Sep 08 '14 at 20:35
  • @dyp, it's my experience that the compiler needs all the virtual functions to create any constructor. Looking through the standard yesterday, it says it's unspecified whether the compiler implicitly instantiates virtual functions if they are not needed. It doesn't say when they are implied if/when they are, at least not in the same page. – JDługosz Sep 09 '14 at 13:55