Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c++ > #91307 > unrolled thread

What is visitor pattern?

Started bywij <wyniijj5@gmail.com>
First post2023-08-20 06:55 -0700
Last post2023-08-29 19:27 +0200
Articles 20 on this page of 53 — 8 participants

Back to article view | Back to comp.lang.c++


Contents

  What is visitor pattern? wij <wyniijj5@gmail.com> - 2023-08-20 06:55 -0700
    Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-20 07:22 -0700
      Re: What is visitor pattern? wij <wyniijj5@gmail.com> - 2023-08-20 19:24 -0700
      Re: What is visitor pattern? wij <wyniijj5@gmail.com> - 2023-08-27 13:13 -0700
        Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-28 00:13 -0700
          Re: What is visitor pattern? wij <wyniijj5@gmail.com> - 2023-08-28 23:18 -0700
    Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-20 14:12 -0400
      Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-20 13:34 -0700
        Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-20 18:01 -0400
          Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-20 21:12 -0700
            Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-21 06:53 +0000
              Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-21 00:10 -0700
                Re: What is visitor pattern? Tim Rentsch <tr.17687@z991.linuxsc.com> - 2023-08-26 21:26 -0700
                  Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-26 22:57 -0700
                    Re: What is visitor pattern? Tim Rentsch <tr.17687@z991.linuxsc.com> - 2023-08-28 15:07 -0700
            Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-21 22:55 -0400
              Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-21 21:56 -0700
                Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-24 01:38 -0400
                  Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-24 04:45 -0700
                    Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-24 23:11 -0400
                      Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-25 07:52 -0700
                        Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-26 00:55 -0400
      Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-21 06:18 +0200
        Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-21 23:28 -0400
          Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-22 05:47 +0200
            Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-22 00:11 -0400
              Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-22 14:30 +0200
                Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-24 01:56 -0400
                  Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-24 09:15 +0200
                    Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-24 20:02 -0400
                      Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-25 05:13 +0200
                        Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-26 01:05 -0400
                          Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 09:24 +0200
                            Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-26 08:01 +0000
                              Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 11:29 +0200
                                Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-26 15:00 +0000
                                  Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 17:08 +0200
                                    Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-26 15:15 +0000
                                      Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 17:24 +0200
                                        Re: What is visitor pattern? Muttley@dastardlyhq.com - 2023-08-26 15:36 +0000
                                          Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 17:39 +0200
                                      Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-26 17:26 +0200
                                  Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-29 21:44 -0400
                            Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-29 22:14 -0400
                              Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-30 06:13 +0200
                                Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-30 23:18 -0400
    Re: What is visitor pattern? "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2023-08-21 01:17 +0200
      Re: What is visitor pattern? "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2023-08-21 01:24 +0200
    Re: What is visitor pattern? Michael S <already5chosen@yahoo.com> - 2023-08-25 05:21 -0700
      Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-25 14:33 +0200
      Re: What is visitor pattern? Öö Tiib <ootiib@hot.ee> - 2023-08-25 08:28 -0700
      Re: What is visitor pattern? Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> - 2023-08-26 01:17 -0400
    Re: What is visitor pattern? Bonita Montero <Bonita.Montero@gmail.com> - 2023-08-29 19:27 +0200

Page 1 of 3  [1] 2 3  Next page →


#91307 — What is visitor pattern?

Fromwij <wyniijj5@gmail.com>
Date2023-08-20 06:55 -0700
SubjectWhat is visitor pattern?
Message-ID<331cb164-e6b3-4f9b-af8d-ee933abd79b7n@googlegroups.com>
I found a C++ example https://refactoring.guru/design-patterns/visitor/cpp/example
 but I still don't understand what it tries to say and the example code don't compile. 
Can anyone explain the visitor pattern or  better webpages?
probably making the example  compile may help, thanks in advance.

[toc] | [next] | [standalone]


#91308

FromÖö Tiib <ootiib@hot.ee>
Date2023-08-20 07:22 -0700
Message-ID<ea1ee058-e460-4e34-a001-4e9375f1a7can@googlegroups.com>
In reply to#91307
On Sunday, 20 August 2023 at 16:55:56 UTC+3, wij wrote:
> I found a C++ example https://refactoring.guru/design-patterns/visitor/cpp/example 
> but I still don't understand what it tries to say and the example code don't compile. 

Racoon with buzzsaw indicates self-irony.
Example that does not compile is smelly ... OTOH it perhaps just lacks #includes.

#include <array>
#include <iostream>
#include <string>

> Can anyone explain the visitor pattern or better webpages? 

Maybe wikipedia?
<https://en.wikipedia.org/wiki/Visitor_pattern>

> probably making the example compile may help, thanks in advance.

Unsure ... people's mind does not grasp double dispatch too well.  

[toc] | [prev] | [next] | [standalone]


#91315

Fromwij <wyniijj5@gmail.com>
Date2023-08-20 19:24 -0700
Message-ID<a374236f-a043-4521-b551-b489764a69f9n@googlegroups.com>
In reply to#91308
On Sunday, August 20, 2023 at 10:22:48 PM UTC+8, Öö Tiib wrote:
> On Sunday, 20 August 2023 at 16:55:56 UTC+3, wij wrote: 
> > I found a C++ example https://refactoring.guru/design-patterns/visitor/cpp/example 
> > but I still don't understand what it tries to say and the example code don't compile.
> Racoon with buzzsaw indicates self-irony. 
> Example that does not compile is smelly ... OTOH it perhaps just lacks #includes. 
> 
> #include <array> 
> #include <iostream> 
> #include <string>
> > Can anyone explain the visitor pattern or better webpages?
> Maybe wikipedia? 
> <https://en.wikipedia.org/wiki/Visitor_pattern>
> > probably making the example compile may help, thanks in advance.
> Unsure ... people's mind does not grasp double dispatch too well.

Thank. When I saw "array" I habitually translate to "vector", so the 2nd
parameter in "std::array<const Component *, 2>" looked strange to me!!!
 
From the compiled example, it looked to me that 'Visitor pattern' simply means
a class family (Component) has a virtual member, say Accept(..) in the 'visitor' 
case, whose definition is defined by another class member (e.g. Visit::xxx)... no more.

[toc] | [prev] | [next] | [standalone]


#91451

Fromwij <wyniijj5@gmail.com>
Date2023-08-27 13:13 -0700
Message-ID<8b34895c-0c2f-429b-b35f-4a2d293f62abn@googlegroups.com>
In reply to#91308
On Sunday, August 20, 2023 at 10:22:48 PM UTC+8, Öö Tiib wrote:
>...
> Unsure ... people's mind does not grasp double dispatch too well.

This is because people's mind is not depending on such academical concept 'double dispatch'.
I think many people might be interested in the visitor pattern. So, this is my
understanding from a usecase of virtual class, e.g.

class class Element {
  public:
    virtual void act(Actor &a) = 0;   // act==accept
};
    
class ThingA : public Element { /* omitted */ }
class ThingB : public Element { /* omitted */ }
class ThingC : public Element { /* omitted */ }
    
int main() { 
 // The usecase of Element family should normally for something like the list[] below:
 Element *list[3]= { new ThingA, new ThingB, new ThingC };

 // By now, because only the act(..) member is available, the common usage
 // pattern is like the following tow 'for' loops:
    
 for(int i=0; i<3; ++i) { // Ask elements in list[] to do someting (SomeAct1)
   list[i]->act(SomeAct1);
 }
  
 for(int i=0; i<3; ++i) { // Ask elements in list[] to do someting (SomeAct2)
   list[i]->act(SomeAct2);
 }
  
 // That something SomeAct1,SomeAct2 are derived from Visitor and defined by user
 // This is main point of Visitor Pattern, separates the implement of Element and SomeAct.
}

[toc] | [prev] | [next] | [standalone]


#91455

FromÖö Tiib <ootiib@hot.ee>
Date2023-08-28 00:13 -0700
Message-ID<6006e16c-837a-4ab9-9cb8-6c5e21531a01n@googlegroups.com>
In reply to#91451
On Sunday, 27 August 2023 at 23:13:36 UTC+3, wij wrote:
> On Sunday, August 20, 2023 at 10:22:48 PM UTC+8, Öö Tiib wrote: 
> >... 
> > Unsure ... people's mind does not grasp double dispatch too well. 
> 
> This is because people's mind is not depending on such academical concept 'double dispatch'. 
> I think many people might be interested in the visitor pattern. So, this is my 
> understanding from a usecase of virtual class, e.g. 
> 
Yes, your understanding of use-case is correct, just that the data hierarchy
of a program is usually not a flat list. It is usually something like tree or directed
graph without cycles. 

For example list of devices (of different types), every device has list of parameters 
(of different types) and list of optional submodules/peripherals connected (of 
different types) that may also have options and parameters. 

Also visitor is usually used when it is not about operation on one element but needs
to do some operation over all that tree or some subtree of it.

So for above example about devices the visitors can be like that ...
One visitor wants to visit all that data to find out total current and maximum power
consumption of configuration of devices.    
Second visitor wants to figure on what temperatures the devices and peripherals
operate.
Third wants to find what kind of backup devices need to be in warehouse for
urgently fix the setup.
Fourth wants to store all current configurable parameter values of the set.
Fifth wants to compose command sequence to turn all the devices off in
fastest and safest manner.

[toc] | [prev] | [next] | [standalone]


#91465

Fromwij <wyniijj5@gmail.com>
Date2023-08-28 23:18 -0700
Message-ID<0469bc48-41a6-4824-8cd1-9b100526e3a1n@googlegroups.com>
In reply to#91455
On Monday, August 28, 2023 at 3:14:04 PM UTC+8, Öö Tiib wrote:
> On Sunday, 27 August 2023 at 23:13:36 UTC+3, wij wrote: 
> > On Sunday, August 20, 2023 at 10:22:48 PM UTC+8, Öö Tiib wrote: 
> > >... 
> > > Unsure ... people's mind does not grasp double dispatch too well. 
> > 
> > This is because people's mind is not depending on such academical concept 'double dispatch'. 
> > I think many people might be interested in the visitor pattern. So, this is my 
> > understanding from a usecase of virtual class, e.g. 
> >
> Yes, your understanding of use-case is correct, just that the data hierarchy 
> of a program is usually not a flat list. It is usually something like tree or directed 
> graph without cycles. 
> 
> For example list of devices (of different types), every device has list of parameters 
> (of different types) and list of optional submodules/peripherals connected (of 
> different types) that may also have options and parameters. 
> 
> Also visitor is usually used when it is not about operation on one element but needs 
> to do some operation over all that tree or some subtree of it. 
> 
> So for above example about devices the visitors can be like that ... 
> One visitor wants to visit all that data to find out total current and maximum power 
> consumption of configuration of devices. 
> Second visitor wants to figure on what temperatures the devices and peripherals 
> operate. 
> Third wants to find what kind of backup devices need to be in warehouse for 
> urgently fix the setup. 
> Fourth wants to store all current configurable parameter values of the set. 
> Fifth wants to compose command sequence to turn all the devices off in 
> fastest and safest manner.

I am writing a stave music parser program. Stave music is actually a small 
script language similar to average programming language, there is conditional
control flow and  stave's 'environment' need to be traced. I am happy the 
visitor pattern is learnt and used immediately. The parser part is reduced form
complex 10k lines of codes to 2k simple codes and done fast, just these several days.

In my case, the list[] array structure is enough since this part of program is
just a parser. The tree,graph,... structure of the 'Element' is left for user
to interpret (the visitor can be a printer or different sound devices).

[toc] | [prev] | [next] | [standalone]


#91309

