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

时间:2019-04-24 09:20:32

标签: c++ lambda

考虑以下代码:

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

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

2 个答案:

答案 0 :(得分:21)

  

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

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

一种替代方法是调度在给定函数类型的operator()限定实例上调用const的能力。遵循这些原则(请注意,这需要同时修复const和非{const operator()的函数对象,这会产生歧义):

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's complaint

答案 1 :(得分:6)

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

设想是否要通过指针在班级中保存某些东西。然后,您可以从类的const成员函数中对pointee调用变异操作,因为它不会(在浅表中)影响类所持有的指针。这与您观察到的情况类似。

关于您的第一个问题,“总是”一词太强了。这取决于您的目标。

  1. 如果要轻松支持自变异函子,则应捕获可变的lambda。但是请注意,这可能会影响您现在可能调用的 库函数。

  2. 如果您希望支持非变异操作,则应使用非变异lambda。我之所以说“偏爱”,是因为正如我们观察到的那样,可以通过额外的间接级别来“欺骗”类型系统。因此,您偏爱的方法只会变得更易于使用,并非不可能。这是明智的建议,使正确使用API​​变得容易,而错误使用起来也会更加困难。