C++ Logo


Advanced search

Re: [std-proposals] Allow non-virtual methods to be final

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Thu, 5 Jan 2023 20:14:47 -0500
On Thu, Jan 5, 2023 at 7:19 PM Steve Thomas via Std-Proposals
<std-proposals_at_[hidden]> wrote:
> On Thu, Jan 5, 2023 at 2:15 PM Brian Bi via Std-Proposals <std-proposals_at_[hidden]> wrote:
>> On Thu, Jan 5, 2023 at 5:09 PM Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]> wrote:
>>> On Thu, Jan 5, 2023 at 10:01 PM Brian Bi <bbi5291_at_[hidden]> wrote:
>>> > No.
>>> >
>>> > There's no legitimate reason to restrict what the author of B can make
>>> > one of its non-virtual functions do. As such, there should not be a
>>> > language-level mechanism for it.
>>> The following program fails to compile:
>>> struct A {
>>> virtual int Func(void) final { return 5; }
>>> };
>>> struct B : A {
>>> int Func(void) { return 6; }
>>> };
>>> int main(void)
>>> {
>>> B obj;
>>> }
>>> Note here that the person who wrote 'B' didn't necessarily know that
>>> 'A' had a virtual function called 'Func', but nonetheless 'B' is
>>> restricted in what it can do. So it won't be that much different
>>> restricting non-virtual functions also.
>> I think you meant to send this to the list, not to me.
>> Sure, if you declare a virtual method `final`, then you restrict what derived classes can do, but the difference is that there's actually a legitimate reason for the restriction. If I have
>> int foo(A& a) { return a.Func(); }
>> and `A::Func` is final, then the compiler can devirtualize the call. In addition, the writer of this code can rely on the guarantee that `a.Func()` has the same value and side effects as `a.A::Func()`. If any other functions in `A` call `A::Func`, then they, too, can rely on such guarantees.
>> On the other hand, if `Func` is non-virtual, then those guarantees already hold; having the ability to prevent a derived class from declaring a method with the same name does not strengthen them. As such, it is a restriction that gives nothing in return.
> There are two things going on here:
> 1) final prevents calls to Func() being delegated to subclasses from the code under A's control (i.e., itself and parent scopes)
> 2) final prevents the name (actually the signature) from being reused in subclass scopes
> 1) is a legitimate goal of final. 2) is not.
> So, the real question is: why does the specification make a half-assed attempt at making 2) true if it's not a goal of final? I say half-assed, since if you are really desperate to hide the name and reuse it in subclasses, you can workaround by adding a trivially defaulted parameter to the signature, at which point the compiler will stop complaining, even with calls that use exactly the same signature as before. If the point of final is not to restrict what you can do in subclasses, then why does the specification do exactly that?

I'd say that the problem comes from mischaracterizing the behavior of
`final`. #1 and #2 are not separate goals. Indeed, #2 isn't a goal at
all; it is the *mechanism* by which we achieve #1.

A call to `a->Func()`, where `a` is an `A*` can only be "delegated to
subclasses from the code under A's control" if one of those
"subclasses" is able to define a function with `Func`'s signature. So
if you want to prevent such delegation, you must prevent users from
being able to declare such functions.

I mean, you *could* have some rule where you say that a function
declaration overrides a base class function only if the base class
function is not declared `final`; otherwise it acts like a non-virtual
function. But because C++ as a language does not *require* you to
explicitly say if a function declaration is overriding another or not,
it would be very strange indeed if a potentially overriding function
declaration would just... stop being one.

Also, what happens if you declare a `virtual` function that
potentially overrides with a base class `virtual` function that's
`final`? Does this now become a new virtual function? To be honest, if
your class hierarchy is *that* incoherent, you deserve a compile

It's just easier to declare it a compile error and make everyone work
out what it is they're really trying to accomplish. After all, the
writer of `B` didn't call it `Func` by accident. And if someone starts
adding virtual functions to a base class that is in active use, that
is a potentially breaking change for everyone who has inherited from
said class.

Lastly, it's important to understand that `override` and `final` were
never intended to be real syntax. Initially they weren't even
pseudo-keywords; they were attributes. As such, they couldn't really
change the *behavior* of the code; they merely made it ill-formed.
Herb Sutter managed to convince the committee that this was a
profoundly stupid idea and that they needed to be real syntax. But the
overall idea that these don't change the behavior of valid code
remains the same.

`final` isn't allowed to change the meaning of what a derived class
does; it only changes whether the code is valid or not.

Received on 2023-01-06 01:14:56