FromPavel <pauldontspamtolk@removeyourself.dontspam.yahoo>
Date2023-08-20 14:12 -0400
Message-ID<uIsEM.123835$QQFb.6082@fx38.iad>
In reply to#91307
wij wrote:
> I found a C++ example https://refactoring.guru/design-patterns/visitor/cpp/example
>   but I still don't understand what it tries to say and the example code don't compile.
> Can anyone explain the visitor pattern or  better webpages?
> probably making the example  compile may help, thanks in advance.
> 
This seems better than Wikipedia:

https://www.gofpattern.com/behavioral/patterns/visitor-pattern.php

Still I think both are slightly over-complicated / non-focused in how 
they describe the original problem.

I would describe it follows:

PROBLEM

1. You have a collection or a stream of objects of different types thru 
which you can iterate. Given [a ref to] any such object, you are able to 
compute its type and/or call a virtual method.
2. You want to iterate thru these objects and do some op1 with each of 
them.  Op1 depends on the object type (whole or part of this dependency 
could be expressible by only calling virtual method(s) on a [ref to] 
common base type).
3. You will likely need to implement other similar operations, say, op2, 
op3 etc

OO SOLUTION

1. Ensure all objects have a common base class
2. Add op1 (and later op2...) as a virtual method at the base class and 
implement it in all most derived classes (or, if you are lucky, there 
will be already some internal nodes in the hierarchy where you can plant 
common logic for close siblings -- or you have to expand the hierarchy 
to add such internal nodes)

OO SOLUTION analysis

Advantage: all dispatching is done for you by virtual method machinery
Drawback: need to change lots of classes every time you add one op, even 
if you could implement op1,op2... using only public API of the object 
type (maybe there are not many differences between how the op should 
behave on the objects of all those many derived classes (but there are 
some)).

VISITOR SOLUTION

1. Ensure all objects have a common base class (say, it's B)
2. create an abstract class Visitor with an abstract method doOp with an 
arg of type [ref to] B.
3. add a single method to B, e.g. acceptVisit that takes [ref to] 
Visitor and calls doOp on it. NOTE: only one such method is needed, no 
matter how many ops you are adding.
4. for every op, create a derived class of Visitor (e.g. Op1Visitor, 
Op2Visitor) e.g. and implement doOp() method.

VISITOR SOLUTION analysis:

Advantages:
1. For every new op, you have to add only one class with one virtual method
2. All logic pertaining to the new op is in one place (doOp method).
Drawbacks:
1. There could be need in some dispatching logic in doOp() method
2. All doOp() methods may have to change if a new type is added

EXAMPLE

Enhance formatting of an XML document by adding a red border around the 
current depiction of only some elements, depending on the element type.

CONCLUSION

Consider using Visitor when the hierarchy of object types is stable and 
an op to add is not a natural extension of the type API that you would 
want to implement anyway.

HTH
-Pavel

[toc] | [prev] | [next] | [standalone]


#91310

FromÖö Tiib <ootiib@hot.ee>
Date2023-08-20 13:34 -0700
Message-ID<6df46174-6867-4577-a73d-0fb70d6a25e8n@googlegroups.com>
In reply to#91309
On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote:
> wij wrote: 
> > I found a C++ example https://refactoring.guru/design-patterns/visitor/cpp/example 
> > but I still don't understand what it tries to say and the example code don't compile. 
> > Can anyone explain the visitor pattern or better webpages? 
> > probably making the example compile may help, thanks in advance. 
> >
> This seems better than Wikipedia: 
> 
> https://www.gofpattern.com/behavioral/patterns/visitor-pattern.php 
> 
> Still I think both are slightly over-complicated / non-focused in how 
> they describe the original problem. 
> 
> I would describe it follows: 
> 
> PROBLEM 
> 
> 1. You have a collection or a stream of objects of different types thru 
> which you can iterate. Given [a ref to] any such object, you are able to 
> compute its type and/or call a virtual method. 
> 2. You want to iterate thru these objects and do some op1 with each of 
> them. Op1 depends on the object type (whole or part of this dependency 
> could be expressible by only calling virtual method(s) on a [ref to] 
> common base type). 
> 3. You will likely need to implement other similar operations, say, op2, 
> op3 etc 
> 
> OO SOLUTION 
> 
> 1. Ensure all objects have a common base class 
> 2. Add op1 (and later op2...) as a virtual method at the base class and 
> implement it in all most derived classes (or, if you are lucky, there 
> will be already some internal nodes in the hierarchy where you can plant 
> common logic for close siblings -- or you have to expand the hierarchy 
> to add such internal nodes) 
> 
> OO SOLUTION analysis 
> 
> Advantage: all dispatching is done for you by virtual method machinery 
> Drawback: need to change lots of classes every time you add one op, even 
> if you could implement op1,op2... using only public API of the object 
> type (maybe there are not many differences between how the op should 
> behave on the objects of all those many derived classes (but there are 
> some)). 
> 
> VISITOR SOLUTION 
> 

That is illustration of why I said "people's mind does not grasp double
dispatch too well."

> 1. Ensure all objects have a common base class (say, it's B) 

Not needed.

> 2. create an abstract class Visitor with an abstract method doOp with an 
> arg of type [ref to] B. 

Nope, pile of overloads of doOp one for each type processed. Otherwise
how you process different types differently?

> 3. add a single method to B, e.g. acceptVisit that takes [ref to] 
> Visitor and calls doOp on it. NOTE: only one such method is needed, no 
> matter how many ops you are adding. 

Not enough. Every type processed has to have that copy-paste
acceptVisit method. Otherwise double dispatch does not work.

> 4. for every op, create a derived class of Visitor (e.g. Op1Visitor, 
> Op2Visitor) e.g. and implement doOp() method. 
> 
> VISITOR SOLUTION analysis: 
> 
> Advantages: 
> 1. For every new op, you have to add only one class with one virtual method 
> 2. All logic pertaining to the new op is in one place (doOp method). 
> Drawbacks: 
> 1. There could be need in some dispatching logic in doOp() method 
> 2. All doOp() methods may have to change if a new type is added 
> 
> EXAMPLE 
> 
> Enhance formatting of an XML document by adding a red border around the 
> current depiction of only some elements, depending on the element type. 
> 
> CONCLUSION 
> 
> Consider using Visitor when the hierarchy of object types is stable and 
> an op to add is not a natural extension of the type API that you would 
> want to implement anyway. 
> 
> HTH 
> -Pavel

[toc] | [prev] | [next] | [standalone]


#91311

FromPavel <pauldontspamtolk@removeyourself.dontspam.yahoo>
Date2023-08-20 18:01 -0400
Message-ID<9ea1f202-4524-03b8-f003-e11b7fa411e7@removeyourself.dontspam.yahoo>
In reply to#91310
Öö Tiib wrote:
> On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote:
>> wij wrote:
>>> I found a C++ example https://refactoring.guru/design-patterns/visitor/cpp/example
>>> but I still don't understand what it tries to say and the example code don't compile.
>>> Can anyone explain the visitor pattern or better webpages?
>>> probably making the example compile may help, thanks in advance.
>>>
>> This seems better than Wikipedia:
>>
>> https://www.gofpattern.com/behavioral/patterns/visitor-pattern.php
>>
>> Still I think both are slightly over-complicated / non-focused in how
>> they describe the original problem.
>>
>> I would describe it follows:
>>
>> PROBLEM
>>
>> 1. You have a collection or a stream of objects of different types thru
>> which you can iterate. Given [a ref to] any such object, you are able to
>> compute its type and/or call a virtual method.
>> 2. You want to iterate thru these objects and do some op1 with each of
>> them. Op1 depends on the object type (whole or part of this dependency
>> could be expressible by only calling virtual method(s) on a [ref to]
>> common base type).
>> 3. You will likely need to implement other similar operations, say, op2,
>> op3 etc
>>
>> OO SOLUTION
>>
>> 1. Ensure all objects have a common base class
>> 2. Add op1 (and later op2...) as a virtual method at the base class and
>> implement it in all most derived classes (or, if you are lucky, there
>> will be already some internal nodes in the hierarchy where you can plant
>> common logic for close siblings -- or you have to expand the hierarchy
>> to add such internal nodes)
>>
>> OO SOLUTION analysis
>>
>> Advantage: all dispatching is done for you by virtual method machinery
>> Drawback: need to change lots of classes every time you add one op, even
>> if you could implement op1,op2... using only public API of the object
>> type (maybe there are not many differences between how the op should
>> behave on the objects of all those many derived classes (but there are
>> some)).
>>
>> VISITOR SOLUTION
>>
> 
> That is illustration of why I said "people's mind does not grasp double
> dispatch too well."
> 
>> 1. Ensure all objects have a common base class (say, it's B)
> 
> Not needed.
> 
>> 2. create an abstract class Visitor with an abstract method doOp with an
>> arg of type [ref to] B.
> 
> Nope, pile of overloads of doOp one for each type processed.
This would render the pattern useless as has no advantage over adding a 
virtual method to do the op. The rationale of the pattern is to permit 
coding one op in one place.

> Otherwise
> how you process different types differently?
I explained this in the problem statement. "Given [a ref to] any such 
object, you are able to
compute its type and/or call a virtual method". In practice this can be 
anything, examples (non-exhaustive): a enum type discriminator, virtual 
cast method, (in modern languages), pattern matching.

> 
>> 3. add a single method to B, e.g. acceptVisit that takes [ref to]
>> Visitor and calls doOp on it. NOTE: only one such method is needed, no
>> matter how many ops you are adding.
> 
> Not enough. Every type processed has to have that copy-paste
> acceptVisit method. Otherwise double dispatch does not work.
Double dispatch works just fine without copy-pasting. Dispatch #1 (by 
the operation type) is done by the virtual method doOp. Dispatch #2 (by 
the object type) is done by using the actual type to call specific 
methods and/or calling virtual methods, on the object ref.

> 
>> 4. for every op, create a derived class of Visitor (e.g. Op1Visitor,
>> Op2Visitor) e.g. and implement doOp() method.
>>
>> VISITOR SOLUTION analysis:
>>
>> Advantages:
>> 1. For every new op, you have to add only one class with one virtual method
>> 2. All logic pertaining to the new op is in one place (doOp method).
>> Drawbacks:
>> 1. There could be need in some dispatching logic in doOp() method
>> 2. All doOp() methods may have to change if a new type is added
>>
>> EXAMPLE
>>
>> Enhance formatting of an XML document by adding a red border around the
>> current depiction of only some elements, depending on the element type.
>>
>> CONCLUSION
>>
>> Consider using Visitor when the hierarchy of object types is stable and
>> an op to add is not a natural extension of the type API that you would
>> want to implement anyway.
>>
>> HTH
>> -Pavel

[toc] | [prev] | [next] | [standalone]


#91316

FromÖö Tiib <ootiib@hot.ee>
Date2023-08-20 21:12 -0700
Message-ID<c772c32e-d0e0-4199-b173-c64e3535159cn@googlegroups.com>
In reply to#91311
On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote:
> Öö Tiib wrote: 
> > On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote: 
> > 
> >> 1. Ensure all objects have a common base class (say, it's B) 
> > 
> > Not needed. 
> > 
> >> 2. create an abstract class Visitor with an abstract method doOp with an 
> >> arg of type [ref to] B. 
> > 
> > Nope, pile of overloads of doOp one for each type processed.
> 
> This would render the pattern useless as has no advantage over adding a 
> virtual method to do the op. The rationale of the pattern is to permit 
> coding one op in one place.
> 
The Visitor class is that "one place"; doing same operation lets say printing
 of 50 types in one function results with huge function that is headache
not advantage. Separate functions are testable and maintainable.

> > Otherwise 
> > how you process different types differently?
> I explained this in the problem statement. "Given [a ref to] any such
> object, you are able to
> compute its type and/or call a virtual method". 

IOW you do huge switch typeid ... case static_ cast or have the virtual 
method (over what we were "gaining advantage") already there?
But we need no bloat from that pattern to do neither. These were the
things we wanted to get rid of. 

> In practice this can be 
> anything, examples (non-exhaustive): a enum type discriminator, virtual 
> cast method, (in modern languages), pattern matching.
> > 
> >> 3. add a single method to B, e.g. acceptVisit that takes [ref to] 
> >> Visitor and calls doOp on it. NOTE: only one such method is needed, no 
> >> matter how many ops you are adding. 
> > 
> > Not enough. Every type processed has to have that copy-paste 
> > acceptVisit method. Otherwise double dispatch does not work.
> 
> Double dispatch works just fine without copy-pasting. Dispatch #1 (by 
> the operation type) is done by the virtual method doOp. Dispatch #2 (by 
> the object type) is done by using the actual type to call specific 
> methods and/or calling virtual methods, on the object ref.

