Class templates, their parent templates, and their method templates

4 minute read

Mixing templates and inheritance can cause some unfamiliar and confusing compiler errors.

The problem

A convoluted title for a somewhat convoluted subject. Imagine you have the following class template

template <typename T>
struct Base {
    void method_1() {}

    template <typename U>
    void method_2() {}
};

Now, if you want to inherit from this class, chances are that you want to support the full range of template parameters of the base class, so you would write your derived class as follows:

template <typename T>
struct Derived : Base<T> {
    void method_3();
};

What happens if Derived<T>::method_3() wants to call Base<T>::method_1()? You might write something like

template <typename T>
void Derived<T>::method_3() {
    method_1();
}

If you try to compile this, you would likely get a compiler error similar to the output I get using clang 5.0.0

16 : <source>:16:5: error: use of undeclared identifier 'method_1'; did you
mean 'method_3'?
    method_1();
    ^~~~~~~~
    method_3
15 : <source>:15:18: note: 'method_3' declared here
void Derived<T>::method_3() {
                 ^
1 error generated.
Compiler returned: 1

Explanation

This happens because in the context of Derived<T>, Base<T> is a dependent name. To put it simply, non-dependent names are looked up and bound at the point of template definition. Dependent names, on the other hand, are looked up and bound at the point of template instantiation.

In fact, all templates are compiled in two steps, referred to as Two Phase Lookup. Among other things, the first phase is responsible for looking up non-dependent names, while the second phase is responsible for the dependent name lookup.

One of the reasons for this is to allow for template code to refer to functions or classes that are visible only at the point of template instantiation, not at the point of definition.

The fix

There are a few ways to work with dependent names.

Using the using keyword

The using keyword can be used to bring dependent names into the scope of the class or the method, so that you can refer to the dependent name like you would normally.

template <typename T>
struct Derived : Base<T> {
    using Base<T>::method_1;

    void method_3();
};

template <typename T>
void Derived<T>::method_3() {
    method_1();
}

Referring to the dependent name directly

Instead of using the using keyword, you can instead refer directly to the class name when calling the function.

template <typename T>
void Derived<T>::method_3() {
    Base<T>::method_1();
}

Using this

Prefixing this-> in front of the method calls will also fix the compilation errors. In fact, prefixing the call with the this tells the compiler to do the name lookup specifically in the second phase of the template compilation.

template <typename T>
void Derived<T>::method_3() {
    this->method_1();
}

Referring to dependent names by prefixing with this-> is the recommended way. It is a lot more resilient to code changes; if you change the name of Base<T> in the examples above, this-> would still work correctly, while the others would have to be updated accordingly.

This can be particularly important when dealing with class templates with virtual member functions.

template <typename T>
struct Base {
    virtual void v_method() {}
};

template <typename T>
struct Derived : Base<T> {
    void call_method();
};

If, for example, the definition of Derived<T>::call_method() is

template <typename T>
void Derived<T>::call_method() {
    Base<T>::v_method();
}

And then you proceed to add an override for Base<T>::v_method() in Derived<T>, Derived<T>::call_method() will need to be updated. If you forget to update definition of Derived<T>::call_method(), the code will still compile but you’ll be inadvertently calling the wrong member function. On the other hand, using this-> avoids this scenario altogether.

template <typename T>
void Derived<T>::call_method() {
    this->v_method();
}

A wrinkle

In all the above examples, this-> works because it refers to the current instantiation (of the class templates). If, on the other hand, you’re referring to a different template instantiation while in a template instantiation (essentially, a dependent template), you have to make use of the template keyword in a way different than you may be used to.

template <typename T>
struct Base {
    template <typename U>
    void method_1() {}
};

template <typename T>
struct Derived : Base<T> {
    void method_2() {
        this->template method_1<T>();
    }
}

In order for Derived<T>::method_2() to call Base<T>::method_1<U>(), the name of the member function template needs to be prefixed with the keyword template. This is because the member function represented by Base<T>::method_1<U>() (i.e., the fully instantiated member function) is not available in the instantiation of the class template (Derived<T>). As a result, the C++ parsing rules state that the < in this->method_1<T>() be read as a less-than sign. The use of the template keyword tells the compiler to look at the original class template for the definition of the member function template and instead instantiate that. In fact, the template keyword can be used in this manner to even refer to member functions (as opposed to member function templates). It just isn’t explicitly required.

template <typename T>
struct Base {
    template <typename U>
    void method_1() {}

    void method_2() {}
};

template <typename T>
struct Derived : Base<T> {
    void method_3() {
        this->template method_1<T>();
        this->template method_2();
    }
};

A note about Visual Studio’s behavior

Microsoft Visual Studio, until version Visual Studio 2017 “15.3” (where you have to use the /permissive- switch), did not support two phase name lookup. As such, the compiler is more permissive and does not require the special treatment of dependent names in template definitions.

Further reading

Dependent Names

Updated:

Leave a Comment

Your email address will not be published. Required fields are marked *

Loading...