这个问题与this previous one有关,其中注意到init-capture mutable
lambdas与Boost的范围和迭代器transform
不兼容,对于一些相当模糊和深度嵌套的typedef
通过攻击Boost.Range源可能或可能不容易解决的失败。
接受的答案建议将lambda存储在std::function
对象中。为了避免潜在的virtual
函数调用开销,我写了两个函数对象,可以作为潜在的解决方法。它们在下面的代码中称为MutableLambda1
和MutableLambda2
#include <iostream>
#include <iterator>
#include <vector>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
// this version is conforming to the Standard
// but is not compatible with boost::transformed
struct MutableLambda1
{
int delta;
template<class T> auto operator()(T elem) { return elem * delta++; }
};
// Instead, this version works with boost::transformed
// but is not conforming to the Standard
struct MutableLambda2
{
mutable int delta;
template<class T> auto operator()(T elem) const { return elem * delta++; }
};
// simple example of an algorithm that takes a range and laziy transformes that
// using a function object that stores and modifies internal state
template<class R, class F>
auto scale(R r, F f)
{
return r | boost::adaptors::transformed(f);
}
int main()
{
// real capturing mutable lambda, will not work with boost::transformed
auto lam = [delta = 1](auto elem) mutable { return elem * delta++; };
auto rng = std::vector<int>{ 1, 2, 3, 4 };
//boost::copy(scale(rng, lam), std::ostream_iterator<int>(std::cout, ",")); /* ERROR */
//boost::copy(scale(rng, MutableLambda1{1}), std::ostream_iterator<int>(std::cout, ",")); /* ERROR */
boost::copy(scale(rng, MutableLambda2{1}), std::ostream_iterator<int>(std::cout, ",")); /* OK! */
}
Live Example不会使用lam
和MutableLambda1
编译行,并为1, 4, 9, 16
的行正确打印MutableLambda2
。
然而,draft Standard提及
5.1.2 Lambda表达式[expr.prim.lambda]
5 [...]此函数调用运算符或运算符模板声明为const (9.3.1)当且仅当lambda表达式为 {-1}}后面没有参数声明子句。 [...]
11对于每个init-capture,由一个名为的静态数据成员 init-capture的标识符在闭包类型中声明。这个 成员不是一个现场而不是
mutable
。 [...]
这意味着mutable
不是一个符合init的捕获MutableLambda2
lambda表达式的手写替代品。
mutable
lambdas的实现方式(即非const函数调用运算符)?mutable
数据成员看似等效的替代mutable
函数调用运算符被禁止? const
依赖于函数对象transform
是operator()
的事实?答案 0 :(得分:2)
template<class L>
struct force_const_call_t {
mutable L f;
template<class...Args>
auto operator()(Args&&...args) const
{ return f(std::forward<Args>(args)...); }
};
template<class L>
force_const_call_t<L> force_const_call(L&&f){
return {std::forward<L>(f)};
}
上面的内容应该让你拿一个lambda,将它包装在force_const_call(
... )
中,并调用你的boost
算法,而不需要自定义mutable
可调用对象(或者更准确地说,上面将lambda变成了自定义mutable
callables。)
答案 1 :(得分:1)
正如注释中所指出的,一个可变的lambda需要一个非const函数调用操作符,以便让对函数对象的const引用代表纯函数。
事实证明,我的应用程序的罪魁祸首是Boost.Iterator,它是boost::adaptors::transformed
的Boost.Range实现的基础。在Boost.Iterator文档的requirements for transform_iterator
中进行了一些挖掘后,结果发现(大胆强调我的)
类型
UnaryFunction
必须是可分配的,可复制的构造,以及 表达式f(*i)
必须有效,其中f
是类型的const对象UnaryFunction
,i
是Iterator类型的对象,其类型为f(*i)
必须为result_of<const UnaryFunction(iterator_traits<Iterator>::reference)>::type
。
因此,有状态的非纯函数对象不能使用lambdas编写,而是必须使用const
函数调用operator()
和mutable
数据成员来表示状态。这也在this related Q&A中进行了评论。
注意:此版本有open bug report。