Итак, создать такую функцию нельзя, знаю. Однако мне очень требуется. В связи с этим интересуюсь, есть ли какие-нибудь обходные примеры? Пока кроме такого ничего не придумал:
class A
{
protected:
virtual void _func(const char*) = 0; // метод для класса B
virtual void _func(float) = 0; // метод для класса C
public:
template<class T> void func(T e) { this->_func(e); }
};
class B : public A
{
private:
void _func(const char* i) {};
protected:
void _func(float i) { std::cout << i << std::endl; };
};
class C : public A
{
private:
void _func(float i) { std::cout << "тест" << std::endl; };
protected:
void _func(const char* i) { std::cout << i << std::endl; };
};
int main()
{
B b;
b.func(14.4); // вызовем _func(float) из B
C c;
c.func("Привет"); // вызовем _func(int) из C
c.func(14.9);
return 0;
}
Однако недостатки очевидны: приходиться переопределять ненужную виртуальную функцию в каждом классе, которой пользоваться не придется. При этом, можно каждому дочернему классу передать аргумент, который мы не хотим передавать(показано в методе void _func(float i) { std::cout << "тест" << std::endl; }; ) Есть ли более элегантные решения? Спасибо.
Я не очень понял, чего вы хотите добиться в примере, но если говорить о виртуальных шаблонных функциях - она не может быть виртуальной, однако шаблонный класс может иметь виртуальную функцию. На определенном этапе инстанцирования шаблонов типы, которые можно передавать в шаблонную псевдовиртуальную функцию становятся известными (иначе бы и смысла не было), а значит ничего не мешает создать кортеж указателей на такие шаблонные классы с обычными виртуальными функциями, конкретизированные нужными вам типами.
template<class T>
class Visitor{
virtual void visit(T& value);
};
template<class... Ts>
class Storage{
std::tuple<Visitor<Ts>*...> m_visitors;
public:
template<std::size_t I, class T>
void visit(T& value){
auto visitor = std::get<I>(m_visitors);
visitor->visit(value);
}
};
Тут возникает проблема с инициализацией визитёров - на каждый тип нужен свой экземпляр, но можно решить проблему при помощи обобщенных лямбд.
В Storage::visit I и T на самом деле связаны, но без метопрограммирования одно из другого получить не получится.
Другой вариант - передавать в качестве аргумента виртуальной функции std::variant с допустимыми типами, а уже реализация виртуальной функции решит, что делать с конкретными типами. Если типы-аргументы имеют разный размер, в std::variant можно передавать ссылки, а не значения (оборачивая их в std::reference_wrapper), или можно использовать boost::variant, который поддерживает обычные ссылки.
template<class... Ts>
class Storage{
std::tuple<Ts...> m_values;
virtual void visit(boost::variant<Ts&...> ref){}
public:
Storage(std::tuple<Ts...> values): m_values(values) {}
void callVisit(){
std::apply([this](auto&... v) mutable{
auto visit = [this](auto& v){ this->visit(v); return 0;};
int unused[] = {visit(v)...}; // Вызов this->visit для всех значений кортежа
}, m_values);
}
};
template<class... Ts>
class StorageImpl: public Storage<Ts...>{
void visit(boost::variant<Ts&...> ref) override {
boost::apply_visitor([](auto& value){
// тут по сути тело шаблонной виртуальной функции, value - шаблонный аргумент
} ,ref);
}
public:
using Storage<Ts...>::Storage;
};
Ничего не мешает передавать два и более аргумента при таком подходе, главное - так или иначе узнать типы, допустимые для передачи.
Ваш дизайн сейчас примерно соответствует такому: абстрактный класс Животное, в который вы одновременно запихиваете функции Летать и Плавать, и далее вы хотите из него порождать, например, Орла и Дельфина. Но это неверный подход. Где, в каком месте у вас "Животное вообще" должно летать? или плавать? Да, есть какие-нибудь летучие рыбы или там утки - но если вы поступаете так, то ограничиваете понятие животных только теми, кто может летать и плавать одновременно.
Если у вас будет код, который вы хотите - с запретом летать в производном классе, например - то к чему приведет ваш (вполне корректный) вызов через указатель на базовый абстрактный класс типа Животное->Летать при передаче ему какого-то Тигра? К ошибке времени выполнения.
В самом лучшем случае нужно дополнить иерархию абстрактными классами, скажем, Птицы и Водоплавающие, а уже потом на них строить конкретные классы. (Кстати, тогда летающие рыбы будут получаться множественным наследованием.) Не надо идти против природы.
Если вы решите свою проблему, запрещая передачу не того аргумента - вы получите новую проблему - обработку ошибки передачи неверного аргумента во время работы программы, в то время как при правильной иерархии вылавливанием такой ошибки может заняться компилятор.
Если и это непонятно - умываю руки, делайте квадратное колесо, а потом спрашивайте, как написать трактор, который будет способен тащить телегу с такими колесами...
Сборка персонального компьютера от Artline: умный выбор для современных пользователей