C++ Logo

STD-PROPOSALS

Advanced search

Subject: Re: [std-proposals] Conditional final class-virt-specifier
From: Jason McKesson (jmckesson_at_[hidden])
Date: 2020-07-15 01:11:09


On Tue, Jul 14, 2020 at 4:56 AM Paweł Benetkiewicz via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> Thank you very much for your responses. Here are mine responses for your questions and a few comments:
>
> > I also think this idea is dysfunctional. […] That's kind of a big change in how `final` works.
> Modern C++ compilers use LARL as resolving ambiguity may (and often do) require a separate pass. Or many passes. In fact, C++ can require an infinite lookahead.
> Unconditional final remains the same, conditional final may require additional pass, same as any other `constraint-logical-or-expression`.

Um, no it doesn't. Constraints of any kind do not require parsing the
definition of the construct being constrained. Constraints are only
based on the declaration. You cannot constrain a class template `T` on
the properties of `T` itself.

> I could call it a big change before introducing `constraint-logical-or-expression` in C++20. But once we have concepts, I don't see the need for any change.
> I am surprised about feedback as it seems you wrote about resolving condition at first pass and forgot `constraint-logical-or-expression` can require multiple passes or forgot I've proposed condition to be `constraint-logical-or-expression` and not `constant-expression`.
>
> > On a similar note, how is your `is_final` supposed to function at all?
> 1) unary is_final (is_final<class T>) should be true if the function is unconditionally final (single one word final specifier as before proposal). In other words: true if no class can derive from T.
> 2) relation is_final (is_final< class Base, class Derived>) should be true if Derived derives from Base and either Base is unconditionally final or it's conditionally final and `constraint-logical-or-expression` of `final-clause` is true (optionally with Derived temporary used as `identifier` in `final-head`). In other words: true if Derived can derive from Base.

I think the point you missed is that, if `final` is conditional, then
to answer this question, `Derived` must be fully defined (if the
condition relies on any property of `Derived` other than that it
exists). Therefore, if a compile error has not happened by the time
you're asking this question, then `Derived` has already inherited from
`Base`.

So what's the point of asking a question whose answer is *certainly* "yes"?

>
> It could be also split into two separate type traits like is_unconditionally_final and is_conditionally_final, but it could break backwards compatibility.
>
> > I have never *heard *of "conditional inheritance."
> Just first few googled examples:
> - https://stackoverflow.com/questions/5040831/restrict-the-classes-that-may-implement-an-interface
> - https://stackoverflow.com/questions/5767160/allowing-implementing-interface-only-for-specific-classes
> - https://stackoverflow.com/questions/26465237/allow-a-mock-class-to-inherit-from-a-final-class
> - https://stackoverflow.com/questions/33007574/restrict-classes-that-can-implement-interface-in-java
> - https://stackoverflow.com/questions/40094218/can-i-limit-which-classes-can-implement-an-interface
> - https://stackoverflow.com/questions/40967838/how-to-restrict-the-interface-implementation-in-c-sharp
> - https://softwareengineering.stackexchange.com/questions/316206/how-to-make-interfaces-usable-for-special-classes-only
> There are many requests for a conditional inheritance across many languages.

OK but what does that have to do with C++?

Class `final` is already a dubious prospect in C++ because C++ uses
inheritance for things that other languages do not. C++ uses
inheritance where other languages might use mixins or partial classes
for example. C++ users sometimes employ inheritance to gain access to
the empty base optimization when applicable. C++ users sometimes
employ inheritance for test driven frameworks (as shown by one of your
questions).

Basically, declaring a class to be `final` should *always* be looked
on as a code smell in C++. It makes about as much sense in C++ to
declare that a class cannot be a base class of another class as it
does to declare that a class cannot be a *member* of another class.

So why do we want to encourage users to do a thing that C++ probably
shouldn't have done to begin with?

> Some languages already supports limited functionality to my proposal, restricting inheritance:
> - PostSharp has `InternalImplement` attribute, see "Restricting Interface Implementation": https://doc.postsharp.net/control-implementation
> - Kotlin allows inheriting from sealed in same file: https://kotlinlang.org/docs/reference/sealed-classes.html
> This pybind commit is a great example of why my proposal is useful: https://github.com/pybind/pybind11/pull/2151/files
>
> > At this point, the name `E` is undeclared, so this can't possibly work.
> It does not need to be declared, as it's a `constraint-logical-or-expression`. It needs to be declared for final conditioning only,
> Look for this example (not proposal related, but also uses `constraint-logical-or-expression`):
> ```
> template<class T>
> requires std::is_same_v<T, C>
> void f();
> struct C {};
> ```
> Here, C in the second line of code also is not defined (not ever declared) but it's ok, as it's found on the second pass.

No it doesn't. On none of the three major compilers does the code
you've posted compile. Seen here: https://gcc.godbolt.org/z/EEeMae

I don't know what you think a `constraint-logical-or-expression` is,
but it's not some magical break-the-rules-of-C++ thing. It has the
same lookup rules as any other aspect of C++. Within a template, names
that are dependent on the template parameters have their lookup
deferred until the template is instantiated, while other names are
looked up at the point where the template is declared.

Being part of the `constraint-logical-or-expression` grammar doesn't
change any of those rules.

> > Not okay; redefinition of struct `F`.
> Indeed my bad in R0 as there is no partial classes support in raw ISO C++:
> - struct F : D {}; // OK: F derives from E
> - struct G : D {}; // ill-formed: G does not derive from E
> + struct H : F, D {}; // OK: F derives from E
> + struct I : G, D {}; // ill-formed: G does not derive from E
> Also, there should be 'In case (3)', not 'In case (2)' in the Example section of proposal, and note in Wording/8 should be marked as added (green).
>
> > Furthermore: the proposal states "Disadvantages: none", "Design trade-offs: none". While the latter may end up being somewhat accurate, the first one is complete nonsense, rendering this proposal technically ill-formed.
> So will it be ok if I change 'Disadvantages' from "None" to "Proposal increases complexity of language by adding optional `final-clause`."? I've read a few proposals (both PR… and N…) and I didn't find such sentences.

It's more that, if you're going to have a list of "Disadvantages" or
"design trade-offs", then it needs to actually list them honestly.

>
> > So the basic question "why is this worth having?" is not answered by the proposal.
> The Proposal is worth having, because it does "heavily increase compatibility correctness by inheritance constraints" by providing an ability to encapsulate interfaces.

But why is that a thing worth doing? The examples only show how to do
things, not why it is *important* to be able to do them.


STD-PROPOSALS list run by herb.sutter at gmail.com

Standard Proposals Archives on Google Groups