Рассмотрим два типа, один из которых наследуется от другого:
#include <iostream>
class shape { };
class circle : Shape { };
И две функции, которые принимают объект этого типа, соответственно:
void shape_func(const shape& s) { std::cout << "shape_func called!\n"; }
void circle_func(const circle& c) { std::cout << "circle_func called!\n"; }
Теперь я хочу шаблон функции, который я могу передать:
Вот моя попытка объявить этот шаблон функции:
template<class T>
void call_twice(const T& obj, void(*func)(const T&))
{
func(obj);
func(obj);
}
(На практике его тело будет делать что-то более полезное, но для демонстрации я просто позволю ему дважды вызывать переданную функцию с переданным объектом.)
int main() {
shape s;
circle c;
call_twice<shape>(s, &shape_func); // works fine
call_twice<circle>(c, &circle_func); // works fine
//call_twice<circle>(c, &shape_func); // compiler error if uncommented
}
Я надеялся, что третий звонок тоже сработает.
В конце концов, так как shape_func
принимает любой shape
также принимает circle
— так, подставив circle
за T
у компилятора должна быть возможность разрешать шаблон функции без конфликтов.
Фактически, именно так ведет себя соответствующая универсальная функция в C #:
// C# code
static void call_twice<T>(T obj, Action<T> func) { ... }
Это можно назвать как call_twice(c, shape_func)
без проблем, потому что, говоря в C # lingo, параметр типа T
из Action<T>
является контравариантный.
Возможно ли это в C ++?
То есть как бы функционировал шаблон call_twice
должны быть реализованы, чтобы принять все три вызова в этом примере?
Один из способов сделать это через SFINAE, лучше всего показано на примере:
#include <iostream>
#include <type_traits>
struct Shape {};
struct Circle : public Shape {};
template<class Bp, class Dp>
std::enable_if_t<std::is_base_of<Bp,Dp>::value,void>
call_fn(Dp const& obj, void (*pfn)(const Bp&))
{
pfn(obj);
}
void shape_func(const Shape& s) { std::cout << "shape_func called!\n"; }
void circle_func(const Circle& s) { std::cout << "circle_func called!\n"; }
int main()
{
Shape shape;
Circle circle;
call_fn(shape, shape_func);
call_fn(circle, circle_func);
call_fn(circle, shape_func);
}
Выход
shape_func called!
circle_func called!
shape_func called!
Как это устроено
Эта реализация использует простое (возможно, слишком много) упражнение, использующее std::enable_if
в сочетании с std::is_base_of
обеспечить квалифицированное разрешение перегрузки потенциально двумя различными типами (один из объектов, другой из списка аргументов функции обеспечения). В частности, это:
template<class Bp, class Dp>
std::enable_if_t<std::is_base_of<Bp,Dp>::value,void>
call_fn(Dp const& obj, void (*pfn)(const Bp&))
говорит, что этот шаблон функции требует двух аргументов шаблона. Если они либо одного типа ИЛИ Bp
как-то основа Dp
затем укажите тип (в этом случае void
). Затем мы используем этот тип как тип результата нашей функции. Следовательно, для первого вызова результирующая реализация выглядит следующим образом после вычета:
void call_fn(Shape const& obj, void (*pfn)(const Shape&))
это было то, что мы хотели. Аналогичное создание в результате второго вызова:
void call_fn(Circle const& obj, void (*pfn)(const Circle&))
Третий экземпляр создаст это:
void call_fn(Circle const& obj, void (*pfn)(const Shape&))
так как Dp
а также Bp
разные, но Dp
является производной.
Дело об отказе
Чтобы увидеть этот сбой (как мы этого хотим), измените код с не связанными типами. Просто удалить Shape
из списка наследования базового класса Circle
:
#include <iostream>
#include <type_traits>
struct Shape {};
struct Circle {};
template<class Bp, class Dp>
std::enable_if_t<std::is_base_of<Bp,Dp>::value,void>
call_fn(Dp const& obj, void (*pfn)(const Bp&))
{
pfn(obj);
}
void shape_func(const Shape& s) { std::cout << "shape_func called!\n"; }
void circle_func(const Circle& s) { std::cout << "circle_func called!\n"; }
int main()
{
Shape shape;
Circle circle;
call_fn(shape, shape_func); // still ok.
call_fn(circle, circle_func); // still ok.
call_fn(circle, shape_func); // not OK. no overload available,
// since a Circle is not a Shape.
}
Результатом будет несоответствующий вызов функции для третьего вызова.
Других решений пока нет …