如何存储多态闭包

How store polymorphic closures?

本文关键字:多态 闭包 存储 何存储      更新时间:2023-10-16

>C++1y 提供多态 lambda(即使用 auto 作为参数类型的一部分):

int         f(int);
double      f(double);
std::string f(const std::string&);
auto funcObj = [](const auto& param){ return f(param); }

存储由 lambda 生成的闭包很容易,如图所示:只需使用 auto 变量。但假设我想创建此类对象的vectorvector是什么类型?通常的答案是使用 std::function ,但这在这种情况下不起作用,因为 AFAIK 没有多态std::function这样的东西,即这在 C++1y 中是不合法的:

std::vector<std::function<auto(const auto&)>> vecOfPolymorphicClosures;

如果这是合法的,那么你可以做一些事情,比如创建一个回调容器,每个回调都可以用任何一组参数调用,每个参数都可以返回一个依赖于所传递参数类型的类型。任何给定回调的结果都可以存储在auto变量中,至少在理论上是这样。

两个问题:

  • 有没有办法在 C++1y 中声明一个变量或容器,该变量或容器可以容纳不同类型的多态 lambda(除了像 boost::any 这样的东西)?
  • 希望这样的事情是可能的,还是这种事情与静态类型不兼容?

No. 好吧,也许吧。

对于您的特定情况,您的 lambda 只是单个函数的覆盖集,f实例化时已知。 可以使用类型擦除创建和传递覆盖集对象,而不会有太大问题。 只需手动枚举替代并将其提供给覆盖集。

因此,如果您的目标是拥有一个对象,即f的覆盖集,是的,您可以这样做。 请参阅"手动"签名重载分辨率 - 在混乱之上添加某种类型擦除,Bob 就是您的叔叔。

一般情况下,您有一些包含任意代码的 lambda auto,没有。

设想此问题的方法是想象一个用你的lambda编译的DLL或共享库,第二个DLL或共享库保存类似function的对象,以及其他一些DLL或共享库想要调用它。

调用 function 时发生的行为取决于 lambda 的定义以及要调用它的任意程度的类型。

为了使其正常工作,几乎完整的运行时编译模型必须在创建 lambda 的 DLL 和调用它的类型的 DLL 中都可用,并且该运行时编译模型必须兼容。

这既不是C++标准所要求的,而且如果这样做会使事情变得更加复杂,并且会消除优化机会。

现在,并非一切都是没有希望的。

如果有一些要支持的固定类型列表,则可以编写多态function签名。 这基本上是上面"覆盖集"解决方案的特例,甚至可以使用它编写。

另一方面,如果您愿意将参数的属性键入 erase 到您的 lambda,并键入 erase,并返回一些统一的类型(无论是 boost::any 还是 boost::variant 或其他什么),您可以做一些事情。 您编写了一个类型擦除对象类型,并公开了它。 然后你有一个std::function< boost::any(type_erasure_object) >,转换发生在调用之外,在调用中你处理所述类型的擦除对象。

使用类型擦除对象选取重载很棘手,因为C++编译器在生成要考虑的重载列表方面没有多大帮助。 如果您手动收集该列表,您甚至可以键入 erase 您将选择的重载。

实现它是可能的,但我以前没有写过。 其他方法都容易得多。

我不认为类型擦除大小写可以解决这个问题,因为它阻止了某些类型的优化。 但从理论上讲,这意味着您可以使用几乎任意的类型。

类型纠删对象必须向最终用户公开,并且它必须擦除您塞入该std::vector的每个 lambda 需要知道的每条类型信息。 因此,在某些情况下,这会显着限制您存储在std::vector中的 lambda。

有关如何键入擦除几乎任意对象的示例,请查看增强类型擦除。

最后,你所要求的很少是问题的实际要求。 你最好描述一下你实际的实际问题,几乎可以肯定它的解决方案不像上面那么深奥。

我想存储的是不同类型的对象,但每个对象都可以使用一组可能无限的参数类型来调用。

翻译单元 A:

// a.cpp
#include <cassert>
std::vector<magical_type> v;
struct lives_in_a { int i; };
// defined in TU B:
void prepare();
int main()
{
    prepare();
    assert( v.front()(lives_in_a { 42 }) == 42 );
}

翻译单元 B:

// b.cpp
struct lives_in_b {
    template<typename Anything>
    int operator()(Anything const& a) const
    { return a.i; }
};
void prepare()
{
    // ignore global initialization order fiasco for the sake
    // of the argument
    extern std::vector<magical_type> v;
    v.push_back(lives_in_b {});
}

何时何地lives_in_b::operator()<lives_in_a>实例化,以便可以调用它?

当用参数lives_in_a {}调用v.front()时?在这种情况下,看不到lives_in_b的定义,甚至很少实例化。

什么时候叫v.push_back(lives_in_b {})?在这种情况下,看不到lives_in_a的定义,因此可能的实例化无能为力。

这表明C++的编译模型和模板实例化工作方式的特定组合不允许该特定愿望。它与静态类型关系不大。

所谓的泛型 lambda 的类型是具有成员模板operator()的类类型。当需要转换时,必须知道实际类型。对于非捕获通用 lambda,当前标准草案甚至包含以下示例:

auto glambda = [](auto a) { return a; };
int (*fp)(int) = glambda;

这与从普通函数模板形成函数指针没有什么不同。

对于一般的通用 lambda,我认为期望可调用对象的转换将触发正确的模板专用化,因此std::function<int(int)> f(glambda);应该可以正常工作。