Const-Overcorrectness

Const-correctness is popular among C++ programmers. Most believe that it is a good thing.

The general idea is simple. Things that are immutable should be declared so. This way, you can prevent error during compile-time.

But C++ is full of pitfalls. There are little guidance on how const-correctness should be achieved.

Recently, I have run into code that are over-corrected.

Just Put It Everywhere

To some, the idea of const-correctness seems to be achieved by simply adding const everywhere.

Here is an example. (rewritten for demonstration purposes)

// simple structure that holds location information
class CLocationData
{
public:
	const float GetAltitude() const;
	const float GetLatitude() const;
	const float GetLongitude() const;
	const std::string GetName() const;

	// Comparison predicate. No complain on it being member func.
	bool operator()(
		CLocationData const *const lhs,
		CLocationData const *const rhs) const;
};

There are 13 use of const here, and 6 of them are misused in my opinion.

Const-Uselessness

Const provides restrictions. It tells you whether or not mutation may occurs.

Consider the member function CLocationData::GetAltitude() from before.

const float GetAltitude() const;

The const on the right is a qualifier that applies to CLocationData::GetAltitude().

In design-by-contract perspective, it provides a powerful promise  – by calling GetAltitude(), the instance of CLocationData pointed by the this pointer will not be mutated.*

This is a great use of const.

The const on the left is a qualifier that applies to float that is returned by value. This implies the float that is copied and returned is immutable.

In the perspective of the caller, it provides absolutely no value because the float was copied, and can not cause any mutation in the internals of CLocationData.

// user can still do this
float a = CLocationData().GetAltitude();

This usage of const completely useless except for some obscure scenario, and only leads of confusion.

* Assuming mutable is not used.

Oversharing of Implementation Details

Here is another use of const in the example.

	bool operator()(
		CLocationData const *const lhs,
		CLocationData const *const rhs) const;

Again, the const on the right indicates that the function is purely used as comparison, and does not mutate the data behind the this pointer. This is good.

So what about about CLocationData const *const lhs?

When it comes to pointer value, you read the declaration from right-to-left. So this is read as – the variable lhs is a constant pointer to a constant CLocationData object.

The const on the left says that the data pointed by lhs is immutable. This is a powerful and important promise, therefore a good use of const.

The const on the right says that the pointer value itself will not change. To the caller’s perspective, it is completely useless because the pointer value is “copied” in the first place, and can not have any side effects on the caller side.

Instead, the const on the right is a restriction imposed on the implementation of CLocationData::operator(). This is an implementation detail that does not need to be shared, and therefore it is a bad use of const.

Conclusion

When returning by value on non-pointer and non-reference types, applying const qualifier on the return value is almost always redundant.

Const imposes a restriction. If this restriction does not apply to the caller, don’t apply it. There is a good chance that it is just implementation details. In design-by-contract perspective, the contract is unnecessarily verbose.

Here’s the example after it has been sanitized.

// simple structure that holds location information
class CLocationData
{
public:
	float GetAltitude() const;
	float GetLatitude() const;
	float GetLongitude() const;
	std::string GetName() const;

	// Comparison predicate. No complain on it being member func.
	bool operator()(
		CLocationData const *lhs,
		CLocationData const *rhs) const;
};

One thought on “Const-Overcorrectness

Leave a comment