Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

Front Page / Technical Details / Portability / ETI

ETI

In context of C++ template problems, ETI is an abbreviation for "Early Template Instantiation" — a Microsoft Visual C++ - specific issue that has been a barrier to any serious work with templates on this platform until Microsoft developers fixed it in Visual C++ 7.1 (2003 .NET). Although the problem is relatively easy to work around if the right techniques are applied systematically through the codebase, the approach is definitely tedious and time-consuming. So, if one day you discover that you are spending too much time dealing with the issue, consider upgrading to the newer version of the compiler. In fact, seriously consider it regardless. The benefits of saved time, money and frustration are well worth the price.

The Problem

Here is a short demonstration of the issue with MSVC 6.x:

template< typename F, typename T > struct apply1
{
    typedef typename F::template apply<T>::type type;
};

Trying to compiling this innocent-looking code, we get:

portability.cpp(4) : error C2903: 'apply' : symbol is neither a class template 
            nor a function template
        portability.cpp(5) : see reference to class template instantiation 
            'apply1<F,T>' being compiled
portability.cpp(4) : error C2143: syntax error : missing ',' before '<'
        portability.cpp(5) : see reference to class template instantiation 
            'apply1<F,T>' being compiled
portability.cpp(4) : error C2059: syntax error : '<'
        portability.cpp(5) : see reference to class template instantiation 
            'apply1<F,T>' being compiled

The "symbol is neither a class template nor a function template" part of the diagnostics is actually often an indication of ETI-related problems. Another typical error message usually says something about nested type such-and-such not being a member of a global namespace.

Both cases are two sides of the same compiler bug, which we call "Early template instantiation": the compiler, for internal purposes, in order to process class template definitions, instantiates class templates with dummy template parameters (int's). That can happen both during parsing of template definitions (and such errors are most easy to identify and fix — the template definition itself just doesn't compile; the example above falls into this category), or later during template instantiation, and these one are hard to detect — the bug will only be triggered in some particular context.

ETI is always performed during parsing of the namespace-scope template definition, which basically means that any template definition that is rendered invalid by substituting its template parameters by ints might not compile, as it happened with our example:

template< typename F, typename T > struct apply1
{
    // typedef typename F::template apply<T>::type type;
    // ETI generates this: 
    typedef typename int::template apply<int>::type type;
};

If you compile this, you'll get exactly the same diagnostics as we've just seen.

Note that we've said "might not compile", because... well, the short answer is, "it depends". We haven't analyzed things to the point that we could tell you the exact condition when ETI leads to an error and when it doesn't, but that's not very important anyway — if it's an error, you just fix it (we'll show you how in a second), and if it's not, then you leave things as is. If one day the potential issue turns into a real one, then you apply the workaround we are about to give you.

The Symptoms

We've already looked at the typical diagnostics, so we won't repeat ourselves. Instead we'll just mention that many MSVC's INTERNAL COMPILER ERRORs (ICEs) are, in fact, caused by an ETI-related problem somewhere deep down the instantiation stack.

The Solution

There is no way we can change the compiler's behavior in this case, so what we have to do is to adjust to it and still make our templates do what we want. Surprisingly, in most cases it's quite simple to achieve:

// potentially unsafe
template< typename F > struct apply0
{
    typedef typename F::type type;
};

// now ETI-safe
template<> struct apply0<int>
{
    typedef int type;
};

Since the original template could have never been instantiated with int, providing a stub int specialization is completely innocent.