Подтвердить что ты не робот

Функция перегрузки и наследование

Я пытаюсь перегрузить некоторую функцию шаблона для выполнения определенного действия, если я его вызываю, используя данный класс MyClass или любой производный класс MyClassDer. Вот код:

#include <iostream>

struct MyClass {
    virtual void debug () const {
        std::cerr << "MyClass" << std::endl;
    };
};

struct MyClassDer : public MyClass {
    virtual void debug () const {
        std::cerr << "MyClassDer" << std::endl;
    };
};

template <typename T> void  func  (const T& t) {
    std::cerr << "func template" << std::endl;
}

void func (const MyClass& myClass) {
    std::cerr << "func overloaded" << std::endl;
    myClass.debug ();
}


int main(int argc, char **argv) {
    func (1);
    MyClass myClass;
    func (myClass);
    MyClassDer myClassDer;
    func (myClassDer);
}

Вывод:

func template
func overloaded
MyClass
func template

func (myClassDer) вызывает функцию шаблона вместо void func (const MyClass& myClass). Что я могу сделать, чтобы получить ожидаемое поведение?

Спасибо

4b9b3361

Ответ 1

Вы можете использовать SFINAE:

#include <type_traits>

template <typename T>
void func (const T& t, typename std::enable_if<!std::is_base_of<MyClass, T>::value>::type * = nullptr) {
    std::cout << "func template" << std::endl;
}

template <
    typename T
    , typename = typename std::enable_if<std::is_base_of<MyClass, T>::value>::type
>
void func (const T& t) {
    std::cout << "func overloaded" << std::endl;
    t.debug ();
}

Если у вас нет С++ 11, boost обеспечивает ту же функциональность.

Живой пример

ИЗМЕНИТЬ

Это должно работать без С++ 11 (с использованием boost):

#include "boost/type_traits.hpp"

template <typename T>
void func (const T& t, typename boost::enable_if<!boost::is_base_of<MyClass, T>::value>::type * = 0) {
    std::cout << "func template" << std::endl;
}

template <typename T>
void func (const T& t, typename boost::enable_if<boost::is_base_of<MyClass, T>::value>::type * = 0) {
    std::cout << "func overloaded" << std::endl;
    t.debug ();
}

Ответ 2

Это то, как работает разрешение перегрузки. Когда поиск завершается, он находит как шаблон, так и функцию. Затем выбираются типы шаблонов и начинается разрешение перегрузки. В случае аргумента типа MyClass два младших:

void func<MyClass>(MyClass const&);
void func(MyClass const&);

Которые одинаково хороши для аргументов, но второй - не шаблон. В случае MyClassDer:

void func<MyClassDer>(MyClassDer const&);
void func(MyClass const&);

В этом случае первый является лучшим кандидатом, чем второй, поскольку второй требует преобразования с производной базой и которое подбирается.

Существуют разные подходы к прямой отправке для вашего кода. Простейший просто подставляет тип аргумента MyClass и, таким образом, возвращает исходный случай:

func(static_cast<MyClass&>(myClassDer));

Хотя это просто, это нужно делать повсюду, и если вы забудете только в одном месте, будет вызвана неправильная вещь. Остальные решения сложны, и вам может потребоваться рассмотреть вопрос о том, не лучше ли было бы предоставлять разные имена функций.

Один из вариантов - использование SFINAE для отключения шаблона, когда тип получен из MyClass:

template <typename T>
typename std::enable_if<!std::is_base_of<MyClass,MyClassDer>::value>::type
func(T const & t) { ... }

В этом случае после поиска компилятор выполнит вывод типа, и он выведет T как MyClassDer, тогда он будет оценивать возвращаемый тип функции (SFINAE может также применяться к другому шаблону или аргумент функции). is_base_of даст false, а enable_if не будет иметь вложенный тип. Объявление функции будет плохо сформировано, и компилятор отбросит его, оставив разрешение с единственным кандидатом, перегрузкой без шаблона.

Другим вариантом будет предоставление единого интерфейса шаблона и отправка внутри шаблона или перегрузки (другим именем) с помощью отправки тегов. Идея аналогична, вы оцениваете черту внутри шаблона и вызываете функцию с типом, сгенерированным из этой оценки.

template <typename T>
void func_impl(T const&, std::false_type) {...}
void func_impl(MyClass const&, std::true_type) {...}

template <typename T>
void func(T const &x) { 
   func_impl(x,std::is_base_of<MyClass,MyClassDer>::type()); 
}

Существуют и другие альтернативы, но это два общих, а остальные в основном основаны на тех же принципах.

Опять же, рассмотрим, стоит ли проблема сложности решения. Если вызов func сам выполняется внутри общего кода, простое изменение имени функции решит проблему без излишней сложности, которую вы или другие поддерживающие могут иметь проблемы с сохранением.

Ответ 3

Почему ваш код не работал: см. @David отличное объяснение. Чтобы заставить его работать, вы можете использовать SFINAE ( "Ошибка сбоев не является Errro" ), добавив параметр скрытого шаблона Requires (имя предназначено только для целей документации)

template <
     typename T, typename Requires = typename 
     std::enable_if<!std::is_base_of<MyClass, T>::value, void>::type 
> 
void  func  (const T& t) {
    std::cerr << "func template" << std::endl;
}

Это отключит этот шаблон для разрешения перегрузки всякий раз, когда T равен или получен из MyClass, и вместо этого выберет правильную функцию (для которой будут выполняться преобразования Derived-to-Base, в отличие от аргумента шаблона вычет, который учитывает только точные соответствия). Вы можете, очевидно, поиграть с этим и добавить несколько перегрузок с неперекрывающимися условиями внутри std::enable_if, чтобы иметь мелкозернистый выбор перегрузок функций, которые будут рассмотрены. Но будьте осторожны, SFINAE тонкий!

Пример Live.

Примечание. Я написал свой SFINAE с синтаксисом С++ 11, используя параметр шаблона по умолчанию для шаблонов функций. В С++ 98 вам нужно добавить обычный параметр по умолчанию или изменить тип возвращаемого значения.

Ответ 4

Полиморфизм возникает во время выполнения, но выбор перегруженной функции происходит во время компиляции.

Итак, во время компиляции лучшей перегрузкой для принятия MyClassDer является

func<MyClassDer> (const MyClassDer& t)

а не

func<MyClass> (const MyClass& t)

тогда компилятор выбирает первый.


Возможность решить проблему:

func(static_cast<MyClass&>(myClassDer));

Ответ 5

MyClass *myClassDer = new MyClassDer;
func(*myClassDer);
delete myClassDer;

Ответ 7

Потому что ваша перегруженная сигнатура функции,

void func (const MyClass& myClass)
{
    std::cerr << "func overloaded" << std::endl;
    myClass.debug ();
}

i.e хочет MyClass в качестве своего параметра, и вы вызываете его с помощью MyClassDer. Поэтому во время компиляции он решает другую перегруженную функцию и связывается с ней. Поскольку другая функция шаблонизирована, компилятор не имеет никакой связи с этим.

Итак, если вы хотите передать объект MyClassDer, вы все равно можете это сделать, используя полиморфизм.

MyClass *myClassDer = new MyClassDer;
func(*myClassDer);

Ответ 8

Просто введите его в базовый тип:

MyClassDer myClassDer;
func(static_cast<MyClass&>(myClassDer));