考虑以下代码:
int x = 3;
auto f1 = [x]() mutable
{
return x++;
};
auto f2 = [f1]()
{
return f1();
};
这将不会编译,因为f1()
不是const,并且f2
没有声明为可变的。这是否意味着如果我有一个接受任意函数参数并在lambda中捕获它的库函数,我总是需要使该lambda可变,因为我不知道哪些用户可以传入?值得注意的是,将f1
包装在std::function
中似乎可以解决此问题(如何解决)。
答案 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调用变异操作,因为它不会(在浅表中)影响类所持有的指针。这与您观察到的情况类似。
关于您的第一个问题,“总是”一词太强了。这取决于您的目标。
如果要轻松支持自变异函子,则应捕获可变的lambda。但是请注意,这可能会影响您现在可能调用的 库函数。
如果您希望支持非变异操作,则应使用非变异lambda。我之所以说“偏爱”,是因为正如我们观察到的那样,可以通过额外的间接级别来“欺骗”类型系统。因此,您偏爱的方法只会变得更易于使用,并非不可能。这是明智的建议,使正确使用API变得容易,而错误使用起来也会更加困难。