Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Virtual functions: To const, or not to const?

62 views
Skip to first unread message

Juha Nieminen

unread,
Oct 7, 2022, 5:47:29 AM10/7/22
to
I have encountered this small dilemma several times. It's a question of
const correctness and proper OO design in C++.

Should a virtual function in a base class be const or not? This especially
in cases where it's very possible that a derived implementation wants
to change its own state (and thus would want the function to not be
const).

The answer seems simple enough: Obviously it's better to not make it
const, to allow for this possibility.

Problem is, when it's not const, now it can't be called using a const
instance, or const reference or pointer to one. There may be situations
where the only thing you have is a const version of the object, and
there's no way around that.

(Ok, technically speaking you *can* call it, by using const_cast. But
that's a really ugly solution, bad design, and in some cases might
even potentially break something. (Const code might assume the object
doesn't change.))

"Well, in that case offer *both* versions of the virtual function in
the base class, duh! Problem solved!"

Except that sometimes, in some situations, there may not be any
reasonable way to implement a const version of a function, because
it *has to* change the object in order to function correctly.
Sometimes a const version just doesn't make any sense. (For example,
a const version of std::vector::push_back() wouldn't make much
sense. What would it be supposed to do? Nothing? That would make
it really odd design, and would have the potential to break
something.)

This isn't even a question of "const correctness". It's a question
of getting a compiler error if all you have is a const reference
(and no way around that) and the virtual function happens to be
non-const (even though the implementation doesn't actually change
anything).

David Brown

unread,
Oct 7, 2022, 6:50:27 AM10/7/22
to
On 07/10/2022 11:47, Juha Nieminen wrote:
> I have encountered this small dilemma several times. It's a question of
> const correctness and proper OO design in C++.
>
> Should a virtual function in a base class be const or not? This especially
> in cases where it's very possible that a derived implementation wants
> to change its own state (and thus would want the function to not be
> const).
>
> The answer seems simple enough: Obviously it's better to not make it
> const, to allow for this possibility.
>
> Problem is, when it's not const, now it can't be called using a const
> instance, or const reference or pointer to one. There may be situations
> where the only thing you have is a const version of the object, and
> there's no way around that.
>

Shouldn't it be a matter of deciding whether the function logically
changes the object or not? If it clearly changes the object, it must be
non-const. But if it leaves the object with the same publicly visible
state, make it const. Any later bits that might need changing should be
mutable.

A virtual function is an interface, not implementation detail. It
doesn't make sense to me to have a virtual function that is logically
const in a base class, but logically non-const in a derived class.

Paavo Helde

unread,
Oct 7, 2022, 7:00:25 AM10/7/22
to
07.10.2022 12:47 Juha Nieminen kirjutas:
> I have encountered this small dilemma several times. It's a question of
> const correctness and proper OO design in C++.
>
> Should a virtual function in a base class be const or not? This especially
> in cases where it's very possible that a derived implementation wants
> to change its own state (and thus would want the function to not be
> const).
>


IMO, the 'const' should be applied logically. There is a virtual
function which does something. Is this something that conceptually
should not modify the object state? If yes, then make the virtual
function const.

If a derived class overrides this function, it should conceptually still
do something which does not modify the object. If it conceptually does,
then it's possible you have bigger problems with the design, starting
with the function name.

If the derived class modifies its state only technically, e.g. by
caching something, we have means to express this in C++ cleanly - that's
the 'mutable' keyword.

When following these rules, now if you have a const reference to the
object and need to call a non-const virtual function on it, this means
you want to call a function on a const object which might (again,
conceptually) modify the object state. That's a design smell, and this
is the exact reason why the language has the const qualifier in the
first place - to catch such things at compile time so the design could
be fixed.

In reality the things won't work out so nicely. In some cases the base
class virtual interface is at so abstract level it is impossible to say
if the functions might conceptually modify the object or not. According
to the above guidelines these virtual functions should be non-const,
which in turn means that this class cannot be really used via const
references. In my code I have got several "non-const" classes which are
basically always accessed only over non-const references. Yes, this
looses the benefits of 'const', but at least it is honest about that and
does not hide it behind a wall of const_cast-s.

Other scenario is e.g. with virtual functions which take callbacks or
functors as arguments. Some callbacks might want to modify the object,
some not. In that case one should provide two different virtual
functions, one const and the other non-const.

If there is some override which does not make sense and which should not
be called, then it's easy for me. I just put a debug assert failure and
a runtime exception there.

hth
Paavo



Richard Damon

unread,
Oct 7, 2022, 7:29:21 AM10/7/22
to
The answer is that if the state isn't part of the visible state, make
the state mutable, which means it CAN be changed in const objects and
not giv problems.

If the state is part of the visible state, then you shouldn't really do
that, as const functions shouldn't visible change the state, but it says
the overriding doesn't really apply. A caller thining they are doing a
constant operation on an object shouldn't find the object having changed.

You have a design issue is you have an operation that inherently might
change the object but also needs to be implemented on const objects.

THAT is your problem.

Öö Tiib

unread,
Oct 7, 2022, 8:56:08 AM10/7/22
to
On Friday, 7 October 2022 at 12:47:29 UTC+3, Juha Nieminen wrote:
> I have encountered this small dilemma several times. It's a question of
> const correctness and proper OO design in C++.
>
> Should a virtual function in a base class be const or not? This especially
> in cases where it's very possible that a derived implementation wants
> to change its own state (and thus would want the function to not be
> const).

If it is not changing base class state only because the base class is kind
of dummy/refusing but otherwise it is logical to change state then of
course do not make method const. If it changes derived state only
because of some internal efficiency or safety (caching, reporting, lazy
loading) but otherwise it is logical not to change state then make
member const but that state mutable.

> The answer seems simple enough: Obviously it's better to not make it
> const, to allow for this possibility.
>
> Problem is, when it's not const, now it can't be called using a const
> instance, or const reference or pointer to one. There may be situations
> where the only thing you have is a const version of the object, and
> there's no way around that.

If it logically mutates state of object then that is good that it can not
be called on const object.

> (Ok, technically speaking you *can* call it, by using const_cast. But
> that's a really ugly solution, bad design, and in some cases might
> even potentially break something. (Const code might assume the object
> doesn't change.))

Yes. That can break something.

> "Well, in that case offer *both* versions of the virtual function in
> the base class, duh! Problem solved!"
>
> Except that sometimes, in some situations, there may not be any
> reasonable way to implement a const version of a function, because
> it *has to* change the object in order to function correctly.

So you want the call to compile on const object despite it can't be
done on the const version? Then the const version can throw or refuse
in some other manner.

> Sometimes a const version just doesn't make any sense. (For example,
> a const version of std::vector::push_back() wouldn't make much
> sense. What would it be supposed to do? Nothing? That would make
> it really odd design, and would have the potential to break
> something.)

Then it is perfect that attempt to call push_back() on const object
gives compiling error.

> This isn't even a question of "const correctness". It's a question
> of getting a compiler error if all you have is a const reference
> (and no way around that) and the virtual function happens to be
> non-const (even though the implementation doesn't actually change
> anything).

We can't have some kind of pre-decided silver bullet here. On
some cases we make virtual function non-const on other cases
const and on third cases provide both overloads. Logic is more or
less same as without dynamic polymorphism.
As always there can be some kind of grey area where it is not
crisp clear what to do but such example needs to be looked at and
decided concretely and individually, general rules are hard to
establish.

Alf P. Steinbach

unread,
Oct 8, 2022, 8:56:33 AM10/8/22
to
On 7 Oct 2022 11:47, Juha Nieminen wrote:
> I have encountered this small dilemma several times. It's a question of
> const correctness and proper OO design in C++.
>
> Should a virtual function in a base class be const or not? [snip]

Others have already answered this to my mind satisfactorily.

However, consider also the case of whether a non-virtual member function
should be `const` or not.

One possible dilemma is proxy class like the one returned by
`std::vector<bool>::operator[]`, where its assignment operator can and
in my opinion should be `const` because it doesn't modify /that/ object,

class Proxy
{
public:
auto operator=( const bool value ) const -> Proxy;
};

But the whole point of assignment is to modify an object, in this case
the real `vector`. So I guess a case can be made that the assignment
operator should not be `const`. E.g., the state that's observable via
this proxy object, changes as a result of an assignment to it.

The reason that I think it should be `const` is that the `const` adds a
constraint that can be useful for programmers: that /this/ object's
internal non-`mutable` state doesn't change.

Nothing about "visible" state or that; for this case it's all about
internal object state.

Then if one doesn't accept that as a universal criterion, and I DON'T,
then the question is what is it that makes classes different in this
respect?

I have no good answer.


- Alf

Bonita Montero

unread,
Oct 8, 2022, 10:35:51 AM10/8/22
to
There is no general answer or it depends on what you expect from
this interface. In individual cases, this question is then easy
to answer and therefore I do not know why it has to be discussed.

Michael S

unread,
Oct 8, 2022, 2:03:43 PM10/8/22
to
There is a general answer - don't use inheritance except as substitute
for interfaces, which typically means abstract classes with no data members.
All other forms of inheritance are known antipattern.
Use aggregation instead.

Bonita Montero

unread,
Oct 8, 2022, 2:36:17 PM10/8/22
to
Inheritance might be sometimes more difficult to handle.
But if you handle it correctly it's often less work than
with pure interfaces.

Mr Flibble

unread,
Oct 8, 2022, 3:20:09 PM10/8/22
to
Bullshit. Whilst in general composition is to be preferred over
inheritance there is nothing fundamentally wrong with deep inheritance
hierarchies if you have the slightest clue; it certainly is NOT an
anti-pattern: it is just another tool in the toolbox.

/Flibble

Manfred

unread,
Oct 8, 2022, 3:43:18 PM10/8/22
to

On 10/7/2022 11:47 AM, Juha Nieminen wrote:
> I have encountered this small dilemma several times. It's a question of
> const correctness and proper OO design in C++.
>
> Should a virtual function in a base class be const or not? This especially
> in cases where it's very possible that a derived implementation wants
> to change its own state (and thus would want the function to not be
> const).

As others have said, 'const'-ness is not really correlated with the
function being virtual or not; rather, it is a design choice strictly
coupled with the expected behavior (or contract) of the function.
In short, if it changes the (logical) state of the object, then it's
just non-const; if it doesn't, and it is not meant to change it, then it
may be (and probably is best, if you are convinced of your design)
qualified as const.

I'll add that during a course on C++ design, more than two decades ago,
the guy stressed that 'const is a very strong qualifier' in the C++ type
system.
Emphasis on /strong/, meaning that the decision whether some member is
or is not const should be considered thoroughly in C++ design, despite
how tiny the change is in code.
If, later along the way, you find yourself in the need of changing that
design choice, then I'd either make a new function or at least make an
overload (which, in C++, works well given the 'strong' nature of the
qualifier)

<snip>

> Except that sometimes, in some situations, there may not be any
> reasonable way to implement a const version of a function, because
> it *has to* change the object in order to function correctly.

I see here one of two things happening: either the 'state' of the object
or the semantics of the function is not defined properly.
A third, rare IME, alternative is that you would have some data member
that is not really part of the logical state of the object - that's the
use for 'mutable'.

Mut...@dastardlyhq.com

unread,
Oct 9, 2022, 5:20:15 AM10/9/22
to
On Sat, 8 Oct 2022 11:03:35 -0700 (PDT)
Michael S <already...@yahoo.com> wrote:
What a load of nonsense. The java system of extending an interface is next to
useless compared to proper inheritence.

>Use aggregation instead.

I don't think you understand the different use cases of inheritence vs
aggregation.

Juha Nieminen

unread,
Oct 10, 2022, 2:20:52 AM10/10/22
to
David Brown <david...@hesbynett.no> wrote:
> Shouldn't it be a matter of deciding whether the function logically
> changes the object or not? If it clearly changes the object, it must be
> non-const. But if it leaves the object with the same publicly visible
> state, make it const. Any later bits that might need changing should be
> mutable.
>
> A virtual function is an interface, not implementation detail. It
> doesn't make sense to me to have a virtual function that is logically
> const in a base class, but logically non-const in a derived class.

There are situations where the base class function is *so* abstract that
it doesn't even express if the derived class implementations should be
mutable or const. Some derived classes might want to implement as a
non-const function, other derived classes as a const function (so that
it can be called with const objects/references).

There's really no way to allow for this in the base class. Making the
base class function const (and expecting the derived class to use
const cast to eg. call other of its own non-const functions) seems
a bit wrong. On the other hand, making it non-const now makes calling
the function with a const object harder. Declaring both a non-const
and const versions of the function may burden the derived class with
having to implement a const version that it can't logically implement.

All possible solutions have something wrong about them.

Richard Damon

unread,
Oct 10, 2022, 7:38:52 AM10/10/22
to
If the definition of what the function can do is that loose, then it
isn't defined well enough to be an "interface".

If in some cases it must be able to be performed on a const object
becuase it is defined to not change the object, but for other sub-types
it can't be used on const objectes because it does change the object,
then the operation isn't the "same" for all cases, and so you can't
define the generic interface.

Given an object which you only know is of an object defined from
something of that base, you don't know if you can use that operation on
it. Thus, it can't be an actual interface.

It may be that all of them can use the non-const version, and specific
sub-types can add (as an overload) a const version.

Juha Nieminen

unread,
Oct 10, 2022, 8:08:25 AM10/10/22
to
Richard Damon <Ric...@damon-family.org> wrote:
> If the definition of what the function can do is that loose, then it
> isn't defined well enough to be an "interface".

That isn't very helpful if you are in this situation.

I suppose the answer is: "If you can't redesign the base class in such
a way that such ambiguity doesn't happen, then perhaps the best option
is to make it non-const and then in the derived class create a const
version that calls the non-const one using a const_cast. It might be
a bit ugly, but it's the best we got."

Paavo Helde

unread,
Oct 10, 2022, 8:13:53 AM10/10/22
to
10.10.2022 09:20 Juha Nieminen kirjutas:
> David Brown <david...@hesbynett.no> wrote:
>> Shouldn't it be a matter of deciding whether the function logically
>> changes the object or not? If it clearly changes the object, it must be
>> non-const. But if it leaves the object with the same publicly visible
>> state, make it const. Any later bits that might need changing should be
>> mutable.
>>
>> A virtual function is an interface, not implementation detail. It
>> doesn't make sense to me to have a virtual function that is logically
>> const in a base class, but logically non-const in a derived class.
>
> There are situations where the base class function is *so* abstract that
> it doesn't even express if the derived class implementations should be
> mutable or const. Some derived classes might want to implement as a
> non-const function, other derived classes as a const function (so that
> it can be called with const objects/references).

If that's really so, then it looks like you have got two different class
hierarchies or at least two interfaces, not one.

Maybe the part(s) of the inheritance tree which you want to call over
const references should be cut off and converted into a separate tree
having a const interface.




Bonita Montero

unread,
Oct 10, 2022, 11:58:24 AM10/10/22
to
Am 08.10.2022 um 21:19 schrieb Mr Flibble:
> On Sat, 8 Oct 2022 11:03:35 -0700 (PDT)
> Michael S <already...@yahoo.com> wrote:
>
>> On Saturday, October 8, 2022 at 5:35:51 PM UTC+3, Bonita Montero
>> wrote:
>>> There is no general answer or it depends on what you expect from
>>> this interface. In individual cases, this question is then easy
>>> to answer and therefore I do not know why it has to be discussed.
>>
>> There is a general answer - don't use inheritance except as substitute
>> for interfaces, which typically means abstract classes with no data
>> members. All other forms of inheritance are known antipattern.
>> Use aggregation instead.
>
> Bullshit. Whilst in general composition is to be preferred over
> inheritance there is nothing fundamentally wrong with deep inheritance
> hierarchies ...

There's nothing wrong in a sense that there's usually no alternative.
But nevertheless these hierarchies might be hard to design.


Tim Rentsch

unread,
Oct 10, 2022, 12:27:15 PM10/10/22
to
Juha Nieminen <nos...@thanks.invalid> writes:

[...]

> All possible solutions have something wrong about them.

That's because what you're asking for is self-contradictory. On
the one hand you want to be able to invoke the method on a const
reference. On the other hand you want invoking the method on a
const reference to give a compile-time error. Obviously the same
interface cannot do both, because either possibility necessarily
precludes the other.

Tim Rentsch

unread,
Oct 10, 2022, 12:43:42 PM10/10/22
to
Paavo Helde <ees...@osa.pri.ee> writes:

> 10.10.2022 09:20 Juha Nieminen kirjutas:
>
>> David Brown <david...@hesbynett.no> wrote:
>>
>>> Shouldn't it be a matter of deciding whether the function logically
>>> changes the object or not? If it clearly changes the object, it must be
>>> non-const. But if it leaves the object with the same publicly visible
>>> state, make it const. Any later bits that might need changing should be
>>> mutable.
>>>
>>> A virtual function is an interface, not implementation detail. It
>>> doesn't make sense to me to have a virtual function that is logically
>>> const in a base class, but logically non-const in a derived class.
>>
>> There are situations where the base class function is *so* abstract that
>> it doesn't even express if the derived class implementations should be
>> mutable or const. Some derived classes might want to implement as a
>> non-const function, other derived classes as a const function (so that
>> it can be called with const objects/references).
>
> If that's really so, then it looks like you have got two different
> class hierarchies or at least two interfaces, not one.

Right. I was wondering how long it would be before someone would
reach this conclusion.

Here is an outline for a scheme that supplies each of the four
logically distinct possibilities:

struct super_neither {
protected:
unsigned u_;

public:
super_neither() : u_(0) {}
virtual ~super_neither();
};

struct super_mutable_only : public virtual super_neither {
virtual unsigned u() { return ++u_; }
};

struct super_nonmutable_only : public virtual super_neither {
virtual unsigned u() const { return 1+u_; }
};

struct super_both : public super_mutable_only, super_nonmutable_only {
virtual unsigned u() { return super_mutable_only::u(); }
virtual unsigned u() const { return super_nonmutable_only::u(); }
};

Different subclasses should choose whichever of the four given
superclasses that is most appropriate to their individual needs.

Disclaimer: code intended only to illustrate the approach; some
details may need changing, depending on particular circumstances.

Richard Damon

unread,
Oct 10, 2022, 8:48:17 PM10/10/22
to
Which is sort of what I described, except I would probably define in the
derived class the CONST version, and make the non-const version forward
to it, since that is always well defined.

If the const version is called on an actual const object, and then it
const-casts to the non-const version, and that ends up actually not
being actually const, then you have invoked undefined behavior.


As I was pointing out, if you end up in this situation, your really need
to evaluate why you got there, as something seems off.
0 new messages