【问题标题】:If lambdas don't have a specified type, how does std::function accept a lambda?如果 lambda 没有指定类型,std::function 如何接受 lambda?
【发布时间】:2020-06-11 19:37:46
【问题描述】:

谁能解释 lambda 函数在 std::function 中是如何表示的?编译器和用作容器的 std::function 是否存在隐式转换?

我最近询问了一个略有不同的question,它被标记为与this question 重复。答案是未定义和未指定 lambda 函数的类型。我发现一些代码似乎为 lambda 函数提供了一个容器,如下所示。我还包括了一个 Stroustrup 引用,这似乎与 lambda 函数没有定义类型相矛盾,但它说它是一个函数闭包类型。这只会进一步混淆问题。

更新: 关于实现函数的部分答案是here

感谢您的指导。

#include <iostream>
#include <vector>
#include <functional>
using namespace std;

static vector<function<void(int)>> cbl;
static vector<function<int(int)>> cblr;

class GT {
public:
    int operator()(int x) {return x;}
};

void f()
{
    auto op = [](int x) {cout << x << endl;}; 
    cbl.push_back(op);

    for (auto& p : cbl)
        p(1);

    auto op2 = [](int x) {return x;};
    cblr.push_back(op2);
    cblr.push_back(GT());
    for (auto& p : cblr)
        cout << p(99) << endl;
}

int main(int argc, char *argv[])
{
    f();
    return 0;
}

编译和结果:

g++ -pedantic -Wall test130.cc && ./a.out
1
99
99

Stroustrup 在 C++ 第 4 版第 297 页中谈到了这一点:

为了允许 lambda 表达式的优化版本,a 未定义 lambda 表达式。但是,它被定义为 §11.4.1 中呈现的样式的函数对象的类型。这 类型,称为闭包类型,是 lambda 独有的,所以没有两个 lambda 具有相同的类型。如果两个 lambda 具有相同的类型,则 模板实例化机制可能已经被混淆了。一种 lambda 是具有构造函数和 const 成员的本地类类型 函数运算符()()。

【问题讨论】:

  • 相关,可能是骗人的:stackoverflow.com/questions/18453145/…
  • 编译器知道 lambda 的类型。
  • “这似乎与 lambda 函数没有类型相矛盾” - 你仍然没有放弃这个误解。 Lambda 的一个类型。每个对象都有一个类型。
  • 跟随 StoryTeller 的评论,一个 lambda 有一个 unspecified 类类型。这并不意味着它没有类型,只是我们不知道它的名称。我们知道它是一个类,所以它是一个仿函数
  • 正确。就像5int 类型一样,lambda 表达式也有自己的类型。但与5 不同的是,C++ 标准并没有告诉您应该调用什么类型的 lambda 或编译器应该实现它的确切方式。那是未指定的。

标签: c++ c++11


【解决方案1】:

类型在那里。只是你事先不知道它是什么。 Lambda 有类型——只是标准没有说明该类型是什么;它只给出类型必须履行的合同。由编译器实现者决定该类型到底是什么。他们不必告诉你。知道也没有用。

因此,您可以像处理任何“通用”类型的存储一样处理它。即:提供适当对齐的存储,然后使用放置new 复制构造或移动构造该存储中的对象。没有一个特定于std::function。如果你的工作是编写一个可以存储任意类型的类,你就会这样做。这也是std::function 必须要做的事情。

std::function 实现者通常采用小对象优化。该类在其体内留下了一些未使用的空间。如果要存储的对象具有适合空房间的对齐方式,并且如果它适合那个未使用的房间,那么存储将来自std::function 对象本身。否则,它必须为它动态分配内存。这意味着例如捕获内在向量类型(AVX、Neon 等)——如果可能的话——通常会使 lambda 不适用于 std::function 内的小对象优化存储。

