Полиморфизм в функциях шаблонов

#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. Ваш второй пример — это не специализация, это перегрузка. Это также не сработает, если передается производный класс, поскольку шаблон выдаст точное соответствие.