This question is related to this previous one where it was noticed that init-capturing mutable lambdas are incompatible with Boost's range and iterator transform for some rather obscure and deeply nested typedef failures that may or may not be easy to resolve through hacking the Boost.Range sources.
The accepted answer suggested storing the lambda in a std::function object. To avoid potential virtual function call overhead, I wrote two function objects that could serve as potential work-arounds. They are called MutableLambda1 and MutableLambda2 in the code below
#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 that won't compile the lines with lam and MutableLambda1, and correctly prints 1, 4, 9, 16 for the line with MutableLambda2.
However, the draft Standard mentions
5.1.2 Lambda expressions [expr.prim.lambda]
5 [...] This function call operator or operator template is declared const (9.3.1) if and only if the lambda-expression’s parameter-declaration-clause is not followed by
mutable. [...]11 For every init-capture a non-static data member named by the identifier of the init-capture is declared in the closure type. This member is not a bit-field and not
mutable. [...]
This means that MutableLambda2 is not a conforming handwritten replacement for an init-capturing mutable lambda expression.
mutable lambdas the way it is (i.e. non-const function call operator)?mutable data members with a const function call operator forbidden? transform rely on the fact that a function objects operator() is const?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)};
}
the above should let you take a lambda, wrap it in force_const_call( ... ), and call your boost algorithm, without a custom mutable callable object (or more precisely, the above turns lambdas into custom mutable callables).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With