gpt4 book ai didi

c++ - 类似于 C# 中的模板化回调参数的逆变

转载 作者:塔克拉玛干 更新时间:2023-11-03 07:05:03 32 4
gpt4 key购买 nike

设置

考虑两种类型,其中一种继承自另一种:

#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"; }

函数模板

现在我想要一个函数模板,我可以将其传递给它:

  1. 这些类型之一的对象(或其他类似类型)
  2. 指向与传递的对象兼容的那些函数之一的指针(或其他类似函数)

以下是我声明此函数模板的尝试:

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# 术语来说,类型参数 TAction<T>contravariant .

问题

这在 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&))

因为DpBp是不同的,但是Dp是一个导数。


失败案例

要看到此失败(正如我们希望的那样),请使用不相关的类型修改代码。只需从 Circle 的基类继承列表中删除 Shape:

#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.
}

第三次调用的结果将是不匹配的函数调用。

关于c++ - 类似于 C# 中的模板化回调参数的逆变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51993554/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com