这个问题与先前的问题有关,其中注意到init-capturing
接受的答案建议将lambda存储在一个
mutable
lambda与Boost的range和iterator transform
不兼容,因为一些相当模糊和深度嵌套的typedef
失败,这些可能很难通过黑客攻击 Boost.Range 源代码来解决。接受的答案建议将lambda存储在一个
std::function
对象中。为了避免潜在的虚函数调用开销,我编写了两个函数对象,它们可以作为潜在的解决方法。它们在下面的代码中称为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! */
}
实时示例无法编译带有lam
和MutableLambda1
的行,并且对于带有MutableLambda2
的行,正确地打印出1, 4, 9, 16
。
然而,标准草案提到了
5.1.2 Lambda表达式[expr.prim.lambda]
5[...]如果lambda表达式的参数声明子句后面没有跟随
mutable
,则此函数调用运算符或运算符模板被声明为const(9.3.1)。[...]11对于每个init-capture,在闭包类型中声明一个由init-capture的标识符命名的非静态数据成员。该成员不是位域,也不是
mutable
。[...]
这意味着MutableLambda2
不是符合规范的手写替代init-capturing mutable
lambda表达式。
问题
- 为什么init-capturing
mutable
lambdas的实现方式是这样的(即非const函数调用运算符)? - 为什么看似等效的
mutable
数据成员与const
函数调用运算符的替代被禁止? - (额外问题)为什么Boost范围和迭代器
transform
依赖于函数对象的operator()
是const
?
MutableLambda2
确实有效,但其实现与标准规定的 lambda 不同。我想知道这是为什么,以及我的解决方法是否有任何隐藏的缺陷。 - TemplateRexMutableLambda2
不符合标准。从哪个方面来说?作为lambda表达式的实现?-- 非const函数调用运算符的可变lambda需要在非const对象上进行调用;另一方面,带有const函数调用运算符和mutable
数据成员的lambda则不需要。这是你要找的区别吗? - dypMutableLambda2
而不是现在所需的MutableLambda1
,那么它会如何破坏用户代码?正如你所指出的,你可以在 const 对象上调用前者,但为什么这样做会有问题呢?(因为它只修改内部状态,而不是传递给operator()
的参数)。 - TemplateRexa
和b
一致。当函数调用运算符是const时,您可能合理地假设使用函数a
和使用函数b
是可互换的。 - dypMutableLambda2
作为我的问题的一个良好注释的解决方案,直到 Boost 跟上 init-capturing lambdas 的步伐。 - TemplateRex