我没有声明是否允许或是否允许捕获内在向量类型,富有成效,明智,甚至可能。它有时是一个角落案例和微妙平台错误的雷区。我不建议任何人在不完全了解正在发生的事情以及在需要时审核生成的装配的能力的情况下去做(暗示:在压力下,通常在演示日凌晨 4 点,西装即将醒来很快就起来了——他们已经很恼火了,他们不得不这么早就打断他们的高尔夫比赛,只是为了看主持人出汗)。

【讨论】:

  • 如何在void* 上调用operator()?将对象包装在具有virtual operator()? 的内部多态类型中真心问。我一直想知道std::function 是如何处理类型擦除的。
  • 它通过不做来处理它。我的意思是:说真的。没有来自void* 的电话。 lambda 的类型可用。您可以生成实际执行调用所需的任何帮助代码:将reinterpret_castvoid* 转换为指向lambda 的类型的代码,然后取消引用该指针,并调用由此获得的引用。该代码可以是无状态函数。所以只需保存一个指向它的指针。简单。类型永远不会丢失。有代码知道类型并对类型化对象进行调用。就像new 一样,存储可以无类型:不能擦除!
【解决方案2】:

“没有类型”和“类型未指定”之间存在区别。未指定意味着它存在,但不知道它是什么。也就是说,它没有可以键入的类型名称,但它确实有一个类型。

op 在您的示例中是具有类型的变量。您不知道哪种有效的字母组合可以识别该类型的名称(实际上,no 有效的字母组合可以识别该类型的名称)。但是可以通过decltype(op)计算类型。

【讨论】:

  • FWIW,标准并没有说未指定类型必须没有名称。而且,实际上,在实践中,它们确实倾向于获取名称(可以通过错误消息观察到,自然地以漂亮的下划线开头以将它们保留在它们的保留中——当然,如果你猜对了,我从未测试过这些是否可以在代码中使用) .但确实不能保证他们有名字。
  • @AsteroidsWithWings: "如果你猜对了,我从未测试过它们是否可以在代码中使用" 你不能使用它们。这就是双下划线规则的全部意义:它们是为实现而保留的,您的程序对此类标识符的任何使用都表示 UB。
  • 抱歉,更清楚地说,我的意思不是保留名称意义上的“合法”,而是“不破坏构建”。也就是说,这些名称是否以可以在代码中拼写的方式公开,或者它们出现在错误消息中只是一种异常表现(甚至是故意的例外)?如果是后者,那么人们仍然可以争辩说这些类型确实没有任何有意义的名称,尽管错误消息似乎证明并非如此。
【解决方案3】:

一个 lambda 有一个类型。你可以编写一个函数模板,它接受一个 lambda 并调用它:

template<typename T>
class F{
    T t;
public:
    F(const T &lambda):t(lambda){}
    void call() {t();}
};

这是std::function的一个非常基本的方法

“编译器和用作容器的 std::function 是否存在隐式转换?” 不,您不需要转换它们。

【讨论】:

  • 一一回答。我尝试按如下方式使用它const auto op3 = []() {cout &lt;&lt; "A" &lt;&lt; endl;}; F f{op3};。我得到missing template arguments before ‘f’ F f{op3};。那么也许类型名不是自动推导出来的?
  • @notaorb 它对我有用wandbox.org/permlink/GVJpVdXo8qTblcNE
  • 谢谢。 g++ 需要 -std=c++17
【解决方案4】:

您不断声称 lambdas“没有类型”,但这不是“未指定”的意思,也不是任何人在最后三个问题上所说的意思。

Lambda 有一个类型,只是你不知道类型的名称。

标准没有指定任何 lambda 类型的名称。如果这样的类型甚至有名称,编译器的内部工作原理就知道它,typeid().name() 可能会显示它,并且错误消息通常会显示它。

但是您不知道也不可能提前知道任何给定 lambda 的类型名称。这是设计使然。所以你不能在你的源代码中写出那个类型。没关系;为什么要?

模板和auto 不需要名称,因为模板就是这样工作的。 std::function 的内部工作不需要知道任何 lambda 类型的名称;他们只需要知道表达式thing(args) 是否有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-02
    • 1970-01-01
    • 2014-08-25
    • 1970-01-01
    相关资源
    最近更新 更多