Date: Fri, 25 Nov 2022 18:02:16 +0100
C++20 has this feature. Check my prev mail (the one just before yours.)
On Fri, Nov 25, 2022, 17:45 Arthur O'Dwyer via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On Fri, Nov 25, 2022 at 9:49 AM Jason McKesson via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> There seems to be a broad lack of understanding of what this feature
>> is doing [...] The goal of the proposal is to be able to write a function
>> that takes
>> a single pointer to any type which inherits from *two or more* base
>> classes. And then use that pointer to access any function which is
>> available from any of these base classes. [...]
>>
>> Personally, I don't think this feature is worth doing. The
>> concept/template solution is adequate for those who need it, and it's
>> probably not something that is *broadly* useful. It's a niche feature.
>>
>
> Ditto, but my reason for not-adding-this-feature-to-C++ wouldn't be "it's
> niche" so much as "you don't need a core-language feature for this; you can
> already accomplish your goal just by using the existing language."
> Consider again the example [slightly simplified]:
>
> struct Control { virtual void SetBackgroundColor(); };
> struct TextEntry { virtual void SetValue(const char*); };
> struct TextCtrl1 : Control, TextEntry {};
> struct TextCtrl2 : Control, TextEntry {};
>
> void Red(??? *p) {
> p->SetBackgroundColor();
> p->SetValue("pending");
> }
> int main() {
> TextCtrl1 tc1;
> TextCtrl2 tc2;
> Red(&tc1);
> Red(&tc2);
> }
>
> Frederick's programming problem is: How do we write function `Red` so that
> it can accept either TextCtrl1 or TextCtrl2, and yet without making `Red`
> itself a function template? (We don't want `Red` to be a template because
> that would force its whole definition to be defined in a header file, which
> means recursively including all the stuff *used* in that definition, and
> so on.)
>
> Frederick's suggested solution was to just make it work by magic:
>
> void Red(__chimerical(Control, TextEntry) *p) {
> p->SetBackgroundColor(); // this means Control::SetBackgroundColor
> p->SetValue("pending"); // this means TextEntry::SetValue
> }
>
> However, this has problems. What would happen if the programmer wrote this
> code, and then later the maintainer of wxWidgets changed the definition of
> `Control` as follows?
> struct Control { void SetBackgroundColor(); void SetValue(const
> char*); };
> Now:
> p->SetValue("pending"); // Does this still mean TextEntry::SetValue?
> if so, why? Is it now ambiguous and ill-formed?
> This is related to the guideline "Don't inherit from types you don't
> control." This magic __chimerical(X,Y) becomes a method of "ad-hoc
> inheritance," by which any client programmer can introduce an
> inheritance-like dependency coupling the interfaces of any two classes X
> and Y.
> If you were proposing to implement the magic behavior by changing name
> lookup, then you also have to explain whether
> struct Control { void SetBackgroundColor(); void SetValue(); };
> would be treated similarly: Is `p->SetValue("pending")` still ambiguous,
> or do we do overload resolution among all the possible meanings of
> `SetValue` and pick the best match, or what?
>
> Anyway, you don't need to change the language for this. There are
> well-established programming techniques to accomplish your goal.
> The simplest would be to completely open-code it:
>
> // https://godbolt.org/z/Eq49Tqa9f
> void Red(void *p) {
> struct Dummy { virtual ~Dummy(); };
> auto asControl = [](void *p) {
> return dynamic_cast<Control*>(static_cast<Dummy*>(p));
> };
> auto asTextEntry = [](void *p) {
> return dynamic_cast<TextEntry*>(static_cast<Dummy*>(p));
> };
> asControl(p)->SetBackgroundColor();
> asTextEntry(p)->SetValue("pending");
> }
>
> This `Red` accepts a pointer to anything inheriting from both Control and
> TextEntry. Of course it also accepts a pointer to anything else (e.g. an
> `int`), and has UB if you do that; but, don't do that. You can check for
> that by tossing a simple template on the front end:
>
> template<class T>
> void Red(T *p) {
> static_assert(is_base_of_v<Control, T> && is_base_of_v<TextEntry,
> T>);
> RedImpl(p); // calls RedImpl(void*) as above
> }
>
> So that's the first option that exists today. The second option is to
> introduce your own "TextCtrl" interface, either as a base class (if you
> control the hierarchy at some point above `TextCtrl1` and `TextCtrl2`):
>
> // https://godbolt.org/z/nMhj1r13e
> struct TextCtrl : Control, TextEntry {};
> struct TextCtrl1 : TextCtrl {}; // CHANGED!!
> struct TextCtrl2 : TextCtrl {}; // CHANGED!!
> void Red(TextCtrl *p) {
> p->SetBackgroundColor();
> p->SetValue("pending");
> }
>
> or using type erasure (if you don't control the hierarchy):
>
> // https://godbolt.org/z/38qvM8T64
> struct TextCtrlPtr {
> TextEntry *t_;
> Control *c_;
> template<class T> TextCtrlPtr(T *p) : t_(p), c_(p) {}
> TextEntry *asTextEntry() const { return t_; }
> Control *asControl() const { return c_; }
> };
> void Red(TextCtrlPtr p) {
> p.asControl()->SetBackgroundColor();
> p.asTextEntry()->SetValue("pending");
> }
>
> Obviously you can polish `TextCtrlPtr` by making `SetBackgroundColor` and
> `SetValue` into member functions of it, if you really want to.
> For certain heavy-OOP-design-pattern-ish applications you might even want
> to introduce an adaptor class of your own. Here `TextCtrl1` and `TextCtrl2`
> are out of our control, but we can write a base class we *want* to use
> (`TextCtrl`) and then a concrete adaptor class `TextCtrlAdaptor` that can
> wrap a pointer to any type that implements both original base classes. This
> is the same as the type-erasure approach, except that the inheritance is
> explicit, and the `TextCtrlAdaptor` object actually IS-A `TextCtrl`. This
> is the first implementation where the `virtual` keyword really matters to
> the correctness of the example.
>
> struct TextCtrl1 : Control, TextEntry {};
> struct TextCtrl2 : Control, TextEntry {};
> struct TextCtrl : Control, TextEntry {};
> struct TextCtrlAdaptor : TextCtrl {
> TextEntry *t_;
> Control *c_;
> template<class T> explicit TextCtrlAdaptor(T *p) : t_(p), c_(p) {}
> void SetBackgroundColor() override { c_->SetBackgroundColor(); }
> // overrides virtual Control::SetBackgroundColor
> void SetValue(const char *s) override { t_->SetValue(s); } //
> overrides virtual TextEntry::SetValue
> };
> void Red(TextCtrl *p) {
> p->SetBackgroundColor();
> p->SetValue("pending");
> }
> int main() {
> TextCtrl1 tc1;
> TextCtrl2 tc2;
> TextCtrlAdaptor ta1(&tc1); Red(&ta1);
> TextCtrlAdaptor ta2(&tc2); Red(&ta2);
> }
>
> HTH,
> Arthur
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
On Fri, Nov 25, 2022, 17:45 Arthur O'Dwyer via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On Fri, Nov 25, 2022 at 9:49 AM Jason McKesson via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> There seems to be a broad lack of understanding of what this feature
>> is doing [...] The goal of the proposal is to be able to write a function
>> that takes
>> a single pointer to any type which inherits from *two or more* base
>> classes. And then use that pointer to access any function which is
>> available from any of these base classes. [...]
>>
>> Personally, I don't think this feature is worth doing. The
>> concept/template solution is adequate for those who need it, and it's
>> probably not something that is *broadly* useful. It's a niche feature.
>>
>
> Ditto, but my reason for not-adding-this-feature-to-C++ wouldn't be "it's
> niche" so much as "you don't need a core-language feature for this; you can
> already accomplish your goal just by using the existing language."
> Consider again the example [slightly simplified]:
>
> struct Control { virtual void SetBackgroundColor(); };
> struct TextEntry { virtual void SetValue(const char*); };
> struct TextCtrl1 : Control, TextEntry {};
> struct TextCtrl2 : Control, TextEntry {};
>
> void Red(??? *p) {
> p->SetBackgroundColor();
> p->SetValue("pending");
> }
> int main() {
> TextCtrl1 tc1;
> TextCtrl2 tc2;
> Red(&tc1);
> Red(&tc2);
> }
>
> Frederick's programming problem is: How do we write function `Red` so that
> it can accept either TextCtrl1 or TextCtrl2, and yet without making `Red`
> itself a function template? (We don't want `Red` to be a template because
> that would force its whole definition to be defined in a header file, which
> means recursively including all the stuff *used* in that definition, and
> so on.)
>
> Frederick's suggested solution was to just make it work by magic:
>
> void Red(__chimerical(Control, TextEntry) *p) {
> p->SetBackgroundColor(); // this means Control::SetBackgroundColor
> p->SetValue("pending"); // this means TextEntry::SetValue
> }
>
> However, this has problems. What would happen if the programmer wrote this
> code, and then later the maintainer of wxWidgets changed the definition of
> `Control` as follows?
> struct Control { void SetBackgroundColor(); void SetValue(const
> char*); };
> Now:
> p->SetValue("pending"); // Does this still mean TextEntry::SetValue?
> if so, why? Is it now ambiguous and ill-formed?
> This is related to the guideline "Don't inherit from types you don't
> control." This magic __chimerical(X,Y) becomes a method of "ad-hoc
> inheritance," by which any client programmer can introduce an
> inheritance-like dependency coupling the interfaces of any two classes X
> and Y.
> If you were proposing to implement the magic behavior by changing name
> lookup, then you also have to explain whether
> struct Control { void SetBackgroundColor(); void SetValue(); };
> would be treated similarly: Is `p->SetValue("pending")` still ambiguous,
> or do we do overload resolution among all the possible meanings of
> `SetValue` and pick the best match, or what?
>
> Anyway, you don't need to change the language for this. There are
> well-established programming techniques to accomplish your goal.
> The simplest would be to completely open-code it:
>
> // https://godbolt.org/z/Eq49Tqa9f
> void Red(void *p) {
> struct Dummy { virtual ~Dummy(); };
> auto asControl = [](void *p) {
> return dynamic_cast<Control*>(static_cast<Dummy*>(p));
> };
> auto asTextEntry = [](void *p) {
> return dynamic_cast<TextEntry*>(static_cast<Dummy*>(p));
> };
> asControl(p)->SetBackgroundColor();
> asTextEntry(p)->SetValue("pending");
> }
>
> This `Red` accepts a pointer to anything inheriting from both Control and
> TextEntry. Of course it also accepts a pointer to anything else (e.g. an
> `int`), and has UB if you do that; but, don't do that. You can check for
> that by tossing a simple template on the front end:
>
> template<class T>
> void Red(T *p) {
> static_assert(is_base_of_v<Control, T> && is_base_of_v<TextEntry,
> T>);
> RedImpl(p); // calls RedImpl(void*) as above
> }
>
> So that's the first option that exists today. The second option is to
> introduce your own "TextCtrl" interface, either as a base class (if you
> control the hierarchy at some point above `TextCtrl1` and `TextCtrl2`):
>
> // https://godbolt.org/z/nMhj1r13e
> struct TextCtrl : Control, TextEntry {};
> struct TextCtrl1 : TextCtrl {}; // CHANGED!!
> struct TextCtrl2 : TextCtrl {}; // CHANGED!!
> void Red(TextCtrl *p) {
> p->SetBackgroundColor();
> p->SetValue("pending");
> }
>
> or using type erasure (if you don't control the hierarchy):
>
> // https://godbolt.org/z/38qvM8T64
> struct TextCtrlPtr {
> TextEntry *t_;
> Control *c_;
> template<class T> TextCtrlPtr(T *p) : t_(p), c_(p) {}
> TextEntry *asTextEntry() const { return t_; }
> Control *asControl() const { return c_; }
> };
> void Red(TextCtrlPtr p) {
> p.asControl()->SetBackgroundColor();
> p.asTextEntry()->SetValue("pending");
> }
>
> Obviously you can polish `TextCtrlPtr` by making `SetBackgroundColor` and
> `SetValue` into member functions of it, if you really want to.
> For certain heavy-OOP-design-pattern-ish applications you might even want
> to introduce an adaptor class of your own. Here `TextCtrl1` and `TextCtrl2`
> are out of our control, but we can write a base class we *want* to use
> (`TextCtrl`) and then a concrete adaptor class `TextCtrlAdaptor` that can
> wrap a pointer to any type that implements both original base classes. This
> is the same as the type-erasure approach, except that the inheritance is
> explicit, and the `TextCtrlAdaptor` object actually IS-A `TextCtrl`. This
> is the first implementation where the `virtual` keyword really matters to
> the correctness of the example.
>
> struct TextCtrl1 : Control, TextEntry {};
> struct TextCtrl2 : Control, TextEntry {};
> struct TextCtrl : Control, TextEntry {};
> struct TextCtrlAdaptor : TextCtrl {
> TextEntry *t_;
> Control *c_;
> template<class T> explicit TextCtrlAdaptor(T *p) : t_(p), c_(p) {}
> void SetBackgroundColor() override { c_->SetBackgroundColor(); }
> // overrides virtual Control::SetBackgroundColor
> void SetValue(const char *s) override { t_->SetValue(s); } //
> overrides virtual TextEntry::SetValue
> };
> void Red(TextCtrl *p) {
> p->SetBackgroundColor();
> p->SetValue("pending");
> }
> int main() {
> TextCtrl1 tc1;
> TextCtrl2 tc2;
> TextCtrlAdaptor ta1(&tc1); Red(&ta1);
> TextCtrlAdaptor ta2(&tc2); Red(&ta2);
> }
>
> HTH,
> Arthur
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
Received on 2022-11-25 17:02:34