Const Correctness Issue of std::function
The const
type qualifier is one of the jewels of the C++ language design. Surrounding by this feature, we devise the "const
correctness" practice to prevent const
objects from getting mutated. The const
correctness rule is straight-forward to follow for implementation of the most classes, but it is harder to heed for classes with type erasure. Unfortunately, the standard library type std::function
is implemented by type erasure; and due to short-sightedness, it becomes one of the ill-behaved citizens that doesn't follow the const-correctness rule.
The Problem
std::function
has one const member operator()
, yet it can mutate the underlying function. For example,
const std::function<int()> f {[x=0]() mutable { return ++x; }};
f(); // returns 1
f(); // returns 2
The Document N43481 first formalized this concern. It states that
This violates not only basic tenets of
const-
correctness, but also the data race avoidance guarantees in [res.on.data.races]/p3, which states that "A C++ standard library function shall not directly or indirectly modify objects accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's non-const
arguments, includingthis
".
The Fix
Implementations of a function
-like class should have separate specializations for const
and non-const
.
template<class Sig> class function; // not defined
template<class R, class... Args>
class function<R(Args...)>;
template<class R, class... Args>
class function<R(Args...) const>;
operator()
of the const
specialization should be annotated as const
, but the constructor of the const
specialization would not accept mutable function objects.
function<int() const> f1 {[x=0]() { return x; }};
f1() // ok;
function<int() const> f2 {[x=0]() mutable { return ++x; }}; // Does not compile
On the other hand, operator()
of the non-const
specialization would not have const
type signature, so you cannot invoke the const
version of such functions at all:
function<int()> f1 {[x=0]() mutable { return ++x; }};
f1(); // ok
const function<int()> f2 {[x=0]() mutable { return ++x; }};
f2(); // Does not compile
The Future
I don't expect std::function
itself to have any change that breaks backward-compatibility. As of the time of this writing (December 2019), my bet is on the proposed std::unique_function
2, which is a drop-in replacement of std::function
that fixes the const-correctness bug among other features. Once we have an alternative in standard, std::function
can be deprecated just like std::auto_ptr
. In the meantime, we can always implement unique_function
on our own, and I have a small library to implement that on Github.