3

So I've read through this: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

And understand how Curiously Recurring Template Pattern (CRTP) works. But it seems like it depends upon the compiler implementation, specifically that the compiler:

  1. Defines the space required for each Template Class
  2. Then compiles the child class's methods
  3. Then compiles the parent class's methods

While I can see how this order allows compilation I feel like it's an leveraging compiler construction rather than an order of compiler passes required by the standard. But I feel like an equally legitimate set of compiler passes would be to:

  1. Define the space required for the parent class
  2. Compile the parent classes methods
  3. Define the space required for the child class
  4. Compile the child classes methods

If the compiler used these passes CRTP would fail on step 2 when it attempts to evaluate a child type.

So I've just made up these compiler passes, but is there a standard requirement that places constraints on the compiler that it much adhere to the 3 passes of the 1st? Or does CRTP exist in a grey area of knowing how compilers are implemented currently?


As I see it to allow that the standard would need to require a 1st pass that establishes object sizes, followed by a 2nd pass that compiles methods. But this 2nd pass wold have to be willing to build a child objects methods before the parent objects, which seems backwards.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    afaik it is well defined. No grey area. – bolov Dec 13 '17 at 15:51
  • 4
    CRTP is fully supported by the standard, to the point that standard library features use it. For example, see [std::enable_shared_from_this](http://en.cppreference.com/w/cpp/memory/enable_shared_from_this). Perhaps someone with better knowledge of the standard could indicate which parts of the standard guarantee that it works. – François Andrieux Dec 13 '17 at 15:51
  • 4
    Strictly speaking, it works because the _injected class name_ exists before the _base clause_ – Passer By Dec 13 '17 at 15:59
  • 8
    The use of the word "exploit" isn't helpful. Do you have a specific example where you're confused as to how something compiles? There really isn't anything special about how CRTP compiles vs any other template. – Barry Dec 13 '17 at 16:04
  • 2
    What do you mean by an "exploit"? Making use of an implementation artifact? It *definitely* isn't that. Making use of an unintended feature of the language as defined? Not really. – Martin Bonner supports Monica Dec 13 '17 at 16:09
  • 4
    "Exploit" usually means "a bug abused for malevolent purposes". CRTP is definitely not that. – Martin Bonner supports Monica Dec 13 '17 at 16:10
  • 1
    *"1. Defines the space required for each Class Template"* Your supposed first step already makes no sense. Class templates don't require space - concrete classes (instantiations of a class template among them) require space (or to be precise, objects of those classes do). If you have `template class C { T m; };`, you can't write `sizeof(C)`, but you can, say, `sizeof(C)`. You seem to labor under misconceptions of how templates and template instantiations work. – Igor Tandetnik Dec 13 '17 at 16:26
  • @MartinBonner I've tried to clarify what I meant by exploit. I'm really asking is CRTP implementation dependent. – Jonathan Mee Dec 13 '17 at 16:30
  • @IgorTandetnik I'd tried to make it more clear that I *did* understand this by saying Class Template. How would you have phrased that? I'm trying to say the object is defined at a sufficiently to allow compilation of methods to begin. – Jonathan Mee Dec 13 '17 at 16:35
  • @Passer By so this is the closest I've gotten to an answer. What do you mean by "base clause"? – Jonathan Mee Dec 13 '17 at 16:36
  • 1
    JonathanMee In @IgorTandetnik's example, `C` is a class template (a template for generating classes), `C` is a template class (a class generated from a template). The order matters! – Martin Bonner supports Monica Dec 13 '17 at 16:37
  • The standard does not care what the compiler does. It just says, "this code is well-defined, so you should compile it to let it run as what I say, in whatever way!" – xskxzr Dec 13 '17 at 17:40
  • @xskxzr Um... no, the standard defines how the compiler operates. Literally everything in the standard is defining allowable compiler inputs and expected outputs. The standard does not care *how* the compiler accomplishes this is probably what you meant to say. – Jonathan Mee Dec 13 '17 at 18:03
  • 2
    Yes. It seems that you are considering template instantiation rather than compilation, where how instantiation works is defined by the standard. Maybe [this paragraph](http://www.eel.is/c++draft/temp.inst#2) is what you are seeking for, which says an instantiation of a class template does not cause the implicit instantiation of the **definitions** of its member functions. – xskxzr Dec 14 '17 at 03:26
  • @xskxzr That may be the exact section that I need... But I just can't understand the example. Can you help me? I've asked a question here: https://stackoverflow.com/q/47815028/2642059 – Jonathan Mee Dec 14 '17 at 13:54

3 Answers3

5

CRTP was never implementation defined or conditionally supported, IIRC it was a pleasant surprise at the time of invention and has been accepted ever since.

From [class]

A class-name is inserted into the scope in which it is declared immediately after the class-name is seen. The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name.

Where class-name is the name of the class being declared. Therefore the class-name is already visible before the base-clause, which is the list of bases, but the class is complete only after its full definition.

However, templates are allowed to use incomplete types as its type-arguments, from [temp]

A template type argument may be an incomplete type.

Note the template is complete even if its type arguments aren't.

template<typename>
struct S {};

struct U     // U is visible after this line
    : S<U>   // S<U> is a complete type
{
    S<U> s;  // well-formed
};           // U is complete after this line

The reason the instantiated template can be complete is because the template type arguments may themselves be incomplete within the template, thereby avoiding cyclic logic

template<typename T>
struct A
{
    T t;  // ill-formed, incomplete type T in instantiation of A<B> from below
};

struct B : A<B>  // implicit instantiation of the specialization A<B>
{
};

We conclude the pattern is valid. How the compiler manages to compile it is irrelevant, if it is standard conformant, it will compile that code.

Passer By
  • 19,325
  • 6
  • 49
  • 96
2

This is well-defined. As noted in comments, even the standard utilizes the functionality.

The way to think about it is that as soon as you write class Child the compiler considers that effectively a forward declaration of the type. So as long as the parent class template definition doesn't depend on the complete type of Child the parent it will compile successfully. [I believe that the size of the parent template class cannot depend on the definition of Child but I can't prove that to myself] Note that the parent template method bodies are free to rely on the complete type of child because they will be dependent types and thus their instantiation is delayed to the second phase of template compilation when the full definition of Child is available.

Mark B
  • 95,107
  • 10
  • 109
  • 188
1

No. The CRTP is not implementation specific. It must be supported by any standard compliant compiler.

Proving this from the words of the standard is quite a laborious task. I'll delete this answer if someone comes along with an answer with references (and makes a comment here to remind me to do so).


As PasserBy say:

Strictly speaking, it works because the injected class name exists before the base clause

The base clause is everything after the colon in:

class Derived : CRTP<Derived>

As François Andrieux and underscore_d points out, we can indirectly prove that the standard requires CRTP to work, because it defines std::enable_shared_from_this which uses the CRTP. (However, it was well defined in C++89, and enable_shared_from_this wasn't added until C++11).

  • 2
    I thought proving it will be hideous too, but some experimenting and helpful compiler errors later, it is actually [pretty straightforward](https://stackoverflow.com/a/47820588/4832499). – Passer By Dec 14 '17 at 19:20