为什么不能初始捕获可变lambda有可变数据成员?

时间:2014-02-22 22:28:45

标签: c++ lambda language-lawyer mutable c++14

这个问题与this previous one有关,其中注意到init-capture mutable lambdas与Boost的范围和迭代器transform不兼容,对于一些相当模糊和深度嵌套的typedef通过攻击Boost.Range源可能或可能不容易解决的失败。

接受的答案建议将lambda存储在std::function对象中。为了避免潜在的virtual函数调用开销,我写了两个函数对象,可以作为潜在的解决方法。它们在下面的代码中称为MutableLambda1MutableLambda2

#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不会使用lamMutableLambda1编译行,并为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表达式的手写替代品。

问题

  • 为什么init-capture mutable lambdas的实现方式(即非const函数调用运算符)?
  • 为什么mutable数据成员看似等效的替代mutable函数调用运算符被禁止?
  • 奖励)为什么Boost范围和迭代器const依赖于函数对象transformoperator()的事实?

2 个答案:

答案 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对象   UnaryFunctioni是Iterator类型的对象,其类型为   f(*i)必须为result_of<const UnaryFunction(iterator_traits<Iterator>::reference)>::type

因此,有状态的非纯函数对象不能使用lambdas编写,而是必须使用const函数调用operator()mutable数据成员来表示状态。这也在this related Q&A中进行了评论。

注意:此版本有open bug report