3

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变量。但是假设我想创建一个vector这样的对象。vector持有什么类型的?通常的答案是使用std::function,但这在这种情况下不起作用,因为 AFAIK 没有 polymorphic 之类的东西std::function,即,这在 C++1y 中是不合法的:

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

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

两个问题:

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

3 回答 3

3

不,也许吧。

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

因此,如果您的目标只是拥有一个作为 覆盖集的对象,是的f,您可以这样做。请参阅“手动”签名重载解决方案——在混乱之上添加一些类型擦除,鲍勃是你的叔叔。

一般情况下,您有一些auto带有任意代码的lambda,不。

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

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

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

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

现在,并非一切都是绝望的。

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

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

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

把它拉下来是可能的,但我以前没有写过。对此的替代方案都容易得多。

我不考虑使用类型擦除的情况来解决这个问题,因为它会阻止某些类型的优化。但从理论上讲,这意味着您可以使用几乎任意类型。

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

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

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

于 2013-10-30T01:18:10.993 回答
1

What I want to store are objects of different types, but each can be called with a potentially unlimited set of argument types.

Translation unit 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 );
}

Translation unit 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 {});
}

When and where is lives_in_b::operator()<lives_in_a> instantiated, so that it may be called?

When v.front() is called with argument lives_in_a {}? In that case there is no definition of lives_in_b in sight, so little to even instantiate.

When v.push_back(lives_in_b {}) is called? In that case there's no definition of lives_in_a in sight, so there's isn't much the would-be instantiation could do with.

This is a demonstration that the particular combination of the compilation model of C++ and the way template instantiation work, doesn't allow for that particular wish. It has less to do with static typing.

于 2013-11-02T03:41:40.830 回答
1

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

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

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

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

于 2013-10-29T23:45:21.927 回答