Of course huge switch or calling virtual method (or now even multiple)
work without the whole pattern. Read up on "double dispatch".
It is tricky like I already said.

[toc] | [prev] | [next] | [standalone]


#91318

FromMuttley@dastardlyhq.com
Date2023-08-21 06:53 +0000
Message-ID<ubv1ke$1q0e7$1@dont-email.me>
In reply to#91316
On Sun, 20 Aug 2023 21:12:41 -0700 (PDT)
=?UTF-8?B?w5bDtiBUaWli?= <ootiib@hot.ee> wrote:
>On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote:
>> object, you are able to
>> compute its type and/or call a virtual method".=20
>
>IOW you do huge switch typeid ... case static_ cast or have the virtual=20
>method (over what we were "gaining advantage") already there?
>But we need no bloat from that pattern to do neither. These were the
>things we wanted to get rid of.=20

IME most design patterns are algorithms any half decent programmer could
have thought up by themselves and/or have been around for half a century
already but they simply got repackaged for a new generation.

Old wine -> new bottle.

[toc] | [prev] | [next] | [standalone]


#91319

FromÖö Tiib <ootiib@hot.ee>
Date2023-08-21 00:10 -0700
Message-ID<01a599fc-0731-45e8-80d2-6f8eb387821dn@googlegroups.com>
In reply to#91318
On Monday, 21 August 2023 at 09:53:20 UTC+3, Mut...@dastardlyhq.com wrote:
> On Sun, 20 Aug 2023 21:12:41 -0700 (PDT) 
> =Öö Tiib <oot...@hot.ee> wrote: 
> >On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote: 
> >> object, you are able to
> >> compute its type and/or call a virtual method". 
> > 
> >IOW you do huge switch typeid ... case static_ cast or have the virtual
> >method (over what we were "gaining advantage") already there? 
> >But we need no bloat from that pattern to do neither. These were the
> >things we wanted to get rid of.
> 
> IME most design patterns are algorithms any half decent programmer could 
> have thought up by themselves and/or have been around for half a century 
> already but they simply got repackaged for a new generation. 
> 
> Old wine -> new bottle.
> 
Double dispatch was first perhaps described by Dan Ingalls in seventies in
context of SmallTalk ... so you are correct that half a century old. 
There seems to be no difference how to package, too lot of people do not
get the idea of it (but can be assigned to maintain) and result is either
broken product or the nuisance what it got rid of edited back in.
  

[toc] | [prev] | [next] | [standalone]


#91448

FromTim Rentsch <tr.17687@z991.linuxsc.com>
Date2023-08-26 21:26 -0700
Message-ID<86v8d1xg92.fsf@linuxsc.com>
In reply to#91319
Tiib <ootiib@hot.ee> writes:

> Double dispatch was first perhaps described by Dan Ingalls in
> seventies

Do you have a reference for that?

> in context of SmallTalk ... [...]

It's Smalltalk, not SmallTalk.

[toc] | [prev] | [next] | [standalone]


#91449

FromÖö Tiib <ootiib@hot.ee>
Date2023-08-26 22:57 -0700
Message-ID<5c32396d-2447-4079-9926-4e4dd3d4df9an@googlegroups.com>
In reply to#91448
On Sunday, 27 August 2023 at 07:26:52 UTC+3, Tim Rentsch wrote:
> Tiib <oot...@hot.ee> writes: 
> 
> > Double dispatch was first perhaps described by Dan Ingalls in 
> > seventies 
> 
> Do you have a reference for that? 
> 
No ... GitHub contains one from eighties;  can be I misremembered:
<https://algoritmos-iii.github.io/assets/bibliografia/simple-technique-for-handling-multiple-polymorphism.pdf>

> > in context of SmallTalk ... [...] 
> 
> It's Smalltalk, not SmallTalk.

[toc] | [prev] | [next] | [standalone]


#91460

FromTim Rentsch <tr.17687@z991.linuxsc.com>
Date2023-08-28 15:07 -0700
Message-ID<86a5uayg62.fsf@linuxsc.com>
In reply to#91449
Tiib <ootiib@hot.ee> writes:

> On Sunday, 27 August 2023 at 07:26:52 UTC+3, Tim Rentsch wrote:
>
>> Tiib <oot...@hot.ee> writes:
>>
>>> Double dispatch was first perhaps described by Dan Ingalls in
>>> seventies
>>
>> Do you have a reference for that?
>
> No ... GitHub contains one from eighties;  can be I misremembered:
> <https://algoritmos-iii.github.io/assets/bibliografia/
>  simple-technique-for-handling-multiple-polymorphism.pdf>

Thank you, that's a good reference.

[toc] | [prev] | [next] | [standalone]


#91328

FromPavel <pauldontspamtolk@removeyourself.dontspam.yahoo>
Date2023-08-21 22:55 -0400
Message-ID<msVEM.440194$xMqa.182640@fx12.iad>
In reply to#91316
Öö Tiib wrote:
> On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote:
>> Öö Tiib wrote:
>>> On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote:
>>>
>>>> 1. Ensure all objects have a common base class (say, it's B)
>>>
>>> Not needed.
>>>
>>>> 2. create an abstract class Visitor with an abstract method doOp with an
>>>> arg of type [ref to] B.
>>>
>>> Nope, pile of overloads of doOp one for each type processed.
>>
>> This would render the pattern useless as has no advantage over adding a
>> virtual method to do the op. The rationale of the pattern is to permit
>> coding one op in one place.
>>
> The Visitor class is that "one place"; doing same operation lets say printing
>   of 50 types in one function results with huge function that is headache
> not advantage. Separate functions are testable and maintainable.
> 
>>> Otherwise
>>> how you process different types differently?
>> I explained this in the problem statement. "Given [a ref to] any such
>> object, you are able to
>> compute its type and/or call a virtual method".
> 
> IOW you do huge switch typeid ... case static_ cast or have the virtual
> method (over what we were "gaining advantage") already there?
Even a switch with 30 cases is no worse than 30 methods although switch 
is not always necessary (try to read the above instead of imagining).

The advantage is in having this logic in one place. If your visitor does 
a coherent thing (which it is supposed to do; else why is it a single 
visitor, there could be and usually would be common code for all or many 
visited). The most pliant cases (that are exactly those gaining most 
from applying a visitor pattern) may get by object virtual methods. 
Simple example:

class BorderingVisitor: public AbstractVisitor {
public:
     void doOp(const Shape& s) const override {
         if (s.area() > 25) {
             dc.drawRedRectangle(s.boundingRectangle().increaseBy(0.1));
         }
     }
private:
     DrawingContext dc;
};

If we wanted a green border around a shape of any of 10 classes derived 
from convex polygon (ConvexTriangle, ConvexQuadrilateral etc), red 
border around a shape of any of 10 classes derived from Oval and no 
border around any of 30 other zero-area shape classes, all we need to do 
is to change doOp to:

void BorderingVisitor::doOp(const Shape& s) const {
  if (s.area() > 25) {
   const Rectangle br{s.boundingRectangle().increaseBy(0.1)};
   if (const ConvexPolygon* cp = s.castToConvexPolygon()) {
    dc.drawGreenRectangle(br);
   }
   else {
    assert(!!s.castToOval());
    dc.drawRedRectangle(br);
   }
  }
}

And this is the complete code for adding a new operation (virtual cast 
is to be only defined once for all operations)

> But we need no bloat from that pattern to do neither. These were the
> things we wanted to get rid of.

Care to show us the code for your "non bloated" "one-place" visitor 
class for the above 2 problems? (like I did, you can omit the code that 
is common for all operations).

> 
>> In practice this can be
>> anything, examples (non-exhaustive): a enum type discriminator, virtual
>> cast method, (in modern languages), pattern matching.
>>>
>>>> 3. add a single method to B, e.g. acceptVisit that takes [ref to]
>>>> Visitor and calls doOp on it. NOTE: only one such method is needed, no
>>>> matter how many ops you are adding.
>>>
>>> Not enough. Every type processed has to have that copy-paste
>>> acceptVisit method. Otherwise double dispatch does not work.
>>
>> Double dispatch works just fine without copy-pasting. Dispatch #1 (by
>> the operation type) is done by the virtual method doOp. Dispatch #2 (by
>> the object type) is done by using the actual type to call specific
>> methods and/or calling virtual methods, on the object ref.
> 
> Of course huge switch or calling virtual method (or now even multiple)
> work without the whole pattern. Read up on "double dispatch".
> It is tricky like I already said.
> 
Surely you are the most competent software engineer understanding 
multiple dispatch. Care to point out a "huge switch" in either of the 
above 2 examples?

[toc] | [prev] | [next] | [standalone]


#91333

