Date: Thu, 11 Apr 2024 14:59:02 -0400
On Thu, Apr 11, 2024 at 12:46 PM Phil Endecott via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> Jason McKesson <jmckesson_at_[hidden]>
> > There are times when you need to pass an object of a known type
> > through an intermediary that handles multiple such operations and
> > therefore doesn't need to know what type you're passing. Callbacks,
> > signal processing, things like that. In those cases, the sender and
> > the receiver really ought to be on the same page as to what is being
> > sent and received.
> >
> > *Precisely* what is being sent and received.
> >
> > I don't think it's good code to send an object of one type through
> > this interface, but have the receiver be blind to exactly what that
> > type was. Especially if that type uses *value semantics* the way `any`
> > does.
>
> Let me describe the situation in which I discovered a need for a
> std::any -like type that would allow me to cast to a base class:
>
> Consider a std::any used represent something like a "current selection" in
> a GUI application.
Do you really want to use `any` for that? Even if you want to
theoretically select "anything", you don't necessarily want to use
`any` for that. Why?
Because it's a value type.
If you select a string in the UI, and you want to change that string
itself, you can't. If the string is in a text box, what you really
want to do is change that text box. But changing a `std::string`
*cannot* change the text box because the `std::string` owns its
characters. The text box's string is a separate object, as is the
portion of that string which is selected.
So if you want to change the selection, you need to talk to the UI
element itself, not the "selection".
> I have a variety of abstract base classes ("interfaces") that the
> application's objects may implement, including whatever is currently
> stored in the selection.
>
> User interface objects (such as menu items) may want to be enabled or
> disabled depending on whether their action is appropriate for the current
> selected object, and then invoke the action through the base class.
>
> So for example I may have an abstract base class like this:
>
> class Alignable
> {
> public:
> enum Alignment { left, center, right };
> virtual void align(Alignment a) = 0;
> };
>
> Then I have concrete derived classes like this:
>
> class Paragraph: public Alignable, ....
>
> Then I have an alignment menu, which does something like this:
>
> align_menu.enabled =
> any_base_cast<Alignable>(¤t_selection) != nullptr;
>
> align_menu.left.on_tap =
> any_base_cast<Alignable>(¤t_selection) -> align(left);
> ...
>
> Contrary to your claim, I believe that it's useful and not "bad code"
> for code that receives an any-like type to not know its exact type,
> but rather to operate on it through base classes that it does know
> about.
While these kinds of interfaces have been fairly common, they're kind
of passe at this point. The modern consensus for such things is to go
with a component-style system, where containment is used rather than
inheritance. You have an object which can have a number of different
components, each of which implements some aspect of the whole. It's
way more scalable than base class interfaces, and it avoids the "fat
interface" problem handily. Also, it's not compile-time defined, so
you can actually change the nature of objects within the system just
by slapping a new component into them.
> In cases where all parties know "*precisely* what is being sent and
> received", a variant can be used.
That doesn't make sense. `variant` exposes everyone who touches the
object to every type it contains. The *entire point* of `any` is that
an intermediary doesn't need to know what it contains; only the source
and the destination know.
<std-proposals_at_[hidden]> wrote:
>
> Jason McKesson <jmckesson_at_[hidden]>
> > There are times when you need to pass an object of a known type
> > through an intermediary that handles multiple such operations and
> > therefore doesn't need to know what type you're passing. Callbacks,
> > signal processing, things like that. In those cases, the sender and
> > the receiver really ought to be on the same page as to what is being
> > sent and received.
> >
> > *Precisely* what is being sent and received.
> >
> > I don't think it's good code to send an object of one type through
> > this interface, but have the receiver be blind to exactly what that
> > type was. Especially if that type uses *value semantics* the way `any`
> > does.
>
> Let me describe the situation in which I discovered a need for a
> std::any -like type that would allow me to cast to a base class:
>
> Consider a std::any used represent something like a "current selection" in
> a GUI application.
Do you really want to use `any` for that? Even if you want to
theoretically select "anything", you don't necessarily want to use
`any` for that. Why?
Because it's a value type.
If you select a string in the UI, and you want to change that string
itself, you can't. If the string is in a text box, what you really
want to do is change that text box. But changing a `std::string`
*cannot* change the text box because the `std::string` owns its
characters. The text box's string is a separate object, as is the
portion of that string which is selected.
So if you want to change the selection, you need to talk to the UI
element itself, not the "selection".
> I have a variety of abstract base classes ("interfaces") that the
> application's objects may implement, including whatever is currently
> stored in the selection.
>
> User interface objects (such as menu items) may want to be enabled or
> disabled depending on whether their action is appropriate for the current
> selected object, and then invoke the action through the base class.
>
> So for example I may have an abstract base class like this:
>
> class Alignable
> {
> public:
> enum Alignment { left, center, right };
> virtual void align(Alignment a) = 0;
> };
>
> Then I have concrete derived classes like this:
>
> class Paragraph: public Alignable, ....
>
> Then I have an alignment menu, which does something like this:
>
> align_menu.enabled =
> any_base_cast<Alignable>(¤t_selection) != nullptr;
>
> align_menu.left.on_tap =
> any_base_cast<Alignable>(¤t_selection) -> align(left);
> ...
>
> Contrary to your claim, I believe that it's useful and not "bad code"
> for code that receives an any-like type to not know its exact type,
> but rather to operate on it through base classes that it does know
> about.
While these kinds of interfaces have been fairly common, they're kind
of passe at this point. The modern consensus for such things is to go
with a component-style system, where containment is used rather than
inheritance. You have an object which can have a number of different
components, each of which implements some aspect of the whole. It's
way more scalable than base class interfaces, and it avoids the "fat
interface" problem handily. Also, it's not compile-time defined, so
you can actually change the nature of objects within the system just
by slapping a new component into them.
> In cases where all parties know "*precisely* what is being sent and
> received", a variant can be used.
That doesn't make sense. `variant` exposes everyone who touches the
object to every type it contains. The *entire point* of `any` is that
an intermediary doesn't need to know what it contains; only the source
and the destination know.
Received on 2024-04-11 18:59:14