3

I was recently surprised to know that this code compiles (at least on gcc and MSVC++):

template<typename T>
class A {
public:
    T getT() { return T(); }
};

class B : public A<B> { };

When this doesn't:

class A;

class B : public A { };

class A {
public:
    B getB() { return B(); }
};

It seems weird to me that a template class could take an incomplete type as a template parameter and have a function that returned one by calling its constructor and still compile. So where exactly are complete types required (or if the list would be shorter, where are they not required)?

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • It might help if you think of templates not as *code* but rather as a *code generation tool*, and only actual code needs to make semantic sense. – Kerrek SB Feb 02 '12 at 20:26
  • @KerrekSB Yes, but I was unclear as to _when_ the _code generation tool_ actually generated its code. I'm clear on it now though. – Seth Carnegie Feb 02 '12 at 22:22

3 Answers3

6

Following are the scenarios where Complete types are not required:

  • Declaring a member to be a pointer or a reference to the incomplete type.
  • Declaring functions which accepts/return incomplete types.
  • Defining functions which accept/return pointers/references to the incomplete type.
  • As a template type argument.

Basically You are fine using an Incomplete type, at any place where the compiler does not need to know the memory layout of the type.

As for the template type argument being allowed to be an Incomplete type, the Standard explicitly says so in 14.3.1 Template type arguments

Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • I believe `struct a; a * p;` and `union b; b * p;` will compile fine, you just don't want to dereference the pointer. However, `union c; c * p;` would result in a compile error. – Jesse Good Feb 02 '12 at 04:28
  • Dereferencing does need a complete type so Yes to the first two. However, I don't see a difference between the third expression and the second expression,Am I missing something? – Alok Save Feb 02 '12 at 04:31
  • Check out this [SO question](http://stackoverflow.com/questions/71416/forward-declaring-an-enum-in-c). Also, I tested on [ideone](http://ideone.com/bxE5s) and got the following error for enums. – Jesse Good Feb 02 '12 at 04:47
  • @Als I'm not sure about "Basically You are fine using an Incomplete type, at any place where the compiler does not need to know the memory layout of the type." If `class B` declares `public: typedef int TInt;`, `class A < T >` cannot use `typedef T::TInt TIntFromDerived;`, when `class B : public A< B >{...};`. I don't think `typdef` has anything to do with memory layout of `B`, but it's not defined yet, nonetheless. – lapk Feb 02 '12 at 04:53
  • Are there any special rules for how you can use the template type argument in a template if it is an incomplete type? – Seth Carnegie Feb 02 '12 at 05:46
  • @SethCarnegie: I gave a quick look at the Standard but I didn't find any explicit mention, The Dr. Dobb's [article](http://drdobbs.com/184403814) by Mathew Austern has some description though. – Alok Save Feb 02 '12 at 06:40
  • @AzzA: Perhaps there are some specific rules w.r.t templates with Incomplete types? – Alok Save Feb 02 '12 at 06:42
  • @Als I think, you mentioned the section in Standard where the rules are defined (Section 14.6). However, the two-phase name look-up thing, I'm afraid, might not be implemented according to these rules on all compilers. (Notably, MSVC++, I think, deviates from it.) – lapk Feb 02 '12 at 06:52
  • @AzzA: Yes the section which I mentioned explicitly approves usage of template type argument by an incomplete type, but I didn't(probably failed to) see any other explicit rules for the type being Incomplete.And Yes perhaps you are correct this is a dark boundary area, where all compilers just might not be standard conforming *yet*. – Alok Save Feb 02 '12 at 06:59
4

This is how CRTP works due to two stage template parsing. Template member functions are not parsed until their instantiation.

EDIT: Maybe, the wording is not very precise. What I meant to say that when compiler sees class B : public A< B > {...};, it goes through A< B >, notices that there is a function B get() {...}, but does not evaluate its definition, leaving it until function's actual instantiation, at which point B has to be a complete type.

EDIT: I believe, the exact rules are covered in Standard section 14.6 (as Als pointed out in his answer). It deals with dependent and non-dependent names and their resolution at different times during compilation according to two-phase template name look-up. However, unfortunately, the two-phase name look-up implementation can differ from Standard on different compilers. Same code might compile on GCC and might not on MSVC++ and vice versa. Even more, seemingly same code might be rejected by same compiler. On MSVC++ I had an issue when base class was using a pointer to derived class function as a default argument for its function. It did not compile under MSVC++ and compiled under GCC (correctly). However, using derived class constructor as a default parameter compiled with both compilers, even on MSVC++. Go figure.

lapk
  • 3,838
  • 1
  • 23
  • 28
  • Member functions of a class template are parsed at the point of the definition (this is the reason for using `typename` and `template` to indicate that a member is a type or template resp.), but they are not instantiated before usage. – Begemoth Feb 02 '12 at 04:13
  • I guess, I tried to say that function definition is not evaluated at the first stage of parsing. – lapk Feb 02 '12 at 04:17
  • Sorry, but when is the function's actual instantiation? When it is called? – Seth Carnegie Feb 02 '12 at 05:46
  • @SethCarnegie When when function is called or its address taken. – lapk Feb 02 '12 at 06:00
  • @SethCarnegie For example, `B tmp;` implicitly instantiates `A< B >` due to inheritance, while `B` is still being instantiated and is not complete. Declaration of `get();` in `A< B >` is noticed by compiler, but its definition is not evaluated, `A< B >` and then `B` get instantiated. If later you call `tmp.get();` at this point compiler will instantiated `get();` and will require `B` to be a complete type (which it is now). If you will never call `get();`, compiler will not even generate code for it. – lapk Feb 02 '12 at 06:09
1

A template isn't really code; it's a template that describes how to build the code, once you fill in the missing pieces (the type parameters). Because of this the compiler allows more leeway in a template definition than it does an actual code definition. When you actually use the template, with the types identified, the compiler needs to generate actual code and all the usual rules apply.

A full definition isn't required if the compiler doesn't need to know the size or offsets to members of the object. Defining a pointer or reference to a class for example doesn't need either of those. At the point where you try to use the pointer or reference you will need a full definition.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622