Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 212
TBBle Avatar asked Dec 11 '13 08:12

TBBle


People also ask

What is explicit template instantiation?

You can use explicit instantiation to create an instantiation of a templated class or function without actually using it in your code. Because this is useful when you are creating library (. lib) files that use templates for distribution, uninstantiated template definitions are not put into object (. obj) files.

Why it is necessary to instantiate a template?

In order for any code to appear, a template must be instantiated: the template arguments must be provided so that the compiler can generate an actual class (or function, from a function template).

Can template methods be virtual?

No, template member functions cannot be virtual.

Can you override a non virtual method C++?

It is allowed and it's not a problem - and if you call the method directly as you have done it will be called fine.


1 Answers

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).

like image 67
James McNellis Avatar answered Nov 15 '22 05:11

James McNellis