Capturing a lambda in another lambda can violate const qualifiers

riv*_*riv 26 c++ lambda

Consider the following code:

int x = 3;
auto f1 = [x]() mutable
{
    return x++;
};
auto f2 = [f1]()
{
    return f1();
};
Run Code Online (Sandbox Code Playgroud)

This will not compile, because f1() is not const, and f2 is not declared as mutable. Does this mean that if I have a library function that accepts an arbitrary function argument and captures it in a lambda, I always need to make that lambda mutable, because I don't know what users can pass in? Notably, wrapping f1 in std::function seems to resolve this problem (how?).

lub*_*bgr 21

Does this mean that if I have a library function that accepts an arbitrary function argument and captures it in a lambda, I always need to make that lambda mutable, because I don't know what users can pass in?

That's a design decision for your library API. You can require client code to pass function objects with a const-qualified operator() (which is the case for non-mutable lambda expressions). If something different is passed, a compiler error is triggered. But if the context might require a function object argument that modifies its state, then yes, you have to make the internal lambda mutable.

另一种选择是分派调用给定函数类型operator()const-qualified实例的能力。遵循这些原则(请注意,这需要同时修复带有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(); }();
}
Run Code Online (Sandbox Code Playgroud)

值得注意的是,将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
Run Code Online (Sandbox Code Playgroud)

这是一个已知问题,值得检查蒂特斯·温特(Titus Winter)对此的抱怨


Sto*_*ica 6

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

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

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

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

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