FromÖö Tiib <ootiib@hot.ee>
Date2023-08-21 21:56 -0700
Message-ID<942afb61-7d90-4251-87a3-18cf4c68fa03n@googlegroups.com>
In reply to#91328
On Tuesday, 22 August 2023 at 05:55:32 UTC+3, Pavel wrote:
> Öö Tiib wrote: 
> > On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote: 
> >> Öö Tiib wrote: 
> >>> On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote: 
> >>> 
> >>>> 1. Ensure all objects have a common base class (say, it's B) 
> >>> 
> >>> Not needed. 
> >>> 
> >>>> 2. create an abstract class Visitor with an abstract method doOp with an 
> >>>> arg of type [ref to] B. 
> >>> 
> >>> Nope, pile of overloads of doOp one for each type processed. 
> >> 
> >> This would render the pattern useless as has no advantage over adding a 
> >> virtual method to do the op. The rationale of the pattern is to permit 
> >> coding one op in one place. 
> >> 
> > The Visitor class is that "one place"; doing same operation lets say printing 
> > of 50 types in one function results with huge function that is headache 
> > not advantage. Separate functions are testable and maintainable. 
> > 
> >>> Otherwise 
> >>> how you process different types differently? 
> >> I explained this in the problem statement. "Given [a ref to] any such 
> >> object, you are able to 
> >> compute its type and/or call a virtual method". 
> > 
> > IOW you do huge switch typeid ... case static_ cast or have the virtual 
> > method (over what we were "gaining advantage") already there?
> 
> Even a switch with 30 cases is no worse than 30 methods although switch 
> is not always necessary (try to read the above instead of imagining). 
> 
Visitor is typically used to search, filter, draw or print whole data object
hierarchy, convert to JSON to XML or to tree in GUI. If whole data hierarchy
is small then you get 30 cases. Switch case is worse than virtual methods.
Think why  virtual methods were added? To get rid of switch cases over type.
Visitor is not meant as excuse to add those back, otherwise just use
the switch case in function, do not manufacture visitors that do not use
double dispatch for confusion. 

> The advantage is in having this logic in one place. If your visitor does 
> a coherent thing (which it is supposed to do; else why is it a single 
> visitor, there could be and usually would be common code for all or many 
> visited). The most pliant cases (that are exactly those gaining most 
> from applying a visitor pattern) may get by object virtual methods. 
> Simple example: 
> 
> If we wanted a green border around a shape of any of 10 classes derived 
> from convex polygon (ConvexTriangle, ConvexQuadrilateral etc), red 
> border around a shape of any of 10 classes derived from Oval and no 
> border around any of 30 other zero-area shape classes, all we need to do 
> is to change doOp to: 
> 

Unused cp in your code?
Can't post sane code from gg ... but also don't want to install newsreaders
to all devices I use. It is drawing border to any shape and so it is unclear
why you need visitor. Logical one place is either DrwawingContext's or
Shape's nonvirtual method or free function:

void Shape::draw_border(DrawingContext &dc) const {
if (area() <= 25) return;
const Rectangle br{boundingRectangle().increaseBy(0.1)}; 
if (isConvexPolygon()) { dc.drawGreenRectangle(br); return; } 
assert(isOval()); 
dc.drawRedRectangle(br); 
} 

> 
> And this is the complete code for adding a new operation (virtual cast 
> is to be only defined once for all operations)

You forget that you have BorderingVisitor class of unclear life , inject
 DrawingContext to it (is it copy?), etc. result is

BorderingVisitor bv(dc);
s.acceptVisit(bv);
 
instead one of: 

dc.draw_border(s);

s.draw_border(dc);

draw_border(dc, s);

> > But we need no bloat from that pattern to do neither. These were the 
> > things we wanted to get rid of.
> Care to show us the code for your "non bloated" "one-place" visitor 
> class for the above 2 problems? (like I did, you can omit the code that 
> is common for all operations).

Yes, sorry for gg breaking all formating.
> > 
> >> In practice this can be 
> >> anything, examples (non-exhaustive): a enum type discriminator, virtual 
> >> cast method, (in modern languages), pattern matching. 
> >>> 
> >>>> 3. add a single method to B, e.g. acceptVisit that takes [ref to] 
> >>>> Visitor and calls doOp on it. NOTE: only one such method is needed, no 
> >>>> matter how many ops you are adding. 
> >>> 
> >>> Not enough. Every type processed has to have that copy-paste 
> >>> acceptVisit method. Otherwise double dispatch does not work. 
> >> 
> >> Double dispatch works just fine without copy-pasting. Dispatch #1 (by 
> >> the operation type) is done by the virtual method doOp. Dispatch #2 (by 
> >> the object type) is done by using the actual type to call specific 
> >> methods and/or calling virtual methods, on the object ref. 
> > 
> > Of course huge switch or calling virtual method (or now even multiple) 
> > work without the whole pattern. Read up on "double dispatch". 
> > It is tricky like I already said. 
> >
> Surely you are the most competent software engineer understanding 
> multiple dispatch. Care to point out a "huge switch" in either of the 
> above 2 examples?

There are no double dispatch in your code so the whole acceptVisit, doOp
add nothing. These were entirely added to do double dispatch to add
virtual methods to large potentially unrelated data hierarchies without
changing any of classes in those hierarchies. What you do is not even
use case for visitor pattern.

[toc] | [prev] | [next] | [standalone]


#91383

FromPavel <pauldontspamtolk@removeyourself.dontspam.yahoo>
Date2023-08-24 01:38 -0400
Message-ID<G1CFM.897823$GMN3.400773@fx16.iad>
In reply to#91333
Öö Tiib wrote:
> On Tuesday, 22 August 2023 at 05:55:32 UTC+3, Pavel wrote:
>> Öö Tiib wrote:
>>> On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote:
>>>> Öö Tiib wrote:
>>>>> On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote:
>>>>>
>>>>>> 1. Ensure all objects have a common base class (say, it's B)
>>>>>
>>>>> Not needed.
>>>>>
>>>>>> 2. create an abstract class Visitor with an abstract method doOp with an
>>>>>> arg of type [ref to] B.
>>>>>
>>>>> Nope, pile of overloads of doOp one for each type processed.
>>>>
>>>> This would render the pattern useless as has no advantage over adding a
>>>> virtual method to do the op. The rationale of the pattern is to permit
>>>> coding one op in one place.
>>>>
>>> The Visitor class is that "one place"; doing same operation lets say printing
>>> of 50 types in one function results with huge function that is headache
>>> not advantage. Separate functions are testable and maintainable.
>>>
>>>>> Otherwise
>>>>> how you process different types differently?
>>>> I explained this in the problem statement. "Given [a ref to] any such
>>>> object, you are able to
>>>> compute its type and/or call a virtual method".
>>>
>>> IOW you do huge switch typeid ... case static_ cast or have the virtual
>>> method (over what we were "gaining advantage") already there?
>>
>> Even a switch with 30 cases is no worse than 30 methods although switch
>> is not always necessary (try to read the above instead of imagining).
>>
> Visitor is typically used to search, filter, draw or print whole data object
> hierarchy, convert to JSON to XML or to tree in GUI. 
correct

> If whole data hierarchy
> is small then you get 30 cases. Switch case is worse than virtual methods.
*In general* neither is better or worse. In particular, as I explained 
at the beginning, if the operation is decomposed correctly, its behavior 
on the objects of different types has to be coherent, similar in some 
way (and maybe more similar than different) and, as such, a significant 
parts of the context or results of intermediate computations could be 
shared. I demonstrated this in my example with red and green borders: a) 
a condition by area prevents visitor from operating on objects is 
computed from their property that is orthogonal to their dynamic type; 
and b) the exact behavior of the operation (red vs green border in the 
example) depends on a subset of types (which, in general, is not 
necessarily compact in the hierarchy).

> Think why  virtual methods were added? To get rid of switch cases over type.
Of course, not. Virtual methods were *used*, with less or greater 
success, to replace switch statements in event-processing scenarios 
(like window behavior) (with greater success) or in state machines (with 
lesser success).

But, they were *invented* to implement polymorphic behavior and 
relatively loose coupling (generally, lesser dependencies) between the 
code that calls the method and the code that implements the method.

Obviously, a single switch statement couples everything together and the 
compilation unit where the switch is depends on every piece of code and 
every concrete type the switch statement handles -- to the extent its 
behavior in the operation must differ from that of other types). But 
this loose coupling is only desirable when the behaviors are severely 
different. On the other hand, when the operation does *to significant 
extent, a single thing* on different type, with 2-3 relatively small 
type-dependent differences, these differences are often better expressed 
in if statement, switch or in a number of different ways (including 
calling combinations of virtual functions *from within the top-level 
method implementing the operation*)

> Visitor is not meant as excuse to add those back, otherwise just use
> the switch case in function, do not manufacture visitors that do not use
> double dispatch for confusion.
Ohh. Not again. If you want to use virtual functions, use them, you 
don't need visitor for this. Add virtual void newOperation(context 
parameters) to the base class and call it in some for_each. This is 
exactly what I explained in the first post: Visitor itself is an 
alternative to adding a virtual methods. The criteria which one to 
choose are well known from the original works on Visitor, like GoF etc: 
if your type hierarchy is big, stable, its model is well understood and 
established (like GUI toolkit), and hence you are unlikely to change the 
hierarchy often BUT you need to add new operations often, use visitors 
(because you don't need to add thing to every class for every 
operation). If your type hierarchy is dynamic and is expected to grow 
often but the set of operation is stable, well establish, unlikely to 
change often (like unit actions in a strategy game where you add and 
enhance units but all they are going to do is to essentially move, shoot 
and die), add a virtual method.

The first thing to explain about Visitor (or any other design pattern) 
is how to decide when to use it and when not to use it (the scope).

If the decision to use Visitor is correct, we already know the class 
hierarchy is stable and there is no harm for the code to depend on a 
fixed set of these classes. The double dispatch for Visitor is just a 
tool to dispatch to the code specific to the type after you dispatched 
to the specific operation. Nothing more, nothing less, see definition of 
multiple dispatch. The specific way to double-dispatch that many Visitor 
examples (unfortunately, started with GoF book, and shown in Wikipedia) 
show, is simply the *implementation detail*. You do not have to trust me.

Says Wikipedia:

"In software engineering, double dispatch is a special form of multiple 
dispatch, and a mechanism that dispatches a function call to different 
concrete functions depending on the runtime types of two objects 
involved in the call"

And, looking at same Wikipedia on multiple dispatch, C++ is *not* one of 
the languages that natively support multiple dispatch and there are 
multiple ways of implementing it, among them
-  "using run time type comparison via dynamic_cast" (if statement method)
- "pointer-to-method lookup table"; and (misleadingly)
- "the visitor pattern"

Why "misleadingly"? Because, looking at visitor pattern, you find

"The visitor takes the instance reference as input, and implements the 
goal through double dispatch."

So, according to Wikipedia article on multiple dispatch one can 
implement it (among the others) using visitor pattern, whereas visitor 
pattern "implements the goal via double dispatch" whereas the double 
dispatch is a special form of multiple dispatch.

Some readers (which seem to include you) resolve this circular reference 
ad-hoc: they see that *specific implementation* of multiple dispatch in 
C++ that happens to illustrate the Visitor article (stemming from the 
GoF book, unfortunately) and somehow decide that this is *the right way* 
(TM) to implement double dispatch. This is despite:

- that double dispatch is defined as a concept an there is no *the right 
way* of doing it in C++ and Wikipedia itself illustrates two such ways 
(both different from the one used in the visitor article and GoF book) 
in the article on multiple dispatch.

- that it must be obvious that it is Visitor that uses double dispatch 
in its implementation, not other may around, because the part cannot use 
the whole and Visitor is "bigger" than double-dispatch because it uses 
both double dispatch and iteration (through the objects in a data 
structure) but iteration is not a part of double dispatch.

