Почему методы шаблона C++ с явным экземпляром не могут переопределять виртуальные методы?

почему TemplateChild в следующем коде не работает? Я знаю, что виртуальные методы не могут быть шаблонами, но почему явно созданные методы шаблонов не могут переопределять виртуальные методы?

#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) и Clang 3.3 (trunk 177401) согласны с тем, что два чистых виртуальных метода не реализованы в TemplateChild, хотя на данный момент в компиляции TemplateChild явно имеет метод с именем "method", который принимает float, и метод с именем "method" это требует int.

это просто потому, что явный экземпляр пришел слишком поздно для TemplateChild, чтобы считаться не чистым виртуальным?

Edit: C++11 14.5.2 [temp.mem] / 4 говорит, что это не разрешено для специализаций. Но я не могу найти ничего такого ясного в [temp.explicit] Раздел для того же самого.

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

Я также отредактировал TemplateBounceChild в соответствии с примером, используемым в этом разделе Черновика c++11.

3 ответов


потому что стандарт говорит Так. См. C++11 14.5.2 [temp.mem] / 3:

шаблон функции-члена не должен быть виртуальным.


давайте рассмотрим, что произойдет, если это будет разрешено.

определение класса TemplateChild может присутствовать в нескольких единицах трансляции (исходные файлы). В каждой из этих единиц перевода компилятор должен иметь возможность генерировать виртуальную таблицу функций (vtable) для TemplateChild, чтобы убедиться, что vtable присутствует для компоновщика, чтобы потреблять.

vtable говорит: "для объекта, динамический тип которого TemplateChild, это окончательная overriders для всех виртуальная функция."Например, для RegularChild, vtable отображает два переопределения RegularChild::method(int) и RegularChild::method(float).

ваши явные экземпляры для TemplateChild::method будет отображаться только в одном блоке перевода, и компилятор знает только, что они существуют в этом одном блоке перевода. При компиляции других единиц перевода он не знает, что существуют явные экземпляры. Это означает, что вы в конечном итоге с двумя разные vtables для класса:

в блок перевода, где присутствуют явные экземпляры, у вас будет vtable, который сопоставляет два переопределения TemplateChild::method<int>(int) и TemplateChild::method<float>(float). Все нормально.

но в единицах перевода, где явных экземпляров нет, у вас будет vtable, который сопоставляется с виртуальными функциями базового класса (которые в вашем примере являются чисто виртуальными; давайте просто притворимся, что есть определения базового класса).

у вас может быть более двух разных vtables, например, если явные экземпляры для int и float у каждого появляются собственные переводы.

в любом случае, теперь у нас есть несколько разные определения для того же самого, что является серьезной проблемой. В лучшем случае линкер мог выбрать один и выбросить остальные. Даже если был какой-то способ сказать компоновщику выбрать тот, с явными экземплярами, у вас все равно будет проблема, что компилятор может девиртуализировать вызовы виртуальных функций, но для этого компилятор должен знать, какой окончательный overriders (в силу, он должен знать, что в таблице vtable).

Итак, есть основные проблемы, которые возникли бы, если бы это было разрешено, и учитывая модель компиляции C++, я не думаю, что эти проблемы разрешимы (по крайней мере, без серьезных изменений в способе компиляции C++).


явный экземпляр не делает ничего больше, чем вызывает экземпляр шаблона. Для шаблона функции это тот же эффект, что и при его использовании. Нет никаких оснований ожидать, что работать по-другому, является ли функция-член, явно или регулярно используется.

специализация шаблона не может переопределить функцию, не являющуюся шаблоном, поскольку они не имеют одного имени. Специализация называется шаблоном-id, включая аргументы шаблона. Игнорируя аргументы шаблона в подписи, несколько специализаций с разными аргументами могут иметь одну и ту же подпись.

Если язык хотел определить, что специализация должна быть виртуальным переопределением, поскольку подпись совпадает с виртуальным членом базового класса, он должен был бы определить, что все аргументы шаблона могли быть выведены, если функция была вызвана с некоторыми аргументами, соответствующими некоторой виртуальной функции. На него нельзя было положиться. проверка того, как вы фактически вызвали функцию (которая выглядит как виртуальная отправка из-за дедукции), потому что вы можете вызвать ее более неясным способом, используя аргументы шаблона, или никогда не вызывать ее вообще (что является проблемой, которую вы пытаетесь обойти с помощью явного создания экземпляра).

комбинация N базового класса virtual функции и M шаблонов производных классов, потенциально соответствующих им, будут иметь сложность O(N*M). Особенность будет плохо масштабироваться в этом случай.

Итак, лучше просто быть явным с регулярным virtual объявление функции в фактическое управление. (Возможно, было бы неплохо иметь возможность псевдонима таких функций вместе, чтобы их адреса сравнивались равными; хотя указатели на виртуальные функции-члены работают по-разному, идентичные функции могут быть микро-оптимизацией.)