15
votes

I am very surprised that on various sampled versions of g++, the following compiles without error or warning:

// Adapted from boost::checked_delete()
template <class T> inline void assert_complete()
{
  typedef char type_must_be_complete[ sizeof(T) ? 1 : -1 ];
  (void) sizeof(type_must_be_complete);
}

class X;

void f()
{
  assert_complete<X>();
}

class X {};

int main() {}

If the definition of X is missing or in a different translation unit, I do get errors.

But in the program as above, isn't the definition of f the single instantiation point of my template? And isn't the incompleteness of X at that instantiation point a semantic error?

Does the (C++03 and/or C++11 Draft) Standard call this program well-formed, ill-formed, ill-formed but diagnostic not required, or undefined behavior?

Edit: @David Rodriguez - dribeas reports that clang++, comeau, and Visual Studio 2010 also accept similar code.

2
Interestingly when I added an int to the definition of X, sizeof(T) was 4. Templates have been known in the past to have precognitive abilities.Seth Carnegie

2 Answers

12
votes

(I was waiting to Alf Steinbach to post an answer, but since he is not doing it, I will post the reference that he mentioned in the Lounge C++ chat):

The standard indicates that template instantiations are performed after the translation unit has already been translated, so that in time, template instantiations happen after all the non templated elements have already been processed. This is described in section 2.2 Phases of translation:

Paragraphs 1-6 define the preprocessor work and basic textual operations (conversions of the character set, concatenation of literals...)

7/ White-space characters separating tokens are no longer significant. Each preprocessing token is converted into a token. (2.7). The resulting tokens are syntactically and semantically analyzed and translated as a translation unit.

8/ Translated translation units and instantiation units are combined as follows: Each translated translation unit is examined to produce a list of required instantiations. The definitions of the required templates are located. It is implementation-defined whether the source of the translation units containing these definitions is required to be available. All the required instantiations are performed to produce instantiation units. [ Note: These are similar to translated translation units, but contain no references to uninstantiated templates and no template definitions. — end note ] The program is ill-formed if any instantiation fails.

I have removed some of the notes for brevity. Now the important bit seems to be that the code is translated without triggering template instantiations in one step, and then in a later step the templates are instantiated. This in turn means that if the type is complete anywhere in the translation unit, it will have been processed by the time the compiler gets to the instantiation.

Disclaimer: This seems like a good reason for all of the compilers that I have tried showing the exact same behavior (gcc, clang, comeau, VS 2010), but this only states when in time the instantiation is performed, it does not explicitly state that the type can be incomplete at the point of instantiation of the template.

6
votes

This line completes the type:

class X {};

So long as the type is completed somewhere in the translation unit then any earlier incomplete instances will be completed.

Here's the relevant section from the standard [basic.types] (3.9 paragraph 7):

A class type (such as “class X”) might be incomplete at one point in a translation unit and complete later on; the type “class X” is the same type at both points. The declared type of an array object might be an array of incomplete class type and therefore incomplete; if the class type is completed later on in the translation unit, the array type becomes complete; the array type at those two points is the same type. The declared type of an array object might be an array of unknown size and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points (“array of unknown bound of T” and “array of N T”) are different types. The type of a pointer to array of unknown size, or of a type defined by a typedef declaration to be an array of unknown size, cannot be completed.