The problem with that (by and large, randomly-selected) implementation 
of double dispatch is that it is a bad choice to illustrate Visitor 
pattern specifically. Besides the mere distracting the reader to its 
clever *implementation details* (not essential for understanding the 
Visitor pattern), it also makes it difficult to emphasize the coherence 
and cohesiveness of the newly added operation and does nothing to 
explain how to organize the access of the new operation to all pieces of 
data it may need. Taking into account that the whole Visitor pattern is 
about adding a new operation to working on a collection or stream ("the 
structure") of objects, it is a very unfortunate choice indeed.

> 
>> The advantage is in having this logic in one place. If your visitor does
>> a coherent thing (which it is supposed to do; else why is it a single
>> visitor, there could be and usually would be common code for all or many
>> visited). The most pliant cases (that are exactly those gaining most
>> from applying a visitor pattern) may get by object virtual methods.
>> Simple example:
>>
>> If we wanted a green border around a shape of any of 10 classes derived
>> from convex polygon (ConvexTriangle, ConvexQuadrilateral etc), red
>> border around a shape of any of 10 classes derived from Oval and no
>> border around any of 30 other zero-area shape classes, all we need to do
>> is to change doOp to:
>>
> 
> Unused cp in your code?
Yes, I started with taking bounding rectangle from every type separately 
(which sometimes may be a valid micro-optimization if we use a fully 
qualified non-virtual call; but then decided to keep it simple to 
emphasize the points I wanted to emphasize)

> Can't post sane code from gg ... but also don't want to install newsreaders
> to all devices I use. It is drawing border to any shape and so it is unclear
> why you need visitor. Logical one place is either DrwawingContext's or
> Shape's nonvirtual method or free function:
> 
> void Shape::draw_border(DrawingContext &dc) const {
> if (area() <= 25) return;
> const Rectangle br{boundingRectangle().increaseBy(0.1)};
> if (isConvexPolygon()) { dc.drawGreenRectangle(br); return; }
> assert(isOval());
> dc.drawRedRectangle(br);
> }
exactly, if you are ok to change the hierarchy every time you add an op, 
you might not need the visitor, all you need is to call draw_border from 
some for_each loop or high-order function.

This might be a valid choice sometimes (my first post started with 
this), but you pay by changing every class *every time you need a new 
operation* and making all Shape classes dependent on DrawingContext (and 
other contexts you might need for different operations (e.g. log file, 
network socket, database connection etc)). In visitor pattern, on the 
other hand, the concrete visitor is the only code that becomes dependent 
on that op-specific context.

> 
>>
>> And this is the complete code for adding a new operation (virtual cast
>> is to be only defined once for all operations)
> 
> You forget that you have BorderingVisitor class of unclear life , inject
>   DrawingContext to it (is it copy?), etc. result is
> 
> BorderingVisitor bv(dc);
> s.acceptVisit(bv);
>   
> instead one of:
> 
> dc.draw_border(s);
> 
> s.draw_border(dc);
> 
> draw_border(dc, s);
> 
>>> But we need no bloat from that pattern to do neither. These were the
>>> things we wanted to get rid of.
>> Care to show us the code for your "non bloated" "one-place" visitor
>> class for the above 2 problems? (like I did, you can omit the code that
>> is common for all operations).
> 
> Yes, sorry for gg breaking all formating.
>>>
>>>> In practice this can be
>>>> anything, examples (non-exhaustive): a enum type discriminator, virtual
>>>> cast method, (in modern languages), pattern matching.
>>>>>
>>>>>> 3. add a single method to B, e.g. acceptVisit that takes [ref to]
>>>>>> Visitor and calls doOp on it. NOTE: only one such method is needed, no
>>>>>> matter how many ops you are adding.
>>>>>
>>>>> Not enough. Every type processed has to have that copy-paste
>>>>> acceptVisit method. Otherwise double dispatch does not work.
>>>>
>>>> Double dispatch works just fine without copy-pasting. Dispatch #1 (by
>>>> the operation type) is done by the virtual method doOp. Dispatch #2 (by
>>>> the object type) is done by using the actual type to call specific
>>>> methods and/or calling virtual methods, on the object ref.
>>>
>>> Of course huge switch or calling virtual method (or now even multiple)
>>> work without the whole pattern. Read up on "double dispatch".
>>> It is tricky like I already said.
>>>
>> Surely you are the most competent software engineer understanding
>> multiple dispatch. Care to point out a "huge switch" in either of the
>> above 2 examples?
> 
> There are no double dispatch in your code so the whole acceptVisit, doOp
> add nothing. 
Yes it is, see above (or directly Wikipedia article on multiple dispatch 
for the examples on how Double dispatch can be implemented in C++).

> These
Unsure I understand what "these" refers to, will assume my doOp and 
acceptVisit functions

