Почему методы шаблона 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
объявление функции в фактическое управление. (Возможно, было бы неплохо иметь возможность псевдонима таких функций вместе, чтобы их адреса сравнивались равными; хотя указатели на виртуальные функции-члены работают по-разному, идентичные функции могут быть микро-оптимизацией.)