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