> were entirely added to do double dispatch 
correct. acceptVisit dispatches by op (the 1st parameter of the outer 
structure "visit" operation) and doOp dispatches by object type (in my 
examples, either by using its base type's virtual function only or via a 
combination of the same and `if' statement on virtual cast result)

> to add
> virtual methods to large potentially unrelated data hierarchies without
> changing any of classes in those hierarchies. 
I assume, by "potentially unrelated" data hierarchy, you mean the new op 
has to behave entirely differently on every type of the hierarchy and 
does not need common data. The issue with this assumption is that the 
very first op you add might relate to the classes like this with but the 
second might benefit from exploiting the type similarities or grouping 
them differently (not even necessary in groups compact in the hierarchy 
graph).


> What you do is not even
> use case for visitor pattern.
Says Wikipedia:

"
What problems can the Visitor design pattern solve?

It should be possible to define a new operation for (some) classes of an 
object structure without changing the classes.
"

"What I do" achieves the above IMO, so it is definitely the use case. 
You might argue whether or not my solution is a Visitor pattern (it is, 
at least it includes all canonical Visitor participants (Abstract 
Visitor, Concrete Visitors, Abstract Element, Concrete Elements (an 
object structure providing access to the elements is implied)) and none 
extra) but I don't understand how it can be argued that my code does not 
define a new operation for (some) classes of an object structure. And it 
surely does not change the classes.

[toc] | [prev] | [next] | [standalone]


#91386

FromÖö Tiib <ootiib@hot.ee>
Date2023-08-24 04:45 -0700
Message-ID<a0da9e2a-a015-4497-96bc-8f9fbff3957an@googlegroups.com>
In reply to#91383
On Thursday, 24 August 2023 at 08:39:04 UTC+3, Pavel wrote:
> Öö Tiib wrote: 
> > On Tuesday, 22 August 2023 at 05:55:32 UTC+3, Pavel wrote: 
> >> Öö Tiib wrote: 
> >>> On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote: 
> >>>> Öö Tiib wrote: 
> >>>>> On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote: 
> >>>>> 
> >>>>>> 1. Ensure all objects have a common base class (say, it's B) 
> >>>>> 
> >>>>> Not needed. 
> >>>>> 
> >>>>>> 2. create an abstract class Visitor with an abstract method doOp with an 
> >>>>>> arg of type [ref to] B. 
> >>>>> 
> >>>>> Nope, pile of overloads of doOp one for each type processed. 
> >>>> 
> >>>> This would render the pattern useless as has no advantage over adding a 
> >>>> virtual method to do the op. The rationale of the pattern is to permit 
> >>>> coding one op in one place. 
> >>>> 
> >>> The Visitor class is that "one place"; doing same operation lets say printing 
> >>> of 50 types in one function results with huge function that is headache 
> >>> not advantage. Separate functions are testable and maintainable. 
> >>> 
> >>>>> Otherwise 
> >>>>> how you process different types differently? 
> >>>> I explained this in the problem statement. "Given [a ref to] any such 
> >>>> object, you are able to 
> >>>> compute its type and/or call a virtual method". 
> >>> 
> >>> IOW you do huge switch typeid ... case static_ cast or have the virtual 
> >>> method (over what we were "gaining advantage") already there? 
> >> 
> >> Even a switch with 30 cases is no worse than 30 methods although switch 
> >> is not always necessary (try to read the above instead of imagining). 
> >> 
> > Visitor is typically used to search, filter, draw or print whole data object 
> > hierarchy, convert to JSON to XML or to tree in GUI.
> correct
> > If whole data hierarchy 
> > is small then you get 30 cases. Switch case is worse than virtual methods.
> 
> *In general* neither is better or worse. 

Switch case over type is in general worse than virtual functions. Longer
function, bigger cyclomatic complexity, harder to understand, harder to
form confidence that it is valid, harder to maintain, harder to test and
also may need more access to implementation details of all types 
involved, breaking encapsulation. 

> In particular, as I explained 
> at the beginning, if the operation is decomposed correctly, its behavior 
> on the objects of different types has to be coherent, similar in some 
> way (and maybe more similar than different) and, as such, a significant 
> parts of the context or results of intermediate computations could be 
> shared. 

Translating data hierarchy to xml for particular version of particular
protocol while supporting several such in parallel? There can be 
intermediate computations shared but all are typically in in visitor
classes or helpers of those. No one wants the classes in hierarchy
to know anything about xml whatsoever, nothing to talk about
particular brand used in particular version of kinds of end-points.

> I demonstrated this in my example with red and green borders: a) 
> a condition by area prevents visitor from operating on objects is 
> computed from their property that is orthogonal to their dynamic type; 
> and b) the exact behavior of the operation (red vs green border in the 
> example) depends on a subset of types (which, in general, is not 
> necessarily compact in the hierarchy).
> 
Your example did not need visitor pattern.

> > Think why virtual methods were added? To get rid of switch cases over type.
> Of course, not. Virtual methods were *used*, with less or greater 
> success, to replace switch statements in event-processing scenarios 
> (like window behavior) (with greater success) or in state machines (with 
> lesser success). 
> 
> But, they were *invented* to implement polymorphic behavior and 
> relatively loose coupling (generally, lesser dependencies) between the 
> code that calls the method and the code that implements the method. 
> 
Same loose coupling is with code that calls function that does switch
case over type, so that kind of victory wasn't achieved.

> Obviously, a single switch statement couples everything together and the 
> compilation unit where the switch is depends on every piece of code and 
> every concrete type the switch statement handles -- to the extent its 
> behavior in the operation must differ from that of other types). But 
> this loose coupling is only desirable when the behaviors are severely 
> different. 
> 
I don't follow what you are saying. Different stock markets and other
financial institutions for example are not severely different in essence,
but the format of data you are expected to exchange with their servers
differs massively enough.

> On the other hand, when the operation does *to significant 
> extent, a single thing* on different type, with 2-3 relatively small 
> type-dependent differences, these differences are often better expressed 
> in if statement, switch or in a number of different ways (including 
> calling combinations of virtual functions *from within the top-level 
> method implementing the operation*)
> 
That is not use-case of visitor pattern.

> > Visitor is not meant as excuse to add those back, otherwise just use 
> > the switch case in function, do not manufacture visitors that do not use 
> > double dispatch for confusion.
> 
> Ohh. Not again. If you want to use virtual functions, use them, you 
> don't need visitor for this. Add virtual void newOperation(context 
> parameters) to the base class and call it in some for_each. This is 
> exactly what I explained in the first post: Visitor itself is an 
> alternative to adding a virtual methods.
> 
You add same number of virtual methods of visitor if you need several
different classes of visitors to visit over same data hierarchies. The
gain is  that these are in one place, in particular visitor and that the 
classes in hierarchies are not changed by single letter.   

> The criteria which one to 
> choose are well known from the original works on Visitor, like GoF etc: 
> if your type hierarchy is big, stable, its model is well understood and 
> established (like GUI toolkit), and hence you are unlikely to change the 
> hierarchy often BUT you need to add new operations often, use visitors 
> (because you don't need to add thing to every class for every 
> operation). If your type hierarchy is dynamic and is expected to grow 
> often but the set of operation is stable, well establish, unlikely to 
> change often (like unit actions in a strategy game where you add and 
> enhance units but all they are going to do is to essentially move, shoot 
> and die), add a virtual method. 
> 
> The first thing to explain about Visitor (or any other design pattern) 
> is how to decide when to use it and when not to use it (the scope). 
> 
That is true, but you seem to suggest to misuse it, where unneeded.

> If the decision to use Visitor is correct, we already know the class 
> hierarchy is stable and there is no harm for the code to depend on a 
> fixed set of these classes. The double dispatch for Visitor is just a 
> tool to dispatch to the code specific to the type after you dispatched 
> to the specific operation. Nothing more, nothing less, see definition of 
> multiple dispatch. The specific way to double-dispatch that many Visitor 
> examples (unfortunately, started with GoF book, and shown in Wikipedia) 
> show, is simply the *implementation detail*. You do not have to trust me. 
> 
Do not touch multiple dispatch yet. Going there now is like going into
four dimensional math when you haven't yet got the planar 2D grid
to work. Stay firmly within studying double dispatch until you understand
what it is. 

> Says Wikipedia: 
> 
> "In software engineering, double dispatch is a special form of multiple 
> dispatch, and a mechanism that dispatches a function call to different 
> concrete functions depending on the runtime types of two objects 
> involved in the call" 
> 
> And, looking at same Wikipedia on multiple dispatch, C++ is *not* one of 
> the languages that natively support multiple dispatch and there are 
> multiple ways of implementing it, among them 
> - "using run time type comparison via dynamic_cast" (if statement method) 
> - "pointer-to-method lookup table"; and (misleadingly) 
> - "the visitor pattern" 
> 
> Why "misleadingly"? Because, looking at visitor pattern, you find 
> 
> "The visitor takes the instance reference as input, and implements the 
> goal through double dispatch." 
> 
> So, according to Wikipedia article on multiple dispatch one can 
> implement it (among the others) using visitor pattern, whereas visitor 
> pattern "implements the goal via double dispatch" whereas the double 
> dispatch is a special form of multiple dispatch. 
> 
Yes. you were confused by first sentence in wikipedia and stopped to 
read there. Double dispatch is multiple dispatch like 2D grid is special
case of 4 dimensional room with 2 dimensions being of zero size.  

> Some readers (which seem to include you) resolve this circular reference 
> ad-hoc: they see that *specific implementation* of multiple dispatch in 
> C++ that happens to illustrate the Visitor article (stemming from the 
> GoF book, unfortunately) and somehow decide that this is *the right way* 
> (TM) to implement double dispatch. This is despite: 
> 
> - that double dispatch is defined as a concept an there is no *the right 
> way* of doing it in C++ and Wikipedia itself illustrates two such ways 
> (both different from the one used in the visitor article and GoF book) 
> in the article on multiple dispatch. 
> 
> - that it must be obvious that it is Visitor that uses double dispatch 
> in its implementation, not other may around, because the part cannot use 
> the whole and Visitor is "bigger" than double-dispatch because it uses 
> both double dispatch and iteration (through the objects in a data 
> structure) but iteration is not a part of double dispatch. 
> 
> The problem with that (by and large, randomly-selected) implementation 
> of double dispatch is that it is a bad choice to illustrate Visitor 
> pattern specifically. Besides the mere distracting the reader to its 
> clever *implementation details* (not essential for understanding the 
> Visitor pattern), it also makes it difficult to emphasize the coherence 
> and cohesiveness of the newly added operation and does nothing to 
> explain how to organize the access of the new operation to all pieces of 
> data it may need. Taking into account that the whole Visitor pattern is 
> about adding a new operation to working on a collection or stream ("the 
> structure") of objects, it is a very unfortunate choice indeed.
> 
The whole point of calling o.accept(v) that then calls v.doOp(o) is double
dispatch. Otherwise why not to call v.doOp(o) right away and erase the
accept()? Think a bit, wall of text how everybody around are confused
does not make it true.  

> > 
> >> The advantage is in having this logic in one place. If your visitor does 
> >> a coherent thing (which it is supposed to do; else why is it a single 
> >> visitor, there could be and usually would be common code for all or many 
> >> visited). The most pliant cases (that are exactly those gaining most 
> >> from applying a visitor pattern) may get by object virtual methods. 
> >> Simple example: 
> >> 
> >> If we wanted a green border around a shape of any of 10 classes derived 
> >> from convex polygon (ConvexTriangle, ConvexQuadrilateral etc), red 
> >> border around a shape of any of 10 classes derived from Oval and no 
> >> border around any of 30 other zero-area shape classes, all we need to do 
> >> is to change doOp to: 
> >> 
> > 
> > Unused cp in your code?
> Yes, I started with taking bounding rectangle from every type separately 
> (which sometimes may be a valid micro-optimization if we use a fully 
> qualified non-virtual call; but then decided to keep it simple to 
> emphasize the points I wanted to emphasize)
> > Can't post sane code from gg ... but also don't want to install newsreaders 
> > to all devices I use. It is drawing border to any shape and so it is unclear 
> > why you need visitor. Logical one place is either DrwawingContext's or 
> > Shape's nonvirtual method or free function: 
> > 
> > void Shape::draw_border(DrawingContext &dc) const { 
> > if (area() <= 25) return; 
> > const Rectangle br{boundingRectangle().increaseBy(0.1)}; 
> > if (isConvexPolygon()) { dc.drawGreenRectangle(br); return; } 
> > assert(isOval()); 
> > dc.drawRedRectangle(br); 
> > }
> exactly, if you are ok to change the hierarchy every time you add an op, 
> you might not need the visitor, all you need is to call draw_border from 
> some for_each loop or high-order function. 
> 
Free function does no way change hierarchy. Adding non-virtual 
convenience  method to base class of one of participants does
it minimally. 

> This might be a valid choice sometimes (my first post started with 
> this), but you pay by changing every class *every time you need a new 
> operation* and making all Shape classes dependent on DrawingContext (and 
> other contexts you might need for different operations (e.g. log file, 
> network socket, database connection etc)). In visitor pattern, on the 
> other hand, the concrete visitor is the only code that becomes dependent 
> on that op-specific context.
> 
It is totally OK choice for your presented use-case, visitor is unneeded 
bloat there. 

> > 
> >> 
> >> And this is the complete code for adding a new operation (virtual cast 
> >> is to be only defined once for all operations) 
> > 
> > You forget that you have BorderingVisitor class of unclear life , inject 
> > DrawingContext to it (is it copy?), etc. result is 
> > 
> > BorderingVisitor bv(dc); 
> > s.acceptVisit(bv); 
> > 
> > instead one of: 
> > 
> > dc.draw_border(s); 
> > 
> > s.draw_border(dc); 
> > 
> > draw_border(dc, s); 
> > 
See? Crickets are chirping, no one is explaining why the bloat was even
needed.
 
> >>> But we need no bloat from that pattern to do neither. These were the 
> >>> things we wanted to get rid of. 
> >> Care to show us the code for your "non bloated" "one-place" visitor 
> >> class for the above 2 problems? (like I did, you can omit the code that 
> >> is common for all operations). 
> > 
> > Yes, sorry for gg breaking all formating. 
> >>> 
> >>>> In practice this can be 
> >>>> anything, examples (non-exhaustive): a enum type discriminator, virtual 
> >>>> cast method, (in modern languages), pattern matching. 
> >>>>> 
> >>>>>> 3. add a single method to B, e.g. acceptVisit that takes [ref to] 
> >>>>>> Visitor and calls doOp on it. NOTE: only one such method is needed, no 
> >>>>>> matter how many ops you are adding. 
> >>>>> 
> >>>>> Not enough. Every type processed has to have that copy-paste 
> >>>>> acceptVisit method. Otherwise double dispatch does not work. 
> >>>> 
> >>>> Double dispatch works just fine without copy-pasting. Dispatch #1 (by 
> >>>> the operation type) is done by the virtual method doOp. Dispatch #2 (by 
> >>>> the object type) is done by using the actual type to call specific 
> >>>> methods and/or calling virtual methods, on the object ref. 
> >>> 
> >>> Of course huge switch or calling virtual method (or now even multiple) 
> >>> work without the whole pattern. Read up on "double dispatch". 
> >>> It is tricky like I already said. 
> >>> 
> >> Surely you are the most competent software engineer understanding 
> >> multiple dispatch. Care to point out a "huge switch" in either of the 
> >> above 2 examples? 
> > 
> > There are no double dispatch in your code so the whole acceptVisit, doOp 
> > add nothing.
>
> Yes it is, see above (or directly Wikipedia article on multiple dispatch 
> for the examples on how Double dispatch can be implemented in C++). 
>
Do not consider multiple dispatch before figuring what double
dispatch is.
 
> > These 
> Unsure I understand what "these" refers to, will assume my doOp and 
> acceptVisit functions
> > were entirely added to do double dispatch
> 
> correct. acceptVisit dispatches by op (the 1st parameter of the outer 
> structure "visit" operation) and doOp dispatches by object type (in my 
> examples, either by using its base type's virtual function only or via a 
> combination of the same and `if' statement on virtual cast result)
> 
Except on your case it does not. There is just single function that
decides if to draw red or green border.

> > to add 
> > virtual methods to large potentially unrelated data hierarchies without 
> > changing any of classes in those hierarchies.
> I assume, by "potentially unrelated" data hierarchy, you mean the new op 
> has to behave entirely differently on every type of the hierarchy and 
> does not need common data. The issue with this assumption is that the 
> very first op you add might relate to the classes like this with but the 
> second might benefit from exploiting the type similarities or grouping 
> them differently (not even necessary in groups compact in the hierarchy 
> graph).
> > What you do is not even 
> > use case for visitor pattern.
> Says Wikipedia: 
> 
> " 
> What problems can the Visitor design pattern solve? 
> 
> It should be possible to define a new operation for (some) classes of an 
> object structure without changing the classes. 
> " 

"When new operations are needed frequently and the object structure 
consists of many unrelated classes, it's inflexible to add new subclasses
each time a new operation is required because "[..] distributing all these
operations across the various node classes leads to a system that's hard
to understand, maintain, and change."

Exactly. But if all you need is single little function then write single
little function. Neither single dispatch (virtual functions) nor double
dispatch (matching visitor classes to data hierarchy classes) are
needed for single little function. How did your function being in
visitor make anything easier to understand, maintain and change?   

> 
> "What I do" achieves the above IMO, so it is definitely the use case. 
> You might argue whether or not my solution is a Visitor pattern (it is, 
> at least it includes all canonical Visitor participants (Abstract 
> Visitor, Concrete Visitors, Abstract Element, Concrete Elements (an 
> object structure providing access to the elements is implied)) and none 
> extra) but I don't understand how it can be argued that my code does not 
> define a new operation for (some) classes of an object structure. And it 
> surely does not change the classes.
> 
That is especially bad and confusing when something looks like a
pattern, uses names from that pattern but actually isn't the pattern
at all in essence.

[toc] | [prev] | [next] | [standalone]


#91401

FromPavel <pauldontspamtolk@removeyourself.dontspam.yahoo>
Date2023-08-24 23:11 -0400
Message-ID<XZUFM.137111$PlBb.67508@fx42.iad>
In reply to#91386
Öö Tiib wrote:
> On Thursday, 24 August 2023 at 08:39:04 UTC+3, Pavel wrote:
>> Öö Tiib wrote:
>>> On Tuesday, 22 August 2023 at 05:55:32 UTC+3, Pavel wrote:
>>>> Öö Tiib wrote:
>>>>> On Monday, 21 August 2023 at 01:02:21 UTC+3, Pavel wrote:
>>>>>> Öö Tiib wrote:
>>>>>>> On Sunday, 20 August 2023 at 21:13:01 UTC+3, Pavel wrote:
>>>>>>>
>>>>>>>> 1. Ensure all objects have a common base class (say, it's B)
>>>>>>>
>>>>>>> Not needed.
>>>>>>>
>>>>>>>> 2. create an abstract class Visitor with an abstract method doOp with an
>>>>>>>> arg of type [ref to] B.
>>>>>>>
>>>>>>> Nope, pile of overloads of doOp one for each type processed.
>>>>>>
>>>>>> This would render the pattern useless as has no advantage over adding a
>>>>>> virtual method to do the op. The rationale of the pattern is to permit
>>>>>> coding one op in one place.
>>>>>>
>>>>> The Visitor class is that "one place"; doing same operation lets say printing
>>>>> of 50 types in one function results with huge function that is headache
>>>>> not advantage. Separate functions are testable and maintainable.
>>>>>
>>>>>>> Otherwise
>>>>>>> how you process different types differently?
>>>>>> I explained this in the problem statement. "Given [a ref to] any such
>>>>>> object, you are able to
>>>>>> compute its type and/or call a virtual method".
>>>>>
>>>>> IOW you do huge switch typeid ... case static_ cast or have the virtual
>>>>> method (over what we were "gaining advantage") already there?
>>>>
>>>> Even a switch with 30 cases is no worse than 30 methods although switch
>>>> is not always necessary (try to read the above instead of imagining).
>>>>
>>> Visitor is typically used to search, filter, draw or print whole data object
>>> hierarchy, convert to JSON to XML or to tree in GUI.
>> correct
>>> If whole data hierarchy
>>> is small then you get 30 cases. Switch case is worse than virtual methods.
>>
>> *In general* neither is better or worse.
> 
> Switch case over type is in general worse than virtual functions. Longer
> function,
Longer than what? Definitely the one function is shorter than the sum of 
the length of all the element type hierarchy shims plus concrete visitor 
visit overloads.

> bigger cyclomatic complexity, 
with good code organization, no bigger than absolutely necessary.

> harder to understand, with good code organization, actually, easier (see my point below about 
maintenance.

> harder to
> form confidence that it is valid,
see below in maintenance.

> harder to maintain, 
actually, easier to maintain. For using that GoF / wiki DD 
implementation, non-insignificant amount of scaffolding code has to be 
written. Especially in a complex hierarchy, one can easily forget to 
write that accept shim for a couple of classes where it is needed. E.g. 
if in the hierarchy (or a part thereof) is "(B, C, D) > A" (> meaning 
inheritance), imagine the op shall do same on A and D but different on B 
and C. Isn't it easy to forget to add the accept shim to C (even though 
the actual logic visit(C), at which the programmer is likely to focus, 
is written all right).

> harder to test and
why?

> also may need more access to implementation details of all types
> involved, breaking encapsulation.
How so? All code is written in the op function that has no access to 
internals.

> 
>> In particular, as I explained
>> at the beginning, if the operation is decomposed correctly, its behavior
>> on the objects of different types has to be coherent, similar in some
>> way (and maybe more similar than different) and, as such, a significant
>> parts of the context or results of intermediate computations could be
>> shared.
> 
> Translating data hierarchy to xml for particular version of particular
> protocol while supporting several such in parallel? 
Imagine {ABCD) type hierarchy above is to be translated to XML and B and 
C have to become XML elements with some non-trivially computable 
attribute atr1 but XML elements produced from A and D don't need it.

> There can be
> intermediate computations shared but all are typically in in visitor
> classes or helpers of those. No one wants the classes in hierarchy
> to know anything about xml whatsoever,
correct but do not see how it is connected to anything I wrote

> nothing to talk about
> particular brand used in particular version of kinds of end-points.
let's not argue with anything I did not say, it's really irritating, I 
will gladly give you an example, just explain why {ABCD} above is no good.

> 
>> I demonstrated this in my example with red and green borders: a)
>> a condition by area prevents visitor from operating on objects is
>> computed from their property that is orthogonal to their dynamic type;
>> and b) the exact behavior of the operation (red vs green border in the
>> example) depends on a subset of types (which, in general, is not
>> necessarily compact in the hierarchy).
>>
> Your example did not need visitor pattern.
No example "needs" visitor pattern: any problem solved with visitor can 
be solved in other ways, e.g. just by adding a virtual method to every 
class. But *both* my examples could be perfectly solved with visitor. If 
the one-time scaffolding is in place, I am ready to argue it is a good 
solution.

