Does this mean that if I have a library function that accepts an arbitrary function argument and captures it in a lambda, I always need to make that lambda mutable, because I don't know what users can pass in?
That's a design decision for your library API. You can require client code to pass function objects with a const
-qualified operator()
(which is the case for non-mutable
lambda expressions). If something different is passed, a compiler error is triggered. But if the context might require a function object argument that modifies its state, then yes, you have to make the internal lambda mutable
.
An alternative would be to dispatch on the ability to invoke operator()
on a const
-qualified instance of the given function type. Something along those lines (note that this needs a fix for function objects with both const
and non-const
operator()
, which results in an ambiguity):
template <class Fct>
auto wrap(Fct&& f) -> decltype(f(), void())
{
[fct = std::forward<Fct>(f)]() mutable { fct(); }();
}
template <class Fct>
auto wrap(Fct&& f) -> decltype(std::declval<const Fct&>()(), void())
{
[fct = std::forward<Fct>(f)]() { fct(); }();
}
Notably, wrapping f1 in std::function seems to resolve this problem (how?).
This is a bug in std::function
due to its type-erasure and copy semantics. It allows non-const
-qualified operator()
to be invoked, which can be verified with such a snippet:
const std::function<void()> f = [i = 0]() mutable { ++i; };
f(); // Shouldn't be possible, but unfortunately, it is
This is a known issue, it's worth checking out Titus Winter's complaint on this.