考虑两种类型,其中一种从另一种继承:
#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#术语来说,T
的类型参数Action<T>
是contravariant。
在C ++中有可能吗?
也就是说,在此示例中,必须如何实现功能模板call_twice
才能接受所有三个调用?
答案 0 :(得分:1)
一种执行此操作的方法是通过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.
}
结果将是第三次调用不匹配的函数。