模板回调的包装器?

Wrapper for template callback?

本文关键字:包装 回调      更新时间:2023-10-16

>立即回调

请考虑以下示例:

template <typename lambda> void call_me_back(const lambda & callback)
{
// Very complicated calculation to find the meaning of everything
callback(42);
}
int main()
{
call_me_back([](const int & value)
{
std :: cout << value << std :: endl;
});
}

在这里,我为call_me_back提供了一个接受int的lambda。 经过长时间的计算后,call_me_back调用callbackcallback将其打印出来。像蛋糕一样简单。

现在,假设根据执行,call_me_back需要使用int或其他类型调用callback。我们可以将前面的示例编辑为

template <typename lambda> void call_me_back(const lambda & callback)
{
// Very complicated calculation to find the meaning of everything
if(rand() % 2)
callback(42);
else
callback("Kill all the humans.");
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
}

目前为止,一切都好。现在callback可以根据其类型执行各种技巧并处理value

延迟回调

现在,回到第一个示例。说call_me_back不准备立即用int打电话给callback.但是,它可以做的是callback存储在某个地方,然后稍后调用它。

例如:

std :: function <void(const int &)> the_callback;
template <typename lambda> void call_me_back(const lambda & callback)
{
the_callback = callback;
}
void ready_to_answer()
{
the_callback(42);
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
}

现在,call_me_back不是立即调用callback,而是将callback存储在std :: function <void(const int &)>对象中(我知道,它在全局范围内,请耐心等待(。然后可能会发生许多事情,在某些时候有人可以调用ready_to_answer,这会检索以前存储的回调并调用它。例如,ready_to_answer可以由另一个线程调用,但是有很多原因需要这样的范式,其中回调可以存储并在以后调用。

问题所在

如果我想实现第二个示例,但有延迟回调怎么办?我似乎无法解决这个问题。

我可以想象std :: function是用接受某种特定类型的虚拟呼叫运营商实现的。然后,std :: function包装指向模板包装器类的指针/引用,该类存储实际的 lambda,并通过将其参数转发到它存储的 lambda 来实现调用运算符。很好,很容易。但是我不能有模板虚拟方法!

我尝试过提出各种解决方案,但我找不到任何可以合理工作的东西。这真的不可能做到吗?难道不可能将一些外部提供的 lambda 接受const auto &参数存储在某处,然后稍后调用它吗?

你是对的,无限的类型集是不可能的,但如果你事先知道所有类型,你可以做到:

std :: function <void(const std :: variant<int, std :: string> &)> the_callback;
template <typename lambda> void call_me_back(const lambda & callback)
{
the_callback = [callback](const auto & arg)
{
std :: visit(callback, arg);
};
}
template <typename T> void ready_to_answer(const T & x)
{
the_callback(x);
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
if (std :: rand() % 2)
{
ready_to_answer(42);
}
else
{
ready_to_answer("Hi!");
}
}

在第二个示例中,您不传递函数,而是使用两个方法operator()(int)operator(char const (&)[..])传递对匿名 lambda 对象的引用。因此,要存储这种回调,您需要复制 lambda 对象或存储特定的回调方法,即使用多个具有相应签名的::std::function。实际上,为这两种情况显式传递两个回调会更清晰。

std::function< void (int) > delalyed_int_cb;
std::function< void (const char *) > delalyed_str_cb;
template< typename callable_taking_int, typename callable_taking_string > void
call_me_back(callable_taking_int && int_cb, callable_taking_string && str_cb)
{
delalyed_int_cb = std::forward< callable_taking_int >(int_cb);
delalyed_str_cb = std::forward< callable_taking_str >(str_cb);
...
}
void
ready_to_answer()
{
if(rand() % 2)
{
delalyed_int_cb(42);
}
else
{
delalyed_str_cb("Kill all the humans.");
}
}

您可以实现一个为您存储 lambda 的类模板。

#include<iostream>
template<typename L>
struct Telephone
{
L l;
Telephone(L l) : l{std::move(l)} {}
template<typename... Args>
decltype(auto) ready_to_answer(Args&&... args)
{
return l(std::forward<Args>(args)...);   
}
};
template<typename L>
auto call_me_back(L&& l)
{
return Telephone<std::decay_t<L>>(std::forward<L>(l));
}
int main()
{
auto telephone = call_me_back([](auto x){ std::cout << x << std::endl; });
telephone.ready_to_answer(42);
telephone.ready_to_answer("Kill all the humans.");
}

这样做的缺点是Telephone模板化在 lambda 上,并且对于每个 lambda 都是不同的类型。

如果您事先知道函数签名的外观,则可以Telephone从公共基类继承并具有虚拟的非模板方法。

Telephone进行一些自动构建和管理,您基本上实现了另一种std::function但具有自定义签名

struct Phone
{
// all Telephone<L> inherits from Phone and have these methods
virtual void ready_to_answer(int) = 0;
virtual void ready_to_answer(const char*) = 0;
};
struct Spouse
{
std::unique_ptr<Phone> phone;
template<typename L>
Spouse(L&& l) : phone{ new Telephone<std::decay_t<L>>{std::forward<L>(l)} } {}
void ready_to_answer(int i) { phone->ready_to_answer(i); }
void ready_to_answer(const char* str) { phone->ready_to_answer(str); }
};

编辑:我继续实施了一个std::function的增强版本,它接受任意数量的签名,所以你可以写

function<void (int), void (std::string)> callback =
[](auto x){std::cout << x << std::endl;};
callback(42);
callback("Kill all humans!");

可以说实现不是微不足道的,这里要解释的太多了。