I'm working on a large project, which contains a section of code that compiles - but I don't understand how. I distilled it down to this simple example:
template <typename T>
struct First {
typedef int type; // (A)
typename T::Three order; // (B)
};
template <typename T> struct Second {
typedef typename T::type type;
};
template <typename T> struct Third {
int val;
T two;
};
struct Traits {
typedef First<Traits> One;
typedef Second<One> Two;
typedef Third<Two> Three;
};
int main(int argc, char** argv) {
Traits::One x;
};
The class First
is templated on Traits
and references Traits::Three
, which itself is a typedef
based on Two
, which is a typedef
based on First<Traits>
... hence it is circular. But this code compiles fine on both gcc4.6 and VC10. However, if I flip the ordering of the two lines marked (A)
and (B)
, the code does not compile, complaining about the typedef
inside of Second
.
Why does this code compile, and why is it that the ordering of the typedef
and the member variable matters?
Template metaprogramming (TMP) is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled.
Template compilation requires the C++ compiler to do more than traditional UNIX compilers have done. The C++ compiler must generate object code for template instances on an as-needed basis. It might share template instances among separate compilations using a template repository.
In other words, a template is a mechanism that allows a programmer to use types as parameters for a class or a function. The compiler then generates a specific class or function when we later provide specific types as arguments.
In general, yes, template classes are usually compiled every time they're encountered by the compiler.
There are a couple of things worth saying.
The code will break if Second
is modified to contain
T badObject;
with a long "instantiated from..." chain and ending with an "incomplete type" error, due to the circularity you expect, but not if you instead add
typename T::type object;
This is telling you that the compiler is cleverly observing it doesn't need to completely encapsulate T
, only to know what T::type
is. To illustrate this, note you can legally have
First { ... typedef T type; ... }
Second { typename T::type object; }
since T contains no currently-being-defined objects, or
First { ... typedef typename T::One type; ... }
Second { typedef typename T::type object; }
since the typedef
in Second
does not require an instance of any objects either - but not, say,
First { ... typedef typename T::One type; ... }
Second { typename T::type object; }
since only then is the compiler is actually required to nest a First<Traits>
object within a First<Traits>
object.
The issue with swapping (A) and (B) is that the clever trick the compiler pulled above is working by introducing a new copy of the definition of each specialized template, parsing it one line at a time. The error occurs if it hasn't got as far as the type definition in First
when it is required to know it by Second
.
You don't need a complete type for a typedef
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With