[Из песочницы] Специализация шаблона базовым классом
Есть несколько базовых классов, наследники и некоторый шаблонный обработчик, выполняющий какие-то действия с экземпляром наследников. Его поведение зависит от того, какие классы являются базовыми для обрабатываемого класса. Возможный вариант я хочу показать.Пусть у нас есть несколько базовых классов и классы, которые могут от них наследоваться.Итак, имеем Base1, Base2, Base3 и классы Deliver12, Deliver23. class Deliver12: public Base1, public Base2 {};
class Deliver23: public Base2, public Base3
{};
И есть некоторый класс Executor.
template
Решение есть, оно описано в книге А. Александреску «Современное проектирование на C++» в разделе «Распознавание конвертируемости и наследования на этапе компиляции». Идея состоит в использовании перегрузки функции принимающей разные типы параметров и возвращающей разные типы. Для определения типа Александреску использовал sizeof (в той редакции, что попалась мне в руки), но в стандарт C++11 был добавлен оператор decltype. Это избавляет от написания лишнего кода.
Итак, перепишем Executor с учетом выше сказанного и заодно добавим хоть какую-нибудь реализацию для метода operator ():
template
template
template
void main () { Deliver12 d12; Deliver23 d23; double d;
Executor
T унаследован от Base1 T унаследован от Base3 Общий вариант Для удобства и красоты вызов Executor: operator () можно обернуть в шаблонную функцию:
template
void main () { Deliver12 d12; Deliver23 d23; double d;
execute (d12); execute (d23); execute (d); } Получилось, вроде, неплохо. Теперь дополнительно специализируем поведение при наследовании от Base2. Не нужно даже специализировать класс Executor, достаточно добавить перезгрузку функции selector и попробовать скомпилировать. Компилятор выдаст сообщение с ошибкой, что он не может выбрать какой вариант функции selector использовать. Как разрешить такую ситуацию? В первую очередь нужно определить какое поведение хотим получить когда класс одновременно унаследован от двух классов, которые влияют на поведение класса Executor. Рассмотрим некоторые варианты:
1. Один из классов более приоритен и второй игнорируем;2. Для ситуации необзодимо специальное поведение;3. Необходимо вызвать последовательно обработку для обоих классов.
Так как 3 пункт является частным случаем 2 пункта, то его рассматривать не будем.
Нужно чтобы функция selector могла распознать варианты с двойным наследованием. Для этого добавим второй аргумент, который будет указателем на другой базовый класс и рассмотрим задачу приняв, что при наличии родителей Base1 и Base2 более приоритетным является Base1, а при наличии Base2 и Base3 необходимо специальное поведение. В таком случае перегрузка функции selector и методо execute будут иметь вид:
class Base23;
void selector (…); Base1 selector (Base1*, …); Base1 selector (Base1*, Base2*); Base2 selector (Base2*, …); Base23 selector (Base2*, Base3*); Base3 selector (Base3*, …);
template
Если по каким-то причинам нет возможности использовать компилятор с поддержкой стандарта C++11, то вместо decltype можно воспользоваться sizeof. Дополнительно нужно будет объявить вспомогательные классы для типов возвращаемых функцией selector. Важно, чтобы функция sizeof возвращала для этих классов разное значение. Шаблоный класс Executor в таком случае должен специализироваться не типом, а целочисленным значением. Выглядеть это будет примерно так:
class IsUnknow { char c; } class IsBase1 { char c[2]; }; class IsBase23 { char c[3]; };
IsUnknow selector (…); IsBase1 selector (Base1*, …); IsBase1 selector (Base1*, Base2*); IsBase23 selector (Base2*, Base3*);
template
template
template
template