在另一个 lambda 中捕获一个 lambda 可能会违反 const 限定符

Capturing a lambda in another lambda can violate const qualifiers

本文关键字:lambda const 另一个 一个      更新时间:2023-10-16

请考虑以下代码:

int x = 3;
auto f1 = [x]() mutable
{
return x++;
};
auto f2 = [f1]()
{
return f1();
};

这不会编译,因为f1()不是常量,并且f2不会声明为可变的。这是否意味着如果我有一个接受任意函数参数并将其捕获在 lambda 中的库函数,我总是需要使该 lambda 可变,因为我不知道用户可以传入什么?值得注意的是,将f1包装在std::function中似乎可以解决此问题(如何?

这是否意味着如果我有一个接受任意函数参数并将其捕获在 lambda 中的库函数,我总是需要使该 lambda 可变,因为我不知道用户可以传入什么?

这是库 API 的设计决策。您可以要求客户端代码使用限定constoperator()传递函数对象(非mutablelambda 表达式就是这种情况)。如果传递了不同的内容,则会触发编译器错误。但是,如果上下文可能需要修改其状态的函数对象参数,那么是的,您必须使内部 lambdamutable

另一种方法是调度在给定函数类型的const限定实例上调用operator()的能力。类似这些内容(请注意,这需要修复同时具有const和非constoperator()的函数对象,这会导致歧义):

template <class Fct>
auto wrap(Fct&& f) -> decltype(f(), void())
{
[fct = std::forward<Fct>(f)]() mutable { fct(); }();
}
template <class Fct>
auto wrap(Fct&& f) -> decltype(std::declval<const Fct&>()(), void())
{
[fct = std::forward<Fct>(f)]() { fct(); }();
}

值得注意的是,将 f1 包装在 std::function 中似乎可以解决这个问题(如何?

这是std::function中的一个错误,因为它的类型擦除和复制语义。它允许调用非const限定operator(),可以使用以下代码片段进行验证:

const std::function<void()> f = [i = 0]() mutable { ++i; };
f(); // Shouldn't be possible, but unfortunately, it is

这是一个已知问题,值得查看Titus Winter对此的投诉。

我将首先解决您的第二个问题。std::function类型擦除,并保存初始化它的函子的副本。这意味着std::function::operator()和实际函子的operator()之间有一层间接性。

如果你愿意的话,想象一下,在你的课堂上通过指针持有一些东西。然后,您可以从类的 const 成员函数调用对 pointee 的变异操作,因为它不会影响(在浅视图中)类持有的指针。这与你观察到的情况类似。

至于你的第一个问题..."总是"这个词太强烈了。这取决于你的目标。

  1. 如果你想轻松支持自变异函子,那么你应该捕获一个可变的 lambda。但请注意,它可能会影响现在可以调用的库函数。

  2. 如果您希望支持非可变操作,那么使用不可变的 lambda。我之所以说"青睐",是因为正如我们所观察到的,类型系统可以通过额外的间接性来"愚弄"。因此,您喜欢的方法只会更容易使用,而不是不可能。正如圣人的建议所说,正确使用您的 API 变得容易,不正确更难。