Const ref member: Yes
Are there cases where you might want a const reference member? Yes.
This article is part of a series of articles on programming idioms.
We’ve just seen that we wanted to store the used interfaces as references and
not mark them const
, and we criticised const usage in that
context. Before hurrying to create a simplistic, but wrong, rule
that member references should never be const
, let’s look at an example
where a const
reference member is good.
Canonical example
This is a slightly simplified example from Elements of Programming
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename T>
class lower_bound_predicate {
const T & a; // const reference is good here
public:
lower_bound_predicate(const T & a) : a(a)
{}
bool operator()(const T & x) {
return !(x < a);
}
};
template<typename I, typename T>
I lower_bound(I f, I l, const T & a) {
lower_bound_predicate<T> p(a);
return partition_point(f, l, p);
}
or you might do it with an equivalent lambda, but the mechanics is the same:
1
2
3
4
5
6
7
template<typename I, typename T>
I lower_bound(I f, I l, const T & a) {
auto p = [&a](const T & x) {
return !(x < a);
};
return partition_point(f, l, p);
}
though note that some details are more logical in the Elements of Programming than in the C++ standard library e.g. with regards to partition.
Explanation
Despite it’s initially unintuitive name, lower_bound
is a great algorithm
that basically uses divide and conquer to do what most people thing of a binary
search on O(lg(N))
time. I already covered how it works in
glorious details.
What I illustrate here is that lower_bound
uses parition_point
which finds
a boundary given a predicate. The lower_bound_predicate
creates the predicate
that we want: for the values smaller than a
it returns false
, for equal or
grater it returns true
. partition_point
finds the point where the predicate
transitions from false
to true
.
To achieve that, lower_bound_predicate
captures a
by const
reference. In
general that would raise dangling reference concerns, but it’s clear from this
particular context that the scope of a
as a function parameter for
lower_bound
extends the scope of the instance of lower_bound_predicate
.
So we do something potentially dangerous for a tangible gain: the algorithm
does not need to copy the type T
which sometimes might need to allocate on
the heap (e.g. if T
is a std::string
).
Similarities
Notice the similarities between a function object and classes in the mockable
interfaces idiom: they are does, things that do stuff, though in the case of
the function object they are refined to only do one thing which is what the
operator()
captures. Them using references in member variables might come
from the similarities they share in their intent.