Reputation: 70546
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
?Upvotes: 3
Views: 493
Reputation: 275750
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).
Upvotes: 2
Reputation: 70546
As pointed out in the comments, a mutable lambda requires a non-const function call operator in order to let const references to function objects represent pure functions.
It turns out that the culprit for my application is Boost.Iterator underyling the Boost.Range implementation of boost::adaptors::transformed
. After some digging in the Boost.Iterator documentation's requirements for transform_iterator
, it turns out that (bold emphasis mine)
The type
UnaryFunction
must be Assignable, Copy Constructible, and the expressionf(*i)
must be valid wheref
is a const object of typeUnaryFunction
,i
is an object of type Iterator, and where the type off(*i)
must beresult_of<const UnaryFunction(iterator_traits<Iterator>::reference)>::type
.
Stateful non-pure function objects can therefore not be written using lambdas but instead have to written using a const
function call operator()
and with mutable
data members representing the state. This was also remarked in this related Q&A.
Note: there is an open bug report for this.
Upvotes: 1