> 
>>> Think why virtual methods were added? To get rid of switch cases over type.
>> Of course, not. Virtual methods were *used*, with less or greater
>> success, to replace switch statements in event-processing scenarios
>> (like window behavior) (with greater success) or in state machines (with
>> lesser success).
>>
>> But, they were *invented* to implement polymorphic behavior and
>> relatively loose coupling (generally, lesser dependencies) between the
>> code that calls the method and the code that implements the method.
>>
> Same loose coupling is with code that calls function that does switch
> case over type, so that kind of victory wasn't achieved.
There is no such thing as "coupling in general". Any particular 
dependency is some kind of coupling. If one has to add 
accept(AbstractVisitor) method to the root of the type hierarchy, both 
the type hierarchy's interface and implementation become compile-time 
dependent on the new operation's interface (AbstractVisitor) (interface 
may depend on forward-declaration only in C++, but not the 
implementation so the implementation will also "see" N "visit" methods). 
Additionally, because every AbstractVisitor depends on (the interface 
of) every of (say, M) concrete element types, the Element's interface 
implementation becomes dependent on every of M Element types (at least 
their forward declarations, in C++). In this implementation, any new 
operation adds the dependency, needs recompilation of class hierarchy etc.

If, as in my example, you only have one AbstractVisitor, adding 2nd and 
further operations adds zero dependencies, don't require recompilation.

> 
>> Obviously, a single switch statement couples everything together and the
>> compilation unit where the switch is depends on every piece of code and
>> every concrete type the switch statement handles -- to the extent its
>> behavior in the operation must differ from that of other types). But
>> this loose coupling is only desirable when the behaviors are severely
>> different.
>>
> I don't follow what you are saying. Different stock markets and other
> financial institutions for example are not severely different in essence,
> but the format of data you are expected to exchange with their servers
> differs massively enough.
Nah, it's the other way around. Say, BankA and BankB may have different 
formats of their, say, client-side FIX protocol. But, they likely both 
talk to NYSE using NYSE FIX/FAST and OATS reporting in exactly same or 
almost same format.

Also, who needs to write an app that puts BankA and BankB to the same 
hierarchy? It's unlinkely that it's one of BankA or BankB. More likely, 
some regulatory agency or an exchange or settlement firm (hereinafter 
*an outside firm*). For the purpose of the outside firm, the formats 
it's interested in to operate on are likely the formats it uses to 
communicate with those Banks (i.e. same or with small customizations). A 
drastically different formats that BankA and B use to communicate with 
their clients will unlikely be needed by an exchange (they could be 
needed by a surveillance agency; but then "my" code would become 
respectively simpler, *always staying simpler* than the correspondent 
GoF code).

> 
>> On the other hand, when the operation does *to significant
>> extent, a single thing* on different type, with 2-3 relatively small
>> type-dependent differences, these differences are often better expressed
>> in if statement, switch or in a number of different ways (including
>> calling combinations of virtual functions *from within the top-level
>> method implementing the operation*)
>>
> That is not use-case of visitor pattern.
You say it more than once and never cared to provide any reasoning. I 
cited the cause to use Visitor (from Wikipedia you should like). That 
cause is fully applicable for the above setup assuming one needs to add 
many operations. I do not intend to repeat this again unless you provide 
good reasoning.

> 
>>> Visitor is not meant as excuse to add those back, otherwise just use
>>> the switch case in function, do not manufacture visitors that do not use
>>> double dispatch for confusion.
>>
>> Ohh. Not again. If you want to use virtual functions, use them, you
>> don't need visitor for this. Add virtual void newOperation(context
>> parameters) to the base class and call it in some for_each. This is
>> exactly what I explained in the first post: Visitor itself is an
>> alternative to adding a virtual methods.
>>
> You add same number of virtual methods of visitor if you need several
> different classes of visitors to visit over same data hierarchies. The
> gain is  that these are in one place, in particular visitor and that the
> classes in hierarchies are not changed by single letter.
Yes, not changing classes when adding an op is the one of the causes for 
using Visitor. Both my and Wiki visitor implementation formally achieve 
this cause. Plain and simple. BUT, Wiki implementation changes the 
compilation units of every Element (aka creates compilation dependency). 
Plain and simple. My implementation doesn't (except for the very 1st 
operation).

> 
>> The criteria which one to
>> choose are well known from the original works on Visitor, like GoF etc:
>> if your type hierarchy is big, stable, its model is well understood and
>> established (like GUI toolkit), and hence you are unlikely to change the
>> hierarchy often BUT you need to add new operations often, use visitors
>> (because you don't need to add thing to every class for every
>> operation). If your type hierarchy is dynamic and is expected to grow
>> often but the set of operation is stable, well establish, unlikely to
>> change often (like unit actions in a strategy game where you add and
>> enhance units but all they are going to do is to essentially move, shoot
>> and die), add a virtual method.
>>
>> The first thing to explain about Visitor (or any other design pattern)
>> is how to decide when to use it and when not to use it (the scope).
>>
> That is true, but you seem to suggest to misuse it, where unneeded.
> 
>> If the decision to use Visitor is correct, we already know the class
>> hierarchy is stable and there is no harm for the code to depend on a
>> fixed set of these classes. The double dispatch for Visitor is just a
>> tool to dispatch to the code specific to the type after you dispatched
>> to the specific operation. Nothing more, nothing less, see definition of
>> multiple dispatch. The specific way to double-dispatch that many Visitor
>> examples (unfortunately, started with GoF book, and shown in Wikipedia)
>> show, is simply the *implementation detail*. You do not have to trust me.
>>
> Do not touch multiple dispatch yet. Going there now is like going into
> four dimensional math when you haven't yet got the planar 2D grid
> to work. Stay firmly within studying double dispatch until you understand
> what it is.
That's not I, that's you favorite ref source Wiki correctly says double 
a dispatch is a special form of multiple dispatch and then, in all C++ 
examples it gives to show MD, it actually shows DD only. In fact, both 
articles use same "Asteroid-Spaceship" example (DD) to exemplify either.

Now IMO, here is nothing magical in dispatching on dynamic types of 3 
parameters as compared to the 2. It is surely more boring to express as 
there is usually more functions to write; but otherwise, nothing too 
exciting.

> 
>> Says Wikipedia:
>>
>> "In software engineering, double dispatch is a special form of multiple
>> dispatch, and a mechanism that dispatches a function call to different
>> concrete functions depending on the runtime types of two objects
>> involved in the call"
>>
>> And, looking at same Wikipedia on multiple dispatch, C++ is *not* one of
>> the languages that natively support multiple dispatch and there are
>> multiple ways of implementing it, among them
>> - "using run time type comparison via dynamic_cast" (if statement method)
>> - "pointer-to-method lookup table"; and (misleadingly)
>> - "the visitor pattern"
>>
>> Why "misleadingly"? Because, looking at visitor pattern, you find
>>
>> "The visitor takes the instance reference as input, and implements the
>> goal through double dispatch."
>>
>> So, according to Wikipedia article on multiple dispatch one can
>> implement it (among the others) using visitor pattern, whereas visitor
>> pattern "implements the goal via double dispatch" whereas the double
>> dispatch is a special form of multiple dispatch.
>>
> Yes. you were confused by first sentence in wikipedia and stopped to
> read there. Double dispatch is multiple dispatch like 2D grid is special
> case of 4 dimensional room with 2 dimensions being of zero size.
Nothing like it. Implementing DD thru the manual scaffolding of what you 
seem to believe *the one and the only correct implementation of DD for 
Visitor pattern in C++" is boring and error-prone enough (see above 
about maintenance) to not be the first choice in explaining Visitor.

