#c #templates #polymorphism
#c #шаблоны #полиморфизм
Вопрос:
Я хочу использовать функцию шаблона для обработки как полиморфных, так и неполиморфных классов. Вот 3 основных класса.
class NotDerived
{
};
class Base
{
public:
virtual ~Base() {}
void base_method() {}
};
class Derived : public Base
{
};
Поскольку у NotDerived нет виртуальных функций, я не могу использовать dynamic_cast, далее идет функция шаблона:
template<class T>
auto foo(Tamp; some_instance)
{
if (std::is_base_of_v<Base, T>)
{
//CASE_1: Works for d and b
/*some_instance.base_method();*/
//CASE_2: Works for d and b
/*auto lamb1 = [](Tamp; some_instance) {some_instance.base_method(); };
lamb1(some_instance);*/
auto lamb2 = [](Tamp; some_instance) {((Baseamp;)some_instance).base_method(); };
lamb2(some_instance);
}
}
Основные функции делают это:
void main()
{
Derived d{};
Base b{};
NotDerived nd{};
foo(d);
foo(b);
foo(nd);
}
Теперь я понимаю, почему CASE_1 не работает в случае передачи nd
переменной, но чего я не могу понять, так это того, что я должен явно привести some_instance
в функцию lamb2, чтобы вызвать base_method.
Может ли кто-нибудь объяснить, почему CASE_1, CASE_2 не работают, в то время как CASE_3 работает. Под работой я подразумеваю вызов base_method, если это возможно, без dynamic_casting.
Также можно ли использовать constexpr
для обработки таких случаев статического полиморфизма или скомпилированного полиморфизма (надеюсь, это законно называть так)
Комментарии:
1. Я не хочу вызывать base_method из nd. Я хочу передать переменные в функцию шаблона и позволить функции шаблона решать, вызывать ли base_method или передавать
2. о, извините, я неправильно прочитал ваш код, это моя вина 😉
Ответ №1:
Случай 3 не работает. Вы приводите к несвязанному типу и используете этот временный для вызова функции, поведение которой не определено.
Проблема с использованием
if (std::is_base_of_v<Base, T>)
{
//...
}
заключается в том, что все в ...
части должно быть доступно для компиляции. Что вам нужно, так это способ вызывать этот код только тогда, когда T
это тип, который вы хотите. Вы можете использовать constexpr, если хотите
if constexpr (std::is_base_of_v<Base, T>)
{
some_instance.base_method();
}
else
{
// do something else since T isn't a Base or derived from Base
}
И теперь, если std::is_base_of_v<Base, T>
равно false, то some_instance.base_method();
оно будет отброшено и никогда не будет скомпилировано.
Комментарии:
1. Case_3 работает, потому что он компилируется, но не вызывает base_method. Моя цель — не вызывать base_method из nd, я просто хочу, чтобы функция шаблона решала, вызывать ли base_method
2. @Demaunt
auto lamb2 = [](Tamp; some_instance) {((Baseamp;)some_instance).base_method(); }; lamb2(some_instance);
выполняет вызовbase_method
, который является UB, посколькуnd
не является производным отBase
.
Ответ №2:
Вам следует заглянуть в официальный документ о SFINAE. Код внутри вашего if будет скомпилирован в любом случае, поэтому он не будет компилироваться.
Если вы используете C 17, вы можете заменить его на constexpr if, который будет оценивать информацию внутри if во время компиляции и игнорировать код, если это необходимо:
template<class T>
auto foo(Tamp; some_instance) {
if constepxr (std::is_base_of_v<Base, T>) {
auto lamb2 = [](Tamp; some_instance) {((Baseamp;)some_instance).base_method(); }
lamb2(some_instance);
} else {
// Whatever you want to do
}
}
Если вы используете более старую версию C , простая перегрузка функции может решить вашу проблему:
template<class T>
auto foo(Tamp; some_instance) {
// Do whatever
}
auto foo(Baseamp; some_instance) {
auto lamb2 = [](Tamp; some_instance) {((Baseamp;)some_instance).base_method(); }
lamb2(some_instance);
}
В качестве альтернативы вы можете использовать механизм SFINAE для поиска нужной функции с помощью std::enable_if
:
template <typename T>
typename std::enable_if<std::is_base_of<Base, T>::value, void>::type
foo() {
auto lamb2 = [](Tamp; some_instance) {((Baseamp;)some_instance).base_method(); }
lamb2(some_instance);
}
template <typename T>
typename std::enable_if<!std::is_base_of<Base, T>::value, void>::type
foo() {
// Do whatever
}
Комментарии:
1. Ваш второй пример — это не специализация, это перегрузка. Это также не сработает, если передается производный класс, поскольку шаблон выдаст точное соответствие.