Why can't C++ explicitly-instantiated template methods override virtual methods?

1.6k views Asked by At

Why does TemplateChild in the following code not work? I know that virtual methods cannot be templates, but why can explicitly-instantiated template methods not override virtual methods?

#include <iostream>

class VirtBase
{
public:
    VirtBase() {};
    virtual ~VirtBase() {};

    virtual void method( int input ) = 0;
    virtual void method( float input ) = 0;
};

class RegularChild : public VirtBase
{
public:
    RegularChild() {};
    ~RegularChild() {};

    void method( int input ) {
        std::cout << "R" << input << std::endl;
    }
    void method( float input ) {
        std::cout << "R" << input << std::endl;
    }
};

class TemplateBounceChild : public VirtBase
{
public:
    TemplateBounceChild() {};
    ~TemplateBounceChild() {};

    void method( int input ) {
        this->method<>( input );
    }
    void method( float input ) {
        this->method<>( input );
    }
    template< typename INPUT >
    void method( INPUT input ) {
        std::cout << "B" << input << std::endl;
    };
};

class TemplateChild : public VirtBase
{
public:
    TemplateChild() {};
    ~TemplateChild() {};

    template< typename INPUT >
    void method( INPUT input ) {
        std::cout << "T" << input << std::endl;
    };
};

template void TemplateChild::method< int >( int );
template void TemplateChild::method< float >( float );

int main( int, char**, char** )
{
    int i = 1;
    float f = 2.5f;

    VirtBase * v;

    RegularChild r;
    v = &r;

    r.method( i );
    r.method( f );
    v->method( i );
    v->method( f );

    TemplateChild c; // TemplateBounceChild here works correctly.
    v = &c;

    c.method( i );
    c.method( f );
    v->method( i );
    v->method( f );

    return 0;
}

gcc 4.4.7 (CentOS 6) and Clang 3.3 (trunk 177401) agree that the two pure virtual methods are not implemented in TemplateChild, although at this point in the compilation TemplateChild explicitly has a method named 'method' that takes a float, and a method named 'method' that takes an int.

Is it just because the explicit instantiation has come too late for TemplateChild to be considered non-pure virtual?

Edit: C++11 14.5.2 [temp.mem]/4 says that this isn't allowed for specialisations. But I can't find anything so clear in the [temp.explicit] section for the same thing.

4 A specialization of a member function template does not override a virtual function from a base class.

I also edited TemplateBounceChild to match the example used in that section of the C++11 draft.

3

There are 3 answers

6
Billy ONeal On BEST ANSWER

Because the Standard Says So. See C++11 14.5.2 [temp.mem]/3:

A member function template shall not be virtual.

3
James McNellis On

Let's consider what would happen if this was allowed.

The definition of the class TemplateChild may be present in multiple translation units (source files). In each of these translation units, the compiler needs to be able to generate the virtual function table (vtable) for TemplateChild, to ensure that the vtable is present for the linker to consume.

The vtable says, "for an object whose dynamic type is TemplateChild, these are the final overriders for all virtual functions." For example, for RegularChild, the vtable maps the two overrides RegularChild::method(int) and RegularChild::method(float).

Your explicit instantiations for TemplateChild::method will only appear in one translation unit, and the compiler only knows that they exist in that one translation unit. When compiling other translation units, it doesn't know that the explicit instantiations exist. This means that you'll end up with two different vtables for the class:

In the translation unit where the explicit instantiations are present, you'll have a vtable that maps the two overrides TemplateChild::method<int>(int) and TemplateChild::method<float>(float). This is okay.

But in the translation units where the explicit instantiations are not present, you'll have a vtable that maps to the base class virtual functions (which in your example are pure virtual; let's just pretend there are base class definitions).

You might even have more than two different vtables, e.g. if the explicit instantiations for int and float each appear in their own translation unit.

In any case, now we have multiple different definitions for the same thing, which is a major problem. At best the linker might pick one and discard the rest. Even if there was some way to tell the linker to pick the one with the explicit instantiations, you'll still have the problem that the compiler may devirtualize virtual function calls, but to do that, the compiler needs to know what the final overriders are (in effect, it needs to know what's in the vtable).

So, there are major issues that would arise if this was allowed, and given the C++ compilation model, I don't think these issues are resolvable (at least not without major changes to the way C++ compilation is done).

5
Potatoswatter On

An explicit instantiation does nothing more than cause the template to be instantiated. For a function template, this is the same effect as using it. There is no reason to expect anything to work differently whether a member function is explicitly instantiated or used regularly.

The template specialization cannot override the non-template function because they do not have the same name. The specialization is named by the template-id including template arguments. Ignoring the template arguments in the signature, several specializations with different arguments could have the same signature.

If the language wanted to determine that a specialization should be a virtual override because the signature coincides with a base class virtual member, it would have to determine that all the template arguments could have been deduced if the function were called with some arguments matching some virtual function. It wouldn't be able to rely on inspecting the way you actually called the function (which looks like a virtual dispatch because of deduction), because you could call it in a more obscure way using template arguments, or never call it at all (which is the problem you're trying to work around using explicit instantiation).

The combination of N base class virtual functions and M derived class templates potentially matching them would have O(N*M) complexity. The special feature would scale poorly in this case.

So, it's better just to be explicit with a regular virtual function declaration per actual override. (It would perhaps be nice to be able to alias such functions together so their addresses would compare equal, though; although pointers to virtual member functions work differently, identical functions could be a micro-optimization.)