Dependent scope and nested templates

No, this has not to do with C++’s grammar, but with lazy instantiation of C++ templates
and two phase lookup.


In C++, a dependent name is a name or symbol whose meaning depends on one or more
template parameters:

template <typename T>
struct Foo {
    Foo () {
        const int x = 42;
        T::Frob (x);
    }
};

By parsing that snippet alone, without knowin all future values of T, no C++ compiler can deduce whether frob in T is a function name, a type name, something else, or whether it exists at all.

To give an example for why this is relevant, imagine some types you will substitute for T:

struct Vietnam {
    typedef bool Frob; // Frob is the name of a type alias
};    

struct Football {
    void Frob (int) {} // Frob is a function name
};

struct BigVoid {};     // no Frob at all!

Put those into our Foo-template:

int main () {
    Foo<Vietnam> fv;   // Foo::Foo would declare a type
    Foo<Football> ff;  // Foo::Foo would make a function call
    Foo<BigVoid> fbv;  // Foo::Foo is not defined at all
}

Relevant in this is the concept of two-phase lookup. In the first phase,
non-dependent code is parsed and compiled:

template <typename T>
struct Foo {
    Foo () {
        const int x = 42; // does not depend on T
        T::Frob (x);      // full check skipped for second phase, only rudimentary checking
    }
};

This first phase is what lets compilers emit error messages in the template definition itself.

The second phase would trigger errors of your template in conjunction with the then-known type T.

Some early C++ compilers would only parse templates once you instantiate them; with those compilers, disambiguation wasn’t needed, because at the point of instantiation, the template arguments are known. The problem with this one-phase lookup is that many errors in the template itself won’t be detected at all or only late in the compile, because templates are by default instantiated lazily, i.e. only parts of a class-template are expanded that are actually used, plus it gives you more cryptic error messages that possibly root in the template-argument.

So in order for two-phase lookup to work, you must help the compiler.
In this case, you must use typename in order to tell the compiler that you mean a type:

template <typename T>
struct Foo {
    Foo () {
        const int x = 42;
        typename T::Frob (x);
    }
};

The compiler now knows that x is a variable of type Frob 🙂

Leave a Comment