There's the multi-dispatch idiom: For each override type of the first
parameter. declare a different virtual function to dispatch the second
parameter.
http://en.wikipedia.org/wiki/Multiple_dispatch
But it requires that the definition of every polymorphic class of one
parameter #includes the definition of all polymorphic types of the
following parameter. This makes the code messy, and not scalable due
to circular dependencies between modules.
I had to use them for a some medium size hirarchy and this idiom
became quite a bother. Not to mention that users coult not add their
types becuase this idiom is circular dependant.
I'm more interested in some library that would support this in a more
generic way.
I suppose any such library would need some compiler specific code for
each compiler that it supports, but the API should be the same.
BTW, Stroustrup in "The Design and Evolution of C++ Bjarne Stroustrup"/
13.8 comsiders adding multimethods as a c++ language favorable.
Although less important than many other features. And there are some
proposals for it. However, I don't know there's currently any point in
the future when it's predicted to be added. And a library solutions
seems good
itaj
Not necessarily. If your arguments are references (as they should be),
you only need forward-declarations to declare those member functions.
In the translation unit, of course, you'll need the corresponding
headers included. But that's no mess, that's just a necessity.
>[..]
V
--
I do not respond to top-posted replies, please don't ask
Agreed.
But another big problem is also that you need many different virtual
functions declarations each belongs to a certain polymorphic class of
the next parameter. In the wikipedia example the names of the
functions contain the class name, or you can add a dummy parameter
using the type. But they must be different virtual functions declared.
This is also what I meant by circular dependency, not just the
#include.
The problem with that is that one cannot use this multimethods idiom
in a core module with intentions for users of the module to inherit
his classes and define their overrides.
itaj
Uh... Yes. If an object of classA has to interact with objects of
classB, classC and classD, all those classes will need virtual functions
to interact with an object of classA. And if they need to interact with
each other, they *need to know* about each other. And if you need to
override those behaviors, you're going to have to provide some way for
those objects to make the distinction between objects with which they
need to interact. If that all is a "well, duh" moment for you, don't
blame the language or the model, change them.
> The problem with that is that one cannot use this multimethods idiom
> in a core module with intentions for users of the module to inherit
> his classes and define their overrides.
Why not? The core module provides a way for objects *defined in it* to
interact. Why can't you expand the core module model and provide more
interaction between yet unknown classes (that you define in your own
modules)? Do you see the idiom breaking down somehow?
What problem are you trying to solve? Let's try together. Show us
where you hit an obstacle, perhaps we can come up with a solution...
On 22 Feb., Victor Bazarov wrote:
> Why not? The core module provides a way for objects *defined in it* to
> interact. Why can't you expand the core module model and provide more
> interaction between yet unknown classes (that you define in your own
> modules)? Do you see the idiom breaking down somehow?
One of the problems is that you would have to add overloaded versions
for each of your derived class in the base class interface. This may
not be possible for libraries unless it is a header-only library.
Regards,
Stuart
What I was saying about this specific dynamic_cast dispatch idiom, is
that I don't think it's a very good one.
By "mess" I meant extra bookkeeping work on code management, that
would be unnecessary if there was a better library to support
multimethods instead this specific idiom.
I'm trying see if anyone knows such library, or a better idiom.
> > The problem with that is that one cannot use this multimethods idiom
> > in a core module with intentions for users of the module to inherit
> > his classes and define their overrides.
>
> Why not? The core module provides a way for objects *defined in it* to
> interact. Why can't you expand the core module model and provide more
> interaction between yet unknown classes (that you define in your own
> modules)? Do you see the idiom breaking down somehow?
>
> What problem are you trying to solve? Let's try together. Show us
> where you hit an obstacle, perhaps we can come up with a solution...
>
Very, well.
I won't get into descibing the real module I was working on that
triggered my problem, but I can describe a very short example to
demonstrate it.
For example:
An animal kingdom module defines base classes Carnivour and Prey.
And a multimethod:
void hunt( Carnivour const&, Prey& );
I want to enable users of my module to have their classes inherited
from Carnivour or Prey, and enable them to define override functions
for hunt special for their types.
A certain user then create his classes: Lion, Anaconda and Bear
derived from Carnivour, Giraffe and Gazelle derived from Prey.
He also wants to override the possible hunt implementation.
The following code demonstrates how I wished it could be written
(based on the proposition in "The Design and Evolution of C++ Bjarne
Stroustrup").
I'm looking for a library that will support such multimethods.
Basically replacing the hipothetic language syntax with some library
constrcuts. But enable the same general construction and dependency of
the module and user code.
The dynamic_cast dispatch idiom cannot do that (it's becoming too
lengthy, I'll demonstrate if further post).
//using some hipothetic multimethod syntax
//////my module "animal kingdom"
////animal_kingdom/carnivour.h
class Carnivour
{
};
////animal_kingdom/prey.h
class Prey
{
};
////animal_kingdom/hunt.h
//define a multimethod void hunt( Carnivour const&, Prey& )
#include "carnivour.h"
#include "prey.h"
void hunt( virtual Carnivour const& carnivour, virtual Prey& prey ) =
0;
//////From here on the user code
////lion.h
#include "carnivour.h"
class Lion: public Carnivour
{
};
////the same way files for:
class Anaconda: public Carnivour {};
class Bear: public Carnivour {};
class Giraffe: public Prey {};
class Gazelle: public Prey {};
////hunt_override_1.cpp
#include "lion.h"
#include "Gazelle.h"
void hunt( override Lion&, override Gazelle& )
{
//jumps on it and bite its neck
}
//and same for:
void hunt( override Lion&, override Girrafe& )
{
//bite its ass
}
void hunt( override Anaconda&, override Gazelle& )
{
//inject venom
}
//Anaconda can't kill girrafes so no override for that one
void hunt( override Bear&, override Prey& )
{
//because Bears catch everything in the same way lol.
}
//main.cpp
int main()
{
Carnivour& carnivour = Bear(...);
Prey& prey = Gazelle(...);
void const result( hunt( carnivour, prey ) );
}
itaj
>
> What I was saying about this specific dynamic_cast dispatch idiom, is
> that I don't think it's a very good one.
> By "mess" I meant extra bookkeeping work on code management, that
> would be unnecessary if there was a better library to support
> multimethods instead this specific idiom.
> I'm trying see if anyone knows such library, or a better idiom.
>
> The dynamic_cast dispatch idiom cannot do that (it's becoming too
> lengthy, I'll demonstrate if further post).
>
Well, I was wrong about "cannot do that". It is possible for this
user.
But it requires listing all dynamic_cast options for derived class.
And the user still has to list them all in one place.
On the other hand if someone later wants to add carnivour and prey
classes he must have access to the implementations of other classes.
I'll work on a good example for that.
In any case I think it's much more bookkeeping than could be with some
library support. Especially if it were 3 or more virtual parameters.
However, I am mainly interested in how people usually code such cases,
without any reference to my opinion about this idiom.
So whichever way you use (or would use) in such cases, I'll take it as
your answer to my question (this idiom or anything else).
itaj
> I won't get into descibing the real module I was working on that
> triggered my problem, but I can describe a very short example to
> demonstrate it.
>
> For example:
> An animal kingdom module defines base classes Carnivour and Prey.
> And a multimethod:
> void hunt( Carnivour const&, Prey& );
> I want to enable users of my module to have their classes inherited
> from Carnivour or Prey, and enable them to define override functions
> for hunt special for their types.
> A certain user then create his classes: Lion, Anaconda and Bear
> derived from Carnivour, Giraffe and Gazelle derived from Prey.
> He also wants to override the possible hunt implementation.
>
> The following code demonstrates how I wished it could be written
> (based on the proposition in "The Design and Evolution of C++ Bjarne
> Stroustrup").
> I'm looking for a library that will support such multimethods.
> Basically replacing the hipothetic language syntax with some library
> constrcuts. But enable the same general construction and dependency of
> the module and user code.
[snip]
The following should scale okay.
#include <iostream>
class CarnivourDispatch;
class Prey
{
public:
virtual void dispatchMe (CarnivourDispatch*) = 0;
};
class Carnivour
{
public:
virtual void hunt (Prey& prey) = 0;
};
class Giraffe;
class Gazelle;
class CarnivourDispatch : public Carnivour
{
public:
virtual void hunt (Prey& prey)
{
prey.dispatchMe (this);
}
virtual void _hunt (Gazelle&) = 0;
virtual void _hunt (Giraffe&) = 0;
};
class Lion: public CarnivourDispatch
{
protected:
virtual void _hunt (Gazelle& Gazelle)
{
std::cout << "Lion jumps on gazelle and bites its neck.";
}
virtual void _hunt (Giraffe&)
{
std::cout << "Lion bites giraffe's ass";
}
};
class Giraffe : public Prey
{
public:
void dispatchMe (CarnivourDispatch* Hunter)
{
Hunter->_hunt (*this);
}
};
class Gazelle : public Prey
{
public:
void dispatchMe (CarnivourDispatch* Hunter)
{
Hunter->_hunt (*this);
}
};
//main.cpp
int main()
{
Carnivour* carnivour = new Lion;
Prey* prey1 = new Gazelle;
Prey* prey2 = new Giraffe;
carnivour->hunt (*prey1);
carnivour->hunt (*prey2);
}
Probably the dispatchMe-methods in Prey derived class should be added
by a template, but I tried to keep it simple.
Regards,
Stuart
> The following should scale okay.
[snip code]
You replace a dynamic_cast if-else-if list with a virtual function
Prey::dispatchMe. This save the detailing on 1 parameter. I'm not sure
it would be so helpful for 3 or more virtual parameters.
Also the more internal animal_kingdom module contains Prey which has
to know (forward-dec) the name of CarnivourDispatch which in turn has
to know all polymorphic types under Prey. As it is, it breaks the
encapsulation of the module, becuase user must make it somehow aware
of his different derivations. I think that would be especially more
problematic if there was another user-code adding more derivations,
but that would want to use some of these derivations too.
> Probably the dispatchMe-methods in Prey derived class should be added
> by a template, but I tried to keep it simple.
Yeah, ultimately everything should work conviniently for override
implementation using some template functions, and also if any of the
base classes (Carnivour or Prey) was actualy a template class.
I don't think we stepped on anything for which that would cause
difference.
I'm sorry what I say here is not 100% formally clear. It's more like a
brainstorm to evaluate idioms for multimethods.
itaj
>
> BTW, Stroustrup in "The Design and Evolution of C++ Bjarne Stroustrup"/
> 13.8 comsiders adding multimethods as a c++ language favorable.
> Although less important than many other features. And there are some
> proposals for it. However, I don't know there's currently any point in
> the future when it's predicted to be added. And a library solutions
> seems good
>
...Stroustrup in "The Design and Evolution of C++ Bjarne Stroustrup"
paragraph 13.8, considers multimethods as a c++ language feature, a
favorable addition. But less important...
itaj
HTH
Paul.
Hi ita,
If g++ is available, you can use the -std=gnu++0x option to enable
variadic templates and then use the code demonstrated the
test driver here:
http://svn.boost.org/svn/boost/sandbox/variadic_templates
/libs/composite_storage/sandbox/pack
/one_of_multiple_dispatch.test.cpp
to do multimethods. Note that that demo uses 2 different methods.
One uses a disjoint union data type (the one with the one_of_maybe tag)
the other uses virtual methods (which is the one you're interested in).
To select the virtual method version, include:
#define REIFIER_VISITOR
in the driver.
HTH.
-Larry
>
> Hi ita,
>
> If g++ is available, you can use the -std=gnu++0x option to enable
> variadic templates and then use the code demonstrated the
> test driver here:
Sure c++0X is good.
>
> http://svn.boost.org/svn/boost/sandbox/variadic_templates
> /libs/composite_storage/sandbox/pack
> /one_of_multiple_dispatch.test.cpp
>
> to do multimethods. Note that that demo uses 2 different methods.
> One uses a disjoint union data type (the one with the one_of_maybe tag)
> the other uses virtual methods (which is the one you're interested in).
> To select the virtual method version, include:
>
> #define REIFIER_VISITOR
>
> in the driver.
>
I look at this file, and I'm not sure where to begin.
Is there any documentation I can read for the code it uses?
I only had the boost release version. I'm downloading this svn tree
now...
Also, this sandbox is like new libraries that aren't yet in boost?
Do you mean that boost will include this multimethod support?
itaj
http://article.gmane.org/gmane.comp.lib.boost.user/65480
shows it has quadratic performance w.r.t. number of variants.
Although boost::variant also suffers from this, it does
suffer as badly, as indicated by the timing curves shown
in the vault .zip file.
Is it supposed to be possible do checkout the whole svn tree?
It keeps breaking.
Should I better take just part?
>
> Not unless I finally do some documentation and submit it for
> review and it's accepted.
For compisite_storage you mean? Or certain part?
> However, I'm still working on other things and still have to
> figure a way to make the multiple-dispatch code faster.
> A timing comparison w.r.t. boost::variant is in the boost vault
> as indicated by this post:
>
> http://article.gmane.org/gmane.comp.lib.boost.user/65480
>
> shows it has quadratic performance w.r.t. number of variants.
What exactly is the name of
"variants"? Is it like number of virtual parameters for the function?
> Although boost::variant also suffers from this, it does
> suffer as badly, as indicated by the timing curves shown
> in the vault .zip file.
One measure is the assignment operator.
What does the binary test do?
I know nothing about boost development, what does it measure? Can you
point me to some reading about it?
So one_of_multiple_dispatch is supposed to do what I want?
What exactly is the relation of it with one_of_maybe?
Is it only you working on it? Which parts?
itaj
This is about "compile faster" right?
itaj
>
> Not unless I finally do some documentation and submit it for
> review and it's accepted.
> However, I'm still working on other things and still have to
> figure a way to make the multiple-dispatch code faster.
> A timing comparison w.r.t. boost::variant is in the boost vault
> as indicated by this post:
>
That is compilation time, right?
itaj
So, after I managed to check out the code, it makes more sense.
But, tell me if the following is correct:
using this library on a hirarchy of classes and a certain multimethod.
- I have to collect the declarations of all different overrides in the
same functor class, like functor3 and functor_any do.
- I have to collect all the concrete classes of the hirarchy in one
list, as under hosts_concrete<>
If so, I think this API doesn't solve the main disadvantages I see
with the dynamic_cast idiom.
In my example it would look about:
* except I'm not sure what should be ResultType in there, becuase
hunt() returns void, but what happens if other multimethods return
other types?
struct Carnivours_concrete
: mpl::package< Lion, Anaconda, Bear >
{
};
class Carnivour
{
typedef reifier_visit_abstract_seq< ResultType
, typename Carnivours_concrete::type>
visitor_abstract;
virtual ResultType accept( visitor_abstract const&)const=0;
};
class Lion: public Carnivour
{
...
ResultType accept( visitor_abstract const& a_visitor)const
{
return a_visitor.visit(*this);
}
};
class hunt_functor
{
void operator()( Lion const&, Gazelle& )
{
//jumps on it and bite its neck
}
//and same for:
void operator()( Lion const&, Girrafe& )
{
//bite its ass
}
void operator()( Anaconda const&, Gazelle& )
{
//inject venom
}
//Anaconda can't kill girrafes so no override for that one
void operator()( Bear const&, Prey& )
{
//because Bears catch everything in the same way lol.
}
};
itaj
<snip>
> void operator()( Anaconda const&, Gazelle& )
> {
> //inject venom
>
I do like your wildlife theme but, Anacondas are constrictors.
> }
>
> //Anaconda can't kill girrafes so no override for that one
>
Debateable. :)
GL with your porject , its beyond me atm.
Yes.
> - I have to collect all the concrete classes of the hirarchy in one
> list, as under hosts_concrete<>
Yes. Otherwise, how could you define the abstract visitor class in an
extensible way?
>
> If so, I think this API doesn't solve the main disadvantages I see
> with the dynamic_cast idiom.
>
>
> In my example it would look about:
> * except I'm not sure what should be ResultType in there, becuase
> hunt() returns void, but what happens if other multimethods return
> other types?
I'm not sure. I remember finding, for some reason, the using
reifier_visitor was less flexible the reifier_switch for, IIRC, this
reason. I'll have to think more on this.
>
> struct Carnivours_concrete
> : mpl::package< Lion, Anaconda, Bear >
> {
> };
>
>
> class Carnivour
> {
> typedef reifier_visit_abstract_seq< ResultType
> , typename Carnivours_concrete::type>
> visitor_abstract;
>
> virtual ResultType accept( visitor_abstract const&)const=0;
> };
>
> class Lion: public Carnivour
> {
> ...
>
> ResultType accept( visitor_abstract const& a_visitor)const
> {
> return a_visitor.visit(*this);
> }
> };
>
> class hunt_functor
> {
>
>
> void operator()( Lion const&, Gazelle& )
> {
> //jumps on it and bite its neck
> }
>
> //and same for:
> void operator()( Lion const&, Girrafe& )
> {
> //bite its ass
> }
>
> void operator()( Anaconda const&, Gazelle& )
> {
> //inject venom
>
> }
>
> //Anaconda can't kill girrafes so no override for that one
>
> void operator()( Bear const&, Prey& )
> {
> //because Bears catch everything in the same way lol.
>
> }
>
>
> };
>
> itaj
Let me see if I can implement this with reifier_visitor.
I'll get back to you.
Thanks for the feedback!
-regards,
Larry
I suppose the general idea could maybe be to hold some static variable
of dispatch matrices and edit it in constructors of other static
variables.
But that is only good for pure runtime dispatch, from what I figured,
your library is about compile type dispatching, so maybe it doesn't
have enough in common with the solution I'm looking for. I'm not sure.
>
>
> > If so, I think this API doesn't solve the main disadvantages I see
> > with the dynamic_cast idiom.
>
> > In my example it would look about:
> > * except I'm not sure what should be ResultType in there, becuase
> > hunt() returns void, but what happens if other multimethods return
> > other types?
>
> I'm not sure. I remember finding, for some reason, the using
> reifier_visitor was less flexible the reifier_switch for, IIRC, this
> reason. I'll have to think more on this.
>
Mainly I deduced my conclusion from the test code, seeing there how
you used reifier_visitor and reifier_apply.
I just gazed at the code of reifier_visitor, I don't know how it works
at all, neither the difference with reifier_switch.
itaj
I think so.
> It keeps breaking.
> Should I better take just part?
Yes, at least according to the following post:
http://article.gmane.org/gmane.comp.lib.boost.user/65939/match=using+sandbox+es
>
>>
>> Not unless I finally do some documentation and submit it for
>> review and it's accepted.
>
> For compisite_storage you mean? Or certain part?
Well, now that you mention it, I'd have to provide documentation for
mpl, at least, and maybe some other variadic template libraries used
by composite storage.
>
>> However, I'm still working on other things and still have to
>> figure a way to make the multiple-dispatch code faster.
>> A timing comparison w.r.t. boost::variant is in the boost vault
>> as indicated by this post:
>>
>> http://article.gmane.org/gmane.comp.lib.boost.user/65480
>>
>> shows it has quadratic performance w.r.t. number of variants.
>
> What exactly is the name of
> "variants"? Is it like number of virtual parameters for the
>function?
I was unclear. If you have a types:
typedef variant<T11,T12,...,T1n_1> var_1;
typedef variant<T21,T22,...,T2n_2> var_2;
...
typedef variant<Tm1,Tm2,...,T1n_m> var_m;
and an overloaded function, f, taking m arguments where:
arg_1 can be one of the var_1 template args.
arg_2 can be one of the var_2 template args.
...
arg_m can be one of the var_m template args.
the the compile-time goes up proportional to
n_1*n_2*...*n_m
However, that's just extrapolating from the benchmark results where m
was only 2, but you can see from the curve fits that the time does go
up proportional to n_1*n_2.
>
>> Although boost::variant also suffers from this, it does
>> suffer as badly, as indicated by the timing curves shown
>> in the vault .zip file.
>
> One measure is the assignment operator.
> What does the binary test do?
If you mean the TestVariantBinary.cpp in the sandbox, then it is the
test driver for comparing 3 methods for doing binary visitation:
boost::variant
boost/composite_storage/pack/multiple_dispatch
A specialization similar to boost::variant but optimized
for smart pointers. This was developed by Paul Bormans.
> I know nothing about boost development, what does it measure?
I don't know about boost development, but the measurement done
by the Makefile in the variants_compare.zip in the vault is that done
by the g++ compiler when passed the -ftime-report option. More
precisely it measures the TOTAL time output by that report option.
> Can you point me to some reading about it?
I'd just try using the -ftime-report option to see the output. I just
picked the TOTAL time part of the output because that seemed good
enough.
>
> So one_of_multiple_dispatch is supposed to do what I want?
Yes, when using reifier_visitor as 1st arg to reify_apply.
> What exactly is the relation of it with one_of_maybe?
I should clarify:
There are two methods used:
1) reifier_visitor
2) reifier_switch
Each method uses an overloaded function:
f(c_1,c_2,...,c_m)
where:
c_1 may be from one of a set of types, T11, T21,...,T1n_1
c_2 may be from one of a set of types, T21, T22,...,T2n_2
...
c_m may be from one of a set of types, Tm1, Tm2,...,T2n_m
IOW, there are n_1*n_2*....*n_m overloads of f.
The dispatching is done with a call such as:
reify_apply<reifier_xxx>(f,a_1,a_2,...,a_m)
where:
xxx = visitor or switch
a_1,a_2,...,a_m are abstract arguments which are "reified"
(turned into conrete arguments) by reifier_xxx.
The differences between the abstract types of these two methods is
described next.
1) reifier_visitor:
With virtual functions you have an inheritance hierarchy:
base_type
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
|/ \|/ \|
derive_1 derive_2 derive_3
and base_type has virtual function taking an abstract visitor:
struct abs_visitor
{
virtual void visit(derive_1&)=0;
virtual void visit(derive_2&)=0;
virtual void visit(derive_3&)=0;
};
and each derive_i has the concrete visit functions, reflecting the
standard visitor pattern:
http://www.dofactory.com/Patterns/PatternVisitor.aspx
The correspondence between the above inheritance diagram and the code
here:
http://svn.boost.org/svn/boost/sandbox/variadic_templates
/libs/composite_storage/sandbox/pack
/one_of_multiple_dispatch.test.cpp
is:
here code
---- ----
base_type template<typename ResultType>
struct host_abstract;
derived_I template<unsigned I,typename ResultType>
struct host_concrete;
The abstract types in this method are like base_type.
The concrete types are like derived_I.
2) reifier_switch
In contrast, with reifier_switch, you have a disjoint sum
data structure implemented using a tag or discriminator
and a set of "bounded"(the name used by boost::variant docs)
types. This type is declared as:
enum tag_type
{ tag_1
, tag_2
, tag_3
};
typedef
multiple_dispatch::container
< tags::one_of_maybe
, mpl::integral_c<tag_type,tag_1>//discriminator
, derive_1 //bound type corresponding to tag_1
, derive_2 //bound type corresponding to tag_2
, derive_3 //bound type corresponding to tag_3
>
base_type;
The abstract types in this method are like base_type.
The concrete types are like derived_I.
Does the above make things clearer?
>
> Is it only you working on it?
Yes.
> Which parts?
All.
>
> itaj
>
>
replace_source_with_target_ptr.hpp
got the attached to run and print:
Lion jumps on Girrafe and bites its ass.
However, when the #if on line 188 is switch, I get
very obscure compile-time error messages. So, it
can't handle concrete references. It apparently
can only handle abstract references.
I'll see if I can correct that.
-Larry
hosts_concrete<Lion>
is an incomplete type in reifier_visitor.hpp line 209.
This is where there's a hosts_concrete<HeadAbstract>
and HeadAbstract is Lion which is not abstract.
hosts_concrete is supposed to be specialized on
the abstract class to return the set of concrete
classes for that abstract class. Since Lion already
is an concrete class, there's no need to specialize
hosts_concrete on it; hence, the error.
Will think about solution.
If you can afford C instead of C++, you can read papers and use the
COS lib
http://cern.ch/laurent.deniau/html/cos-dls09-draft.pdf (short
version)
http://cern.ch/laurent.deniau/html/cos-oopsla09-draft.pdf (long
version)
regards,
Laurent.
>
> If you can afford C instead of C++, you can read papers and use the
> COS lib
>
Well, that is basically my idea. Although when you work in C, the
language doesn't offer almost any of the features of this object
system (classes, inheritance, function resolution, templates?), so it
seems right to use it for everything. Unlike C, C++ has core features
for everything that this library does, except multimethods.
Principally I want to use the core features of C++ to write my code. I
only need a library that supports multimethods, that would extend them
over hirarchies that are already written with core C++ features. So in
that sense, I don't think I can afford transforming all my hirarchies
into any library. That would beat the purpose of finding the crispiest
idiom for multimethods.
itaj
Well, I thought of a way to somewhat weaken that restriction.
Using partial specialization, it's possible to break the overrides to
difference locations, except that the invocations have to include the
declarations for them.
for example:
template< template< typename Args ... > Ftor >
class functor_forward
{
typedef Ftor::ResultType ResultType;
//data
private: Ftor ftor_;
//xtor
public:
template< Args1 ... >
functor_forward( Args1&& args )
:
ftor_( std::forward( args... ) )
{}
//methods
public: ResultType operator()(Args const&... args) const
{
return ftor_( args ... );
}
};
Then user code can write partial specializations:
template< typename ConcreteCarnivour, typename ConcretePrey >
class hunt_functor
{
};
class hunt_functor< Lion, Giraffe >
{
public: typedef void ResultType;
public: void operator()( Lion const& lion, Giraffe& giraffe ) const
{
//...
}
};
//and somewhere else in the code:
class hunt_functor< Bear, Prey >
{
...
};
//but invocation must include forward-dec for all specializations
pack::multiple_dispatch::reify_apply<
pack::multiple_dispatch::reifier_xxx >(
functor_forward< hunt_functor >(), carnivour, prey )
It's also possible to use function overload:
template< typename ConcreteCarnivour, typename ConcretePrey >
class hunt_functor
{
public: typedef void ResultType;
public: void operator()(
ConcreteCarnivour const& carnivour,
ConcretePrey& prey ) const
{
return hunt( carnivour, prey );
}
};
void hunt( Lion const&, Giraffe& );
void hunt( Bear cosnt&, Prey& );
//implementations wherever.
pack::multiple_dispatch::reify_apply<
pack::multiple_dispatch::reifier_xxx >(
functor_forward< hunt_functor >(), carnivour, prey )
itaj
> There's the multi-dispatch idiom: For each override type of the first
> parameter. declare a different virtual function to dispatch the second
> parameter.http://en.wikipedia.org/wiki/Multiple_dispatch
> But it requires that the definition of every polymorphic class of one
> parameter #includes the definition of all polymorphic types of the
> following parameter. This makes the code messy, and not scalable due
> to circular dependencies between modules.
The classical solution is:
class Derived1;
class Derived2;
// ...
class Base
{
protected:
virtual void doSomething(Derived1&);
virtual void doSomething(Derived2&);
// ...
public:
virtual void doSomething(Base& other);
};
class Derived1 : public Base
{
protected:
virtual void doSomething(Derived1& other)
{
// Derived1, Derived1 handling...
}
virtual void doSomething(Derived2& other)
{
// Derived1, Derived2 handling...
}
public:
virtual void doSomething(Base& other)
{
other.doSomething(*this);
}
};
If the hierarchy is closed (all of the derived classes
identifiable, and managed by a single entity), and not too big,
this is fine. If you want to be able to easily expand the
hierarchy, then it is far from ideal, and anything over four or
five derived classes quickly becomes overwhelming.
Multiple dispatch on open or large hierarchies is problematic in
general. For n derived classes, you need n^2 functions
(regardless of how the dispatch works), and when you add
a derived class, you have to define its interactions with all of
the existing classes. It's best avoided, but it is possible to
make it work: you need something like a
std::map<std::pair<std::type_index, std::type_index>,void (*)
(Base&, Base&)>
, where void Base::doSomething(Base& other) isn't virtual, but
looks up the actual (free) function to call in the map. Getting
the map initialized with all of the necessary functions is
non-trivial, but if you can provide a default Base&, Base&
functionality, and only need a few specific overrides, it might
be OK.
> I had to use them for a some medium size hirarchy and this
> idiom became quite a bother. Not to mention that users coult
> not add their types becuase this idiom is circular dependant.
> I'm more interested in some library that would support this in
> a more generic way. I suppose any such library would need
> some compiler specific code for each compiler that it
> supports, but the API should be the same.
If you want an open hierarchy which supports all possible
combinations, the problem isn't the compiler; it's how to
provide all of the necessary functions. Each new class has to
provide all of the functions for it and all of the existing
classes. This quickly gets out of hand.
--
James Kanze
template
< typename HostConcrete
>
struct host_abstract
/**@brief
* Metafunction returning Abstract class
* of which HostConcrete is a member.
* IOW, does inverse of hosts_concrete.
*/
{
typedef
HostConcrete
type
//By default, HostConcrete is member
//of a type hierarchy starting at
//HostConcrete.
;
};
and them modify the argument, HeadAbstract, to hosts_concrete to
super of reifier_visitor by host_abstract<HeadAbstract>::type.
That converts any concrete type back to its abstract super.
Of course that's less than ideal because it requires
specializations of host_abstract to map all the derived classes
back to their abstract classes and causes mover virtual
function calls to undo what was just done by host_abstract.
Will upload the modified reifier_visitor.hpp to sandbox.
The modified driver, with all the host_abstract specializations,
is attached.
-Larry
There is no possible way that Double Dispatch could work without some
entity knowing all sub-classes that are involved in Double Dispatch.
The way I have shown is probably far from perfect, but it shows that
this problem can be solved by C++ without resorting to dynamic_casts.
> I think that would be especially more
> problematic if there was another user-code adding more derivations,
> but that would want to use some of these derivations too.
Well, if two users A and B add different objects, someone has to
figure what should happen if objects from user A meet objects of user
B.
SCNR:
#include <iostream>
// We want to be able to dynamically dispatch three different
// base classes, Prey, Carnivour, and Location, with n
// sub-classes. Since Double Dispatch uses vtables, we have to
// have a class with n^2 entries in its vtable. This class contains
// the actual algorithmic part (in our scenario this class will
// be the Carnivour class).
// To be able to implement Double Dispatch, we have to stuff
// one additional class with a vtable of n entries (Location).
///////////////////////////////////////////////////////////
class CarnivourDispatch;
class Location;
class Prey
{
public:
virtual void dispatchMe (CarnivourDispatch*,Location&) = 0;
};
///////////////////////////////////////////////////////////
class Location;
class Carnivour
{
public:
virtual void hunt (Prey& prey, Location& location) = 0;
};
///////////////////////////////////////////////////////////
// CarnivourDispatch needs to know all derived classes, both
// prey and locations.
class Plains;
class Ocean;
class Gazelle;
class Giraffe;
class Gnu;
class CarnivourDispatch : public Carnivour
{
public:
// This starts the double dispatch.
virtual void hunt (Prey& prey, Location& location)
{
prey.dispatchMe (this, location);
}
virtual void huntPreyLocation (Gazelle&, Plains&) = 0;
virtual void huntPreyLocation (Giraffe&, Plains&) = 0;
virtual void huntPreyLocation (Gnu&, Plains&) = 0;
virtual void huntPreyLocation (Gazelle&, Ocean&) = 0;
virtual void huntPreyLocation (Giraffe&, Ocean&) = 0;
virtual void huntPreyLocation (Gnu&, Ocean&) = 0;
};
///////////////////////////////////////////////////////////
template<class t_ImplementationClass>
class PreyDispatchHelper : public Prey
{
public:
void dispatchMe (CarnivourDispatch* Hunter, Location& location)
{
location.dispatchMe (Hunter, *static_cast<t_ImplementationClass*>
(this));
}
};
class Giraffe : public PreyDispatchHelper<Giraffe>
{
};
class Gazelle : public PreyDispatchHelper<Gazelle>
{
};
class Gnu : public PreyDispatchHelper<Gnu>
{
};
//////////////////////////////////////////////////////////////////////////
// We need to know all derived classes of Prey at compile-time.
// Forward declaration of all Prey-derived classes.
class Gazelle;
class Giraffe;
class Gnu;
template<class t_Base1, class t_Base2>
class TMultipleInheritance : public t_Base1, public t_Base2
{
public:
typedef t_Base1 TBase1;
typedef t_Base2 TBase2;
};
class NullPrey
{};
class PreyEnum : public TMultipleInheritance
<
Gnu,
TMultipleInheritance
<
Giraffe,
TMultipleInheritance
<
Gazelle, NullPrey
>
>
>
{
};
// LocationHelper2 adds the virtual methods
// virtual void dispatchMe (CarnivourDispatch*, Gazelle&) = 0;
// virtual void dispatchMe (CarnivourDispatch*, Gnu&) = 0;
// and so on to the Location interface.
template<class t_DummyArgument = int, class t_PreyEnum = PreyEnum>
class LocationHelper2 : public LocationHelper2<t_DummyArgument,
typename t_PreyEnum::TBase2>
{
public:
typedef LocationHelper2<t_DummyArgument, typename
t_PreyEnum::TBase2> TBaseLocationHelper2;
using TBaseLocationHelper2::dispatchMe;
virtual void dispatchMe (CarnivourDispatch* Hunter,typename
t_PreyEnum::TBase1& prey) = 0;
};
template<class t_DummyArgument>
class LocationHelper2<t_DummyArgument, NullPrey>
{
public:
typedef NullPrey TBaseLocationHelper2;
// We need this as base for the using-declaration in the
// non-specialized template.
void dispatchMe (){}
};
// We could as well drop this class, but it makes forward
// declarations much easier.
class Location : public LocationHelper2<>
{
};
////////////////////////////////////////////////////////////
// Implementation helper of the virtual methods of Location
template<class t_ImplemenationClass, class t_PreyEnum = PreyEnum>
class LocationDispatchHelper2 : public
LocationDispatchHelper2<t_ImplemenationClass, typename
t_PreyEnum::TBase2>
{
protected:
typedef LocationDispatchHelper2<t_ImplemenationClass, typename
t_PreyEnum::TBase2> TBaseLocationDispatchHelper2;
public:
using TBaseLocationDispatchHelper2::dispatchMe;
virtual void dispatchMe (CarnivourDispatch* Hunter,typename
t_PreyEnum::TBase1& prey)
{
Hunter->huntPreyLocation (prey,
*static_cast<t_ImplemenationClass*> (this));
}
};
template<class t_ImplemenationClass>
class LocationDispatchHelper2<t_ImplemenationClass, NullPrey> : public
Location
{
protected:
typedef Location TBaseLocationDispatchHelper2;
};
// Now we can easily add our locations.
class Plains : public LocationDispatchHelper2<Plains>
{
};
class Ocean : public LocationDispatchHelper2<Ocean>
{
};
class Lion: public CarnivourDispatch
{
protected:
virtual void huntPreyLocation (Gazelle& gazelle, Plains& plains)
{
std::cout << "Lion jumps on gazelle and bites its neck in the
plains." << std::endl;
}
virtual void huntPreyLocation (Giraffe& giraffe, Plains& plains)
{
std::cout << "Lion bites giraffe's ass in the plains" <<
std::endl;
}
virtual void huntPreyLocation (Gnu& gnu, Plains& plains)
{
std::cout << "Lion rides gnu to death in the plains" << std::endl;
}
virtual void huntPreyLocation (Gazelle&, Ocean&)
{
std::cout << "Lion cooks gazelle in Pacific Ocean." << std::endl;
}
virtual void huntPreyLocation (Giraffe&, Ocean&)
{
std::cout << "Lion runs over giraffe with jet skies." <<
std::endl;
}
virtual void huntPreyLocation (Gnu&, Ocean&)
{
std::cout << "Lion launches torpedo at gnu." << std::endl;
}
};
//main.cpp
int main()
{
Carnivour* carnivour = new Lion;
Location* location = new Plains;
Location* location2 = new Ocean;
Prey* prey1 = new Gazelle;
Prey* prey2 = new Giraffe;
Prey* prey3 = new Gnu;
carnivour->hunt (*prey1, *location);
carnivour->hunt (*prey2, *location);
carnivour->hunt (*prey3, *location);
carnivour->hunt (*prey1, *location2);
carnivour->hunt (*prey2, *location2);
carnivour->hunt (*prey3, *location2);
return 0;
}
Regards,
Stuart
> Multiple dispatch on open or large hierarchies is problematic in
> general. For n derived classes, you need n^2 functions
> (regardless of how the dispatch works), and when you add
> a derived class, you have to define its interactions with all of
> the existing classes. It's best avoided, but it is possible to
> make it work: you need something like a
> std::map<std::pair<std::type_index, std::type_index>,void (*)
> (Base&, Base&)>
> , where void Base::doSomething(Base& other) isn't virtual, but
> looks up the actual (free) function to call in the map. Getting
> the map initialized with all of the necessary functions is
> non-trivial, but if you can provide a default Base&, Base&
> functionality, and only need a few specific overrides, it might
> be OK.
>
>
> If you want an open hierarchy which supports all possible
> combinations, the problem isn't the compiler; it's how to
> provide all of the necessary functions. Each new class has to
> provide all of the functions for it and all of the existing
> classes. This quickly gets out of hand.
>
Ofcourse for k virtual parameters, the number of possible different
overrides is n^k. But so is the number of possible different static
overloads for a regular n-ary function in c++. But usually there are
some generalizations that are done on the possibilities that
practically cut them to around log(n)^k to n*log(n)^k sets and subsets
(just top of my head estimation). In both cases that should be done
using the hierarchy and templates.
* For conviniency I'll base on the hipothetic language feature. But
what I mean is that it should be possible to create a library that
would support the same general constructs, just instead of syntax
you'll have to define classes and static variable of the library type.
template< typename C, typename P >
hunt_impl( C const&, P& )
{
...
}
template< typename C, typename Horse1, typename Horse2 >
hunt_impl( C const& carnivour, Interbreed< Horse1, Horse2 >& prey )
{
...
}
void hunt( override< Bear > const& carnivour, override< Prey >& Prey )
{
return hunt_impl( carnivour, prey );
}
//the following syntax override 2 cases:
void hunt( override< Lion > const& carnivour, override< Giraffe,
Gazelle >& prey )
{
return hunt_impl( carnivour, prey );
}
void hunt( override< Anaconda > const& carnivour, override< Giraffe,
Gazelle >& prey )
{
return hunt_impl( carnivour, prey );
}
Now if anyone came to the conclusion that they really have to specify
all different combinations, in order to describe their problem
correctly, then so be it - such syntax can only make it easier, not
worse.
itaj
Hopefully. At least if n is large.
> In both cases that should be done using the hierarchy and
> templates.
> * For conviniency I'll base on the hipothetic language feature. But
> what I mean is that it should be possible to create a library that
> would support the same general constructs, just instead of syntax
> you'll have to define classes and static variable of the library type.
It certainly isn't difficult to come up with a solution for
a given hierarchy. I'm not sure about a generic solution;
I haven't given it a try, but it seems like it should be
possible. The problem is to make the lookup as effective as
possible: if you require n^k functions, each for the most
derived class, you can use a hash table of some sort on the two
(or more) type_info's. If you don't, it's more difficult, since
type_info doesn't give you information about the base classes.
And you need to specify some sort of ordering: either first
match, and insist that the client codinsert in the correct
order (with the most specific functions where they will be found
first), or best match, and you have to define "best", evaluate
all matches, and define behavior in case of ambiguity. It's not
an easy task.
--
James Kanze
>
> > In both cases that should be done using the hierarchy and
> > templates.
> > * For conviniency I'll base on the hipothetic language feature. But
> > what I mean is that it should be possible to create a library that
> > would support the same general constructs, just instead of syntax
> > you'll have to define classes and static variable of the library type.
>
> It certainly isn't difficult to come up with a solution for
> a given hierarchy. I'm not sure about a generic solution;
> I haven't given it a try, but it seems like it should be
> possible. The problem is to make the lookup as effective as
> possible: if you require n^k functions, each for the most
> derived class, you can use a hash table of some sort on the two
> (or more) type_info's. If you don't, it's more difficult, since
> type_info doesn't give you information about the base classes.
I wouldn't mind if it was intrusive. I.e. for every class in the
hierarchy the user would have to state the list of base classes.
> And you need to specify some sort of ordering: either first
> match, and insist that the client codinsert in the correct
> order (with the most specific functions where they will be found
> first), or best match, and you have to define "best", evaluate
> all matches, and define behavior in case of ambiguity. It's not
> an easy task.
>
I thought though there might have been such a library already
somewhere.
itaj
Well, multimethod is only the beginning of the game. The true feature
of COS is fast message multi-dispatch and generic delegation (message
forwarding) which require to transparently (on-the-fly) to marshall
(caller site) and un-marshall (callee site) the arguments of the
multimethods.
Coming back to C++, the problem with multimethod is that C++ support
multiple inheritance. It's a killer for generic manipulation of
function pointers and makes it's object model particularly complex
(and not easily extensible). The only way I found to partially solve
the problem is to build on-the-fly a (templated) pimpl object specific
to each multimethod with functor capabilities (in the C++ sense). The
idea is to shift problematic generic pointer to functions to
manageable generic pointer to simple objects. The rest relies more on
highly specialized (very fast) hash tables and algebraic properties of
group generator (for keys). On the other hand, generic delegation is
beyond what C++ can do because it already has too many core features
(main advantage of C).
> Principally I want to use the core features of C++ to write my code. I
> only need a library that supports multimethods, that would extend them
> over hirarchies that are already written with core C++ features. So in
> that sense, I don't think I can afford transforming all my hirarchies
> into any library. That would beat the purpose of finding the crispiest
> idiom for multimethods.
I understand your point. My conclusion from trying to port COS to C++
(still a dream) is that C++ needs native support for multimethods (cf.
BS paper for the best proposal I know) because multiple inheritance is
itself a core feature of C++. Otherwise, either code complexity,
compilation time or runtime speed will strongly suffer from
multimethods, if I compare to COS which is extremely simple for its
users and very fast.
The output of the currently uploaded predator_prey.cpp is:
Lion jumps on Girrafe and bites its ass.
Anaconda ignores Girraffe.
Bear mauls Gazelle
Lion jumps on Gazelle and bites its neck.
Not AFAICT. What's needed is a specialization of hosts_concrete for
each abstract class. For example, see the current predator_prey.cpp:
There's two specializations of hosts_concrete:
template<>
struct hosts_concrete
< Predator_abstract
>
: bmpl::package
< Lion
, Bear
, Anaconda
>
{
};
template<>
struct hosts_concrete
< Prey_abstract
>
: bmpl::package
< Gazelle
, Girrafe
>
{
};
Each argument after the functor argument to
reify_apply<reifier_visitor> must be a HostAbstract& for some
HostAbstract such that hosts_concrete<HostAbstract> is defined.
> But that is only good for pure runtime dispatch, from what I figured,
> your library is about compile type dispatching,
No. In each case, reifier_visitor and reifier_switch, dispatching is
done a runtime:
reifier_visitor:
This uses the classic visitor pattern where the abstract visit
methods occur in the superclasses of reifier_visitor. These
reifier_visitor superclasses are the:
reifier_visit_concrete_pack
< ReifierVisitor
, ConcretePkg
, ConcreteHead
, ConcreteTail...
>
classes. Each defines the visit method for ConcreteHead. Since
they are inherited one after the other, ultimately there's one
visit method for each class in
hosts_concrete<HostAbstract>::type.
reifier_switch:
For this method, there's no need for something like hosts_concrete
since the set of concrete classes, {C1,C2,...,Cn} are explicitly
listed in the template arguments to:
composite_storage::pack::container
< one_of_maybe
, Index0
, C1,C2,...,Cn
>.
In place of the virtual function calls to reify the abstract class
into its concrete class, the reifier_switch uses a switch
statement on the one_of_maybe which() result.
> so maybe it doesn't have enough in common with the solution I'm
> looking for. I'm not sure.
I hope the above clarifies the issue.
[snip]
>
> Mainly I deduced my conclusion from the test code, seeing there how
> you used reifier_visitor and reifier_apply. I just gazed at the
> code of reifier_visitor, I don't know how it works at all, neither
> the difference with reifier_switch.
The reify_apply trades calls with the reifier_XXX, where XXX in
{switch,visitor} n times, where n is the number of arguments to be
applied to the reify_apply functor argument. During each such trade,
the reifier reifies the next abstract argument to produce the concrete
counterpart, then calls reify_apply_impl with the modified argument
list. When reify_apply_impl is called with the complete concretized
argument list(see reify_apply.hpp:68), it applies the functor to the
concrete argument list.
Does that make things clearer?
HTH.
-regards,
Larry