> 
>> Some readers (which seem to include you) resolve this circular reference
>> ad-hoc: they see that *specific implementation* of multiple dispatch in
>> C++ that happens to illustrate the Visitor article (stemming from the
>> GoF book, unfortunately) and somehow decide that this is *the right way*
>> (TM) to implement double dispatch. This is despite:
>>
>> - that double dispatch is defined as a concept an there is no *the right
>> way* of doing it in C++ and Wikipedia itself illustrates two such ways
>> (both different from the one used in the visitor article and GoF book)
>> in the article on multiple dispatch.
>>
>> - that it must be obvious that it is Visitor that uses double dispatch
>> in its implementation, not other may around, because the part cannot use
>> the whole and Visitor is "bigger" than double-dispatch because it uses
>> both double dispatch and iteration (through the objects in a data
>> structure) but iteration is not a part of double dispatch.
>>
>> The problem with that (by and large, randomly-selected) implementation
>> of double dispatch is that it is a bad choice to illustrate Visitor
>> pattern specifically. Besides the mere distracting the reader to its
>> clever *implementation details* (not essential for understanding the
>> Visitor pattern), it also makes it difficult to emphasize the coherence
>> and cohesiveness of the newly added operation and does nothing to
>> explain how to organize the access of the new operation to all pieces of
>> data it may need. Taking into account that the whole Visitor pattern is
>> about adding a new operation to working on a collection or stream ("the
>> structure") of objects, it is a very unfortunate choice indeed.
>>
> The whole point of calling o.accept(v) that then calls v.doOp(o) is double
> dispatch. Otherwise why not to call v.doOp(o) right away and erase the
> accept()? 
You could, the complete class of Visitor problems is still solvable 
without the accept shim. It is an implementation detail as well. It is 
sometimes useful for the purposes other than that particular DD 
implementation, e.g. to limit the exposure of the element types to the 
"object structure" participant.

For an example of a framework implementing Visitor without shims, see 
e.g. std::visit.

> Think a bit, wall of text how everybody around are confused
> does not make it true.
agree

> 
>>>
>>>> The advantage is in having this logic in one place. If your visitor does
>>>> a coherent thing (which it is supposed to do; else why is it a single
>>>> visitor, there could be and usually would be common code for all or many
>>>> visited). The most pliant cases (that are exactly those gaining most
>>>> from applying a visitor pattern) may get by object virtual methods.
>>>> Simple example:
>>>>
>>>> If we wanted a green border around a shape of any of 10 classes derived
>>>> from convex polygon (ConvexTriangle, ConvexQuadrilateral etc), red
>>>> border around a shape of any of 10 classes derived from Oval and no
>>>> border around any of 30 other zero-area shape classes, all we need to do
>>>> is to change doOp to:
>>>>
>>>
>>> Unused cp in your code?
>> Yes, I started with taking bounding rectangle from every type separately
>> (which sometimes may be a valid micro-optimization if we use a fully
>> qualified non-virtual call; but then decided to keep it simple to
>> emphasize the points I wanted to emphasize)
>>> Can't post sane code from gg ... but also don't want to install newsreaders
>>> to all devices I use. It is drawing border to any shape and so it is unclear
>>> why you need visitor. 
Because to decide whether to draw the border and (in the Example 2) its 
color the element type is needed. I was about to introduce an 
intermediate "ConvexArea" class but got lazy; regardless, Example 2 
demonstrates the dependency on type.

Logical one place is either DrwawingContext's or
>>> Shape's nonvirtual method or free function:
>>>
>>> void Shape::draw_border(DrawingContext &dc) const {
>>> if (area() <= 25) return;
>>> const Rectangle br{boundingRectangle().increaseBy(0.1)};
>>> if (isConvexPolygon()) { dc.drawGreenRectangle(br); return; }
>>> assert(isOval());
>>> dc.drawRedRectangle(br);
>>> }
>> exactly, if you are ok to change the hierarchy every time you add an op,
>> you might not need the visitor, all you need is to call draw_border from
>> some for_each loop or high-order function.
>>
> Free function does no way change hierarchy. Adding non-virtual
> convenience  method to base class of one of participants does
> it minimally.
See above about dependency and maintenance.

> 
>> This might be a valid choice sometimes (my first post started with
>> this), but you pay by changing every class *every time you need a new
>> operation* and making all Shape classes dependent on DrawingContext (and
>> other contexts you might need for different operations (e.g. log file,
>> network socket, database connection etc)). In visitor pattern, on the
>> other hand, the concrete visitor is the only code that becomes dependent
>> on that op-specific context.
>>
> It is totally OK choice for your presented use-case, visitor is unneeded
> bloat there.
It *is* a Visitor as it has all the participants and solve the problem.

> 
>>>
>>>>
>>>> And this is the complete code for adding a new operation (virtual cast
>>>> is to be only defined once for all operations)
>>>
>>> You forget that you have BorderingVisitor class of unclear life , inject
>>> DrawingContext to it (is it copy?), etc. result is
>>>
>>> BorderingVisitor bv(dc);
>>> s.acceptVisit(bv);
>>>
>>> instead one of:
>>>
>>> dc.draw_border(s);
dc is not supposed to know anything about a Shape, It draws a 
rectangular body given a Rectangle only to specify the exact geometry of 
the Shape. It is not a business of dc to compute. What if I want to, 
instead of as a border of the size of the shape-cirumscribing rectangle 
increased by 0.1 unit in all directions, add margins of 2 units at the 
right and 1 unit at other 3 directions? That increaseBy call in my 
example code is there for reason.

>>>
>>> s.draw_border(dc);
>>>
>>> draw_border(dc, s);
>>>
> See? Crickets are chirping,
no, they are strangely croaking, the border does not look like I wanted 
it to, see above.

>  no one is explaining why the bloat was even
> needed.
someone did but someone else did not like to read. Where's my increaseBy?

>   
>>>>> But we need no bloat from that pattern to do neither. These were the
>>>>> things we wanted to get rid of.
>>>> Care to show us the code for your "non bloated" "one-place" visitor
>>>> class for the above 2 problems? (like I did, you can omit the code that
>>>> is common for all operations).
>>>
>>> Yes, sorry for gg breaking all formating.
>>>>>
>>>>>> In practice this can be
>>>>>> anything, examples (non-exhaustive): a enum type discriminator, virtual
>>>>>> cast method, (in modern languages), pattern matching.
>>>>>>>
>>>>>>>> 3. add a single method to B, e.g. acceptVisit that takes [ref to]
>>>>>>>> Visitor and calls doOp on it. NOTE: only one such method is needed, no
>>>>>>>> matter how many ops you are adding.
>>>>>>>
>>>>>>> Not enough. Every type processed has to have that copy-paste
>>>>>>> acceptVisit method. Otherwise double dispatch does not work.
>>>>>>
>>>>>> Double dispatch works just fine without copy-pasting. Dispatch #1 (by
>>>>>> the operation type) is done by the virtual method doOp. Dispatch #2 (by
>>>>>> the object type) is done by using the actual type to call specific
>>>>>> methods and/or calling virtual methods, on the object ref.
>>>>>
>>>>> Of course huge switch or calling virtual method (or now even multiple)
>>>>> work without the whole pattern. Read up on "double dispatch".
>>>>> It is tricky like I already said.
>>>>>
>>>> Surely you are the most competent software engineer understanding
>>>> multiple dispatch. Care to point out a "huge switch" in either of the
>>>> above 2 examples?
>>>
>>> There are no double dispatch in your code so the whole acceptVisit, doOp
>>> add nothing.
>>
>> Yes it is, see above (or directly Wikipedia article on multiple dispatch
>> for the examples on how Double dispatch can be implemented in C++).
>>
> Do not consider multiple dispatch before figuring what double
> dispatch is.
not again. I already cited the definition of what DD is. You refer to a 
particular implementation as ff it were the definition.

>   
>>> These
>> Unsure I understand what "these" refers to, will assume my doOp and
>> acceptVisit functions
>>> were entirely added to do double dispatch
>>
>> correct. acceptVisit dispatches by op (the 1st parameter of the outer
>> structure "visit" operation) and doOp dispatches by object type (in my
>> examples, either by using its base type's virtual function only or via a
>> combination of the same and `if' statement on virtual cast result)
>>
> Except on your case it does not. There is just single function that
> decides if to draw red or green border.
yes, depending on the type. Do you expect colors, styles, font foundries 
and textures from an example on visitor? You are welcome to provide your 
own code (the above does not work).

> 
>>> to add
>>> virtual methods to large potentially unrelated data hierarchies without
>>> changing any of classes in those hierarchies.
>> I assume, by "potentially unrelated" data hierarchy, you mean the new op
>> has to behave entirely differently on every type of the hierarchy and
>> does not need common data. The issue with this assumption is that the
>> very first op you add might relate to the classes like this with but the
>> second might benefit from exploiting the type similarities or grouping
>> them differently (not even necessary in groups compact in the hierarchy
>> graph).
>>> What you do is not even
>>> use case for visitor pattern.
>> Says Wikipedia:
>>
>> "
>> What problems can the Visitor design pattern solve?
>>
>> It should be possible to define a new operation for (some) classes of an
>> object structure without changing the classes.
>> "
> 
> "When new operations are needed frequently and the object structure
> consists of many unrelated classes, it's inflexible to add new subclasses
> each time a new operation is required because "[..] distributing all these
> operations across the various node classes leads to a system that's hard
> to understand, maintain, and change."
You know what you did? You made me dig out my (licensed) CD-ROM with GoF 
book. Thank you! As I suspected, the Wikipedia quoted GoF in absolutely 
wrong context. There is nothing about "adding new subclasses" in GoF 
book, it is all about changing Node hierachy classes. The complete 
paragraph says:

"
This diagram shows part of the Node class hierarchy. The problem here is 
that distributing all these operations across the various node classes 
leads to a system that's hard to understand, maintain, and change. It 
will be confusing to have type-checking code mixed with pretty-printing 
code or flow analysis code. Moreover, adding a new operation usually 
requires recompiling all of these classes. It would be better if each 
new operation could be added separately, and the node classes were 
independent of the operations that apply to them.
"
(this paragraph is below the diagram depicting a part of Element 
hierarchy, has nothing to do to Visitor classes or adding "new 
subclasses". Unsure where this came from).

Whoever wrote this page should be .. well, politely reprimanded.

The quote above also emphasizes my point that adding compilation 
dependency to the element type hierarchy shall not be taken lightly.


> 
> Exactly. But if all you need is single little function then write single
> little function. Neither single dispatch (virtual functions) nor double
> dispatch (matching visitor classes to data hierarchy classes) are
> needed for single little function. How did your function being in
> visitor make anything easier to understand, maintain and change?
How many operations should I have exemplified? I showed 2 and stated the 
Visitor is applicable when one is to add plenty -- what else do you 
expect, not providing a single complete counterexample for the same problem.

> 
>>
>> "What I do" achieves the above IMO, so it is definitely the use case.
>> You might argue whether or not my solution is a Visitor pattern (it is,
>> at least it includes all canonical Visitor participants (Abstract
>> Visitor, Concrete Visitors, Abstract Element, Concrete Elements (an
>> object structure providing access to the elements is implied)) and none
>> extra) but I don't understand how it can be argued that my code does not
>> define a new operation for (some) classes of an object structure. And it
>> surely does not change the classes.
>>
> That is especially bad and confusing when something looks like a
> pattern, uses names from that pattern but actually isn't the pattern
> at all in essence.

Why is it you and not I who gets to define the "essence"? I stated the 
formal Visitor applicability conditions exactly to make this discussion 
objective, to the point, and maybe agree to something useful. How can we 
agree on anything useful if I start referring to some esoteric "essence" 
as I understand it?



[toc] | [prev] | [next] | [standalone]


Page 1 of 3  [1] 2 3  Next page →

Back to top | Article view | comp.lang.